SharePoint: HasRights and Edit Control Block

I recently found a bug in one of my codeplex projects Export Version History. A user was able to see the Export Version History option in Edit Control block aka ECB menu even if the user did not have ViewVersions permissions.

ECB Menu

Notice that in the above screenshot 'Export Version History' menu item is available whereas the out of the box 'Version History' menu item is correctly not available as current user does not have ViewVersions permissions. Also note that versioning is on in the list. 
One of my previous posts shows how a delegate control can be used to conditionally show menu item in ECB Menu. Time to revisit that code and see how this bug can be fixed. Following is the code from that post. It checks verEnabled property of Out of the box ctx object and if it is present it shows the ECB menu.
<script type="text/javascript">
    function Custom_AddListMenuItems(m, ctx) {
        AddECBMenuItems(m, ctx);  
    }

    function Custom_AddDocLibMenuItems(m, ctx) {
        AddECBMenuItems(m, ctx);
    }

    function AddECBMenuItems(m, ctx) {
        var pageUrl = ctx.HttpRoot + "/_layouts/NY.ExportVersionHistory/ExportVersionHistory.aspx?ID=" + currentItemID + "&amp;List=" + ctx.listName;
        if (ctx.verEnabled) {
            CAMOpt(m, "Export Version History", "window.open('" + pageUrl + "');", "/_layouts/images/NY.ExportVersionHistory/Excel_Small.png");
            CAMSep(m);
        }
    }
</script>
Now we need to check whether current user has ViewVersions permissions on the current item. So, I thought of writing the code in SharePoint JavaScript client object model. As client object model code runs asynchronously, it was obvious that ECB menu will be rendered before the async call is completed. Therefore, I started to write client object model code using JQuery deferred/Promises and came up with the following code:
function AddECBMenuItems(m, ctx) {
    var pageUrl = ctx.HttpRoot + "/_layouts/NY.ExportVersionHistory/ExportVersionHistory.aspx?ID=" + currentItemID + "&amp;List=" + ctx.listName;
    getItemPermissions(ctx.listName, currentItemID).then(
    function (permissions) {
        if (permissions.has(SP.PermissionKind.viewVersions)) {
            if (ctx.verEnabled) {
                CAMOpt(m, "Export Version History", "window.open('" + pageUrl + "');", "/_layouts/images/NY.ExportVersionHistory/Excel_Small.png");
                CAMSep(m);
            }
        }
    },
    function (sender, args) {
        console.log('An error occured while retrieving list item:' + args.get_message());
    });    
}

var getItemPermissions = function (listId, itemId) {
    var deferred = $.Deferred();
    var context = SP.ClientContext.get_current();
    var web = context.get_web();
    var list = web.get_lists().getById(listId);
    var item = list.getItemById(itemId);
    context.load(item, 'EffectiveBasePermissions');
    context.executeQueryAsync(
       Function.createDelegate(this,
           function () {
               var permissions = item.get_effectiveBasePermissions();
               deferred.resolve(permissions);
           }),
       Function.createDelegate(this,
           function (sender, args) { deferred.reject(sender, args); }));

    return deferred.promise();
};
Putting above code to test, I was surprised to see that that Export Version History menu item was not available even if the list had versioning enabled and current user had ViewVersions permissions on the list item. It turns out that ECB menu is immediately rendered as can be seen from the following screenshot. By the time code in the if statement is hit, ECB is already created.

But the question is how come OOB Version History is working properly. The answer to this question is present in SharePoint JavaScript file CORE.js. This file contains functions for creating ECB and it resides in Layouts folder. open the debug version of this file named CORE.debug.js as opposed to minified version as reading the latter file can be difficult. Find a function named AddVersionsMenuItem. This function is used by SharePoint to decide whether to show OOB Version History menu item or not. Here is the code of this function.
function AddVersionsMenuItem(m, ctxt, url) {
    if (currentItemID != null) {
        var strCurrentItemID = currentItemID.toString();

        if (strCurrentItemID.indexOf(".0.") >= 0)
            return;
    }
    var fixedItemId = currentItemID;

    if (currentItemIsEventsExcp) {
        if (currentItemID.indexOf(".") != -1)
            fixedItemId = (currentItemID.split("."))[0];
    }
    if (!HasRights(0x0, 0x40))
        return;
    var strDisplayText = Strings.STS.L_Versions_Text;
    var strAction = "NavigateToVersionsAspxV4(event, '" + ctxt.HttpRoot + "', 'list=" + ctxt.listName + "&ID=" + fixedItemId + "&FileName=" + url + "')";
    var strImagePath = ctxt.imagesPath + "versions.gif";
    var menuOption = CAMOpt(m, strDisplayText, strAction, strImagePath, null, String(800));

    CUIInfo(menuOption, "ViewVersions", ["ViewVersions"]);
    menuOption.id = "ID_Versions";
}
As can be see a call is made to HasRights function with two parameters. The first parameter is a hex representation of EmptyMask permission and second is the hex representation of ViewVersions.   
This page has hex and decimal values of SharePoint base permissions. So the simple change which needs to be done in order to fix the bug is to make a call to HasRights function to check the permissions. 
Finally, the updated code:
<script type="text/javascript">
    function Custom_AddListMenuItems(m, ctx) {
        AddECBMenuItems(m, ctx);  
    }

    function Custom_AddDocLibMenuItems(m, ctx) {
        AddECBMenuItems(m, ctx);
    }

    function AddECBMenuItems(m, ctx) {
        var pageUrl = ctx.HttpRoot + "/_layouts/NY.ExportVersionHistory/ExportVersionHistory.aspx?ID=" + currentItemID + "&amp;List=" + ctx.listName;
        if (ctx.verEnabled && HasRights(0x0, 0x40)) {
            CAMOpt(m, "Export Version History", "window.open('" + pageUrl + "');", "/_layouts/images/NY.ExportVersionHistory/Excel_Small.png");
            CAMSep(m);
        }
    }
</script>