Ext.namespace('Zarafa.core'); /** * @class Zarafa.core.EntryId * * Class for decoding entryids and for comparison between two entryids * we have basically two types of entryids object and store entryids. * * object entryids uses structure EID for entryids created using Kopano Core > 6 * and for older entryids it uses structure EID_V0. Store entryids are generally wrapped * with some extra information (like guid for provider, dll name) which should be removed * before comparing two store entryids, after removing this wrapping the unwrapped entryid * uses the format same as object entryids (EID or EID_V0). * * version flag in EID and EID_V0 are Kopano specific flag and indicates which structure is used * to create that entryid, EID always contains version as '01000000' and EID_V0 always contains * '00000000' as version flag. * * server part of EID and EID_V0 indicates server name and it can be variable length, padding can be * upto 3 bytes so it can be anything between 0 to 3 bytes. * * in public store public root folder, ipm_subtree and favorites folder are custom folders of Kopano * so it has static uniqueids. * * @singleton */ Zarafa.core.EntryId = (function() { /* Bit definitions for abFlags[3] of ENTRYID */ var ZARAFA_FAVORITE = '01'; /* GUID of root public folder */ var STATIC_GUID_PUBLICFOLDER = '00000000000000000000000000000003'; /* GUID of root favorite folder */ var STATIC_GUID_FAVORITE = '00000000000000000000000000000002'; /* GUID of ipm_subtree of public store*/ var STATIC_GUID_FAVSUBTREE = '00000000000000000000000000000001'; /* GUID of Global Addressbook */ var MUIDECSAB = 'AC21A95040D3EE48B319FBA753304425'; /* GUID of Contact Provider */ var MUIDZCSAB = '727F0430E3924FDAB86AE52A7FE46571'; /* GUID for OneOff entryid */ var MAPI_ONE_OFF_UID = '812B1FA4BEA310199D6E00DD010F5402'; /* Hardcoded ID used for generating entryid of addressbook container */ /*jshint unused:false*/ var ZARAFA_UID_ADDRESS_BOOK = '00000000'; /* Hardcoded ID used for generating entryid of global addressbook container */ var ZARAFA_UID_GLOBAL_ADDRESS_BOOK = '01000000'; /* Hardcoded ID used for generating entryid of global addresslists container */ var ZARAFA_UID_GLOBAL_ADDRESS_LISTS = '02000000'; var BASE_EID = Ext.extend(Object, { // The entryid which this object represents entryId : '', // The length of the entryid length : 0, // Constructor // param: Entryid The entryid represented by this object constructor : function(entryId) { if(entryId) { // always make entryids in uppercase so comparison will be case insensitive this.entryId = entryId.toUpperCase(); this.length = entryId.length; this.decomposeEntryId(this.entryId); } }, // Detect padding (max 3 bytes) from the entryId getPadding : function(entryId) { var padding = ''; var offset = 0; for (var iterations = 4; iterations > 0; iterations--) { if (entryId.substring(entryId.length - (offset + 2), entryId.length - offset) === '00') { padding += '00'; offset += 2; } else { // if non-null character found then break the loop break; } } return padding; } }); // Entryid from version 6 var EID = Ext.extend(BASE_EID, { abFlags : '', // BYTE[4], 4 bytes, 8 hex characters guid : '', // GUID, 16 bytes, 32 hex characters version : '', // ULONG, 4 bytes, 8 hex characters type : '', // ULONG, 4 bytes, 8 hex characters uniqueId : '', // GUID, 16 bytes, 32 hex characters server : '', // CHAR, variable length padding : '', // TCHAR[3], 4 bytes, 8 hex characters (upto 4 bytes) MIN_LENGTH : 88, name : 'EID', // decompose the entryid and populate all flags of entryid decomposeEntryId : function(entryId) { var offset = 0; // First determine padding, and remove if from the entryId this.padding = this.getPadding(entryId); entryId = entryId.substring(0, entryId.length - this.padding.length); this.abFlags = entryId.substr(offset, 8); offset =+ 8; this.guid = entryId.substr(offset, 32); offset += 32; this.version = entryId.substr(offset, 8); offset += 8; this.type = entryId.substr(offset, 8); offset += 8; this.uniqueId = entryId.substr(offset, 32); offset += 32; this.server = entryId.substr(offset); } }); // The entryid from the begin of Kopano till 5.20 var EID_V0 = Ext.extend(BASE_EID, { abFlags : '', // BYTE[4], 4 bytes, 8 hex characters guid : '', // GUID, 16 bytes, 32 hex characters version : '', // ULONG, 4 bytes, 8 hex characters type : '', // ULONG, 4 bytes, 8 hex characters id : '', // ULONG, 4 bytes, 8 hex characters server : '', // CHAR, variable length padding : '', // TCHAR[3], 4 bytes, 8 hex characters (upto 4 bytes) MIN_LENGTH : 64, name : 'EID_V0', // decompose the entryid and populate all flags of entryid decomposeEntryId : function(entryId) { var offset = 0; // First determine padding, and remove if from the entryId this.padding = this.getPadding(entryId); entryId = entryId.substring(0, entryId.length - this.padding.length); this.abFlags = entryId.substr(offset, 8); offset =+ 8; this.guid = entryId.substr(offset, 32); offset += 32; this.version = entryId.substr(offset, 8); offset += 8; this.type = entryId.substr(offset, 8); offset += 8; this.id = entryId.substr(offset, 8); offset += 8; this.server = entryId.substr(offset); } }); // wrapped store entryid var WrappedSEID = Ext.extend(BASE_EID, { flags : '', // BYTE[4], 4 bytes, 8 hex characters providerUID : '', // GUID, 16 bytes, 32 hex characters version : '', // ULONG, 1 bytes, 2 hex characters // zero type : '', // ULONG, 1 bytes, 2 hex characters // zero DLLFileName : '', // BYTE, variable length // kopano6client.dll terminationChar : '', // BYTE[1], 1 bytes, 2 hex characters // zero unWrappedEntryId : '', // EID/EID_V0, variable length because it contains server name name : 'WrappedSEID', // decompose the entryid and populate all flags of entryid decomposeEntryId : function(storeEntryId) { var offset = 0; this.flags = storeEntryId.substr(offset, 8); offset += 8; this.providerUID = storeEntryId.substr(offset, 32); offset += 32; this.version = storeEntryId.substr(offset, 2); offset += 2; this.type = storeEntryId.substr(offset, 2); offset += 2; // find length of dll name, find null character which indicates end of dll name after the current offset var termCharIndex = storeEntryId.slice(offset).indexOf('00'); this.DLLFileName = storeEntryId.substr(offset, termCharIndex); offset += termCharIndex; this.terminationChar = storeEntryId.substr(offset, 2); offset += 2; this.unWrappedEntryId = storeEntryId.substr(offset); // unwrapped entryid is actually an object entryid so decompose it this.unWrappedEntryId = Zarafa.core.EntryId.createEntryIdObj(this.unWrappedEntryId); } }); // The entryid for addressbook items var ABEID = Ext.extend(BASE_EID, { abFlags : '', // BYTE[4], 4 bytes, 8 hex characters guid : '', // GUID, 16 bytes, 32 hex characters version : '', // ULONG, 4 bytes, 8 hex characters type : '', // ULONG, 4 bytes, 8 hex characters id : '', // ULONG, 4 bytes, 8 hex characters extid : '', // CHAR, variable length padding : '', // TCHAR[3], 4 bytes, 8 hex characters (upto 4 bytes) MIN_LENGTH : 64, name : 'ABEID', // decompose the entryid and populate all flags of entryid decomposeEntryId : function(entryId) { var offset = 0; // First determine padding, and remove if from the entryId this.padding = this.getPadding(entryId); entryId = entryId.substring(0, entryId.length - this.padding.length); this.abFlags = entryId.substr(offset, 8); offset =+ 8; this.guid = entryId.substr(offset, 32); offset += 32; this.version = entryId.substr(offset, 8); offset += 8; this.type = entryId.substr(offset, 8); offset += 8; this.id = entryId.substr(offset, 8); offset += 8; this.extid = entryId.substr(offset); } }); // The entryid for local addressbook items var WrappedABEID = Ext.extend(BASE_EID, { ulVersion : '', // ULONG, 4 bytes, 8 hex characters muid : '', // MAPIUID, 16 bytes, 32 hex characters ulObjType : '', // ULONG, 4 bytes, 8 hex characters ulOffset : '', // ULONG, 4 bytes, 8 hex characters unWrappedEntryId : '', // EID/EID_V0, variable length because it contains server name name : 'WrappedABEID', // decompose the entryid and populate all flags of entryid decomposeEntryId : function(ABEntryId) { var offset = 0; this.ulVersion = ABEntryId.substr(offset, 8); offset += 8; this.muid = ABEntryId.substr(offset, 32); offset += 32; this.ulObjType = ABEntryId.substr(offset, 8); offset += 8; this.ulOffset = ABEntryId.substr(offset, 8); offset += 8; this.unWrappedEntryId = ABEntryId.substr(offset); // unwrapped entryid is actually an object entryid so decompose it this.unWrappedEntryId = Zarafa.core.EntryId.createEntryIdObj(this.unWrappedEntryId); } }); // Wrap an entryid into a Contact Provider entryid // @static WrappedABEID.wrapABEID = function(entryId, objType) { objType = objType.toString(16); // add padding for the type, which is of 4 bytes (8 characters) objType = objType.padStart(2, '0'); objType = objType.padEnd(8, '0'); return '00000000' + MUIDZCSAB + objType + '00000000' + entryId; }; // Unwrap an Contact Provider entryid // @static WrappedABEID.unwrapABEID = function(entryId) { // Remove ulVersion (8 char), muid (32 char), ulObjType (8 char) and ulOffset (8 char) return entryId.substring(56); }; return { /** * Creates an object that has split up all the components of an AB entryID. * @param {String} entryid Entryid * @return {Object} EntryID object */ createABEntryIdObj : function(entryid) { return new ABEID(entryid); }, /** * Compares two AB entryIds. It is possible to have two different entryIds that should match as they * represent the same object (in multiserver environments). * @param {String} entryId1 EntryID * @param {String} entryId2 EntryID * @return {Boolean} Result of the comparison */ compareABEntryIds : function(entryId1, entryId2) { if(!Ext.isString(entryId1) || !Ext.isString(entryId2)) { return false; } if(entryId1 === entryId2) { // if normal comparison succeeds then we can directly say that entryids are same return true; } var eid1 = Zarafa.core.EntryId.createABEntryIdObj(entryId1); var eid2 = Zarafa.core.EntryId.createABEntryIdObj(entryId2); if(eid1.length !== eid2.length) { return false; } if(eid1.abFlags !== eid2.abFlags) { return false; } if(eid1.version !== eid2.version) { return false; } if(eid1.type !== eid2.type) { return false; } if(eid1.length < eid1.MIN_LENGTH) { return false; } if(eid1.extid !== eid2.extid) { return false; } return true; }, /** * Creates an object that has split up all the components of an entryID. * @param {String} entryid Entryid * @return {Object} EntryID object */ createEntryIdObj : function(entryid) { // check if we are dealing with old or new object entryids var versionString = entryid.substr(40, 8); var eidObj; if(versionString === '00000000') { // use EID_V0 struct eidObj = new EID_V0(entryid); } else { // use EID struct eidObj = new EID(entryid); } return eidObj; }, /** * Compares two entryIds. It is possible to have two different entryIds that should match as they * represent the same object (in multiserver environments). * @param {String} entryId1 EntryID * @param {String} entryId2 EntryID * @return {Boolean} Result of the comparison */ compareEntryIds : function(entryId1, entryId2) { if(!Ext.isString(entryId1) || !Ext.isString(entryId2)) { return false; } if(entryId1 === entryId2) { // if normal comparison succeeds then we can directly say that entryids are same return true; } var eid1 = Zarafa.core.EntryId.createEntryIdObj(entryId1); var eid2 = Zarafa.core.EntryId.createEntryIdObj(entryId2); if(eid1.length !== eid2.length) { return false; } if(eid1.abFlags !== eid2.abFlags) { return false; } if(eid1.version !== eid2.version) { return false; } if(eid1.type !== eid2.type) { return false; } if(eid1.name === 'EID_V0') { if(eid1.length < eid1.MIN_LENGTH) { return false; } if(eid1.id !== eid2.id) { return false; } } else { if(eid1.length < eid1.MIN_LENGTH) { return false; } if(eid1.uniqueId !== eid2.uniqueId) { return false; } } return true; }, /** * Creates an object that has split up all the components of a store entryid. * @param {String} storeEntryId unwrapped store entryid. * @return {Object} store entryid object. */ createStoreEntryIdObj : function(storeEntryId) { return new WrappedSEID(storeEntryId); }, /** * Compares two entryIds. It is possible to have two different entryIds that should match as they * represent the same object (in multiserver environments). * @param {String} storeEntryId1 store entryid * @param {String} storeEntryId2 store entryid * @return {Boolean} Result of the comparison */ compareStoreEntryIds : function(storeEntryId1, storeEntryId2) { if(!Ext.isString(storeEntryId1) || !Ext.isString(storeEntryId2)) { return false; } if(storeEntryId1 === storeEntryId2) { // if normal comparison succeeds then we can directly say that entryids are same return true; } var seid1 = Zarafa.core.EntryId.createStoreEntryIdObj(storeEntryId1); var seid2 = Zarafa.core.EntryId.createStoreEntryIdObj(storeEntryId2); // we are only interested in unwrapped entryid part seid1 = seid1.unWrappedEntryId; seid2 = seid2.unWrappedEntryId; if(seid1 === seid2) { // if normal comparison succeeds then we can directly say that entryids are same return true; } if(seid1.length < seid1.MIN_LENGTH || seid2.length < seid2.MIN_LENGTH) { return false; } if(seid1.guid !== seid2.guid) { return false; } if(seid1.version !== seid2.version) { return false; } if(seid1.type !== seid2.type) { return false; } if(seid1.name === 'EID_V0') { if(seid1.length < seid1.MIN_LENGTH) { return false; } if(seid1.id !== seid2.id) { return false; } } else { if(seid1.length < seid1.MIN_LENGTH) { return false; } if(seid1.uniqueId !== seid2.uniqueId) { return false; } } return true; }, /** * Unwrap an Entryid which is of the Contact Provider ({@link #hasContactProviderGUID} * returned true for this entryid}. * @param {String} entryId the Address Book entryid. * @return {String} The unwrapped entryId */ unwrapContactProviderEntryId : function(entryId) { return WrappedABEID.unwrapABEID(entryId); }, /** * Wrap an EntryId which should be wrapped using the Contact Provider * @param {String} entryId The entryid * @return {String} The wrapped entryId */ wrapContactProviderEntryId : function(entryId, objType) { return WrappedABEID.wrapABEID(entryId, objType); }, /** * Create a one-off entryid from the applied parameters. * @param {String} displayname displaye name as configured in record. * @param {String} addrtype weather the record is of type SMTP. * @param {String} emailaddress email address as configured in record. * @return {String} The oneoff entryId */ createOneOffEntryId : function(displayname, addrtype, emailaddress) { return '00000000' + MAPI_ONE_OFF_UID + '00000080' + Zarafa.core.Util.encode_utf16(displayname) + '0000' + Zarafa.core.Util.encode_utf16(addrtype) + '0000' + Zarafa.core.Util.encode_utf16(emailaddress) + '0000'; }, /** * Checks if the passed folder entryid is a folder in the favorites folder, favorites folder * contains 0x01 in the abFlags[3] flag. * @param {String} entryId folder entryid * @return {Boolean} true of folder is a favorite folder else false */ isFavoriteFolder : function(entryId) { var entryIdObj = Zarafa.core.EntryId.createEntryIdObj(entryId); return (entryIdObj.abFlags.substr(6, 8) === ZARAFA_FAVORITE); }, /** * Checks if the given entryid is a oneoff entryid. * @param {String} entryId The entryid * @return {Boolean} true if the entryid is a oneoff */ isOneOffEntryId : function(entryId) { var entryIdObj = Zarafa.core.EntryId.createEntryIdObj(entryId); return entryIdObj.guid === MAPI_ONE_OFF_UID; }, /** * Checks if the passed folder entryid is root favorites folder. * @param {String} entryId folder entryid * @return {Boolean} true of folder is a root favorite folder else false */ isFavoriteRootFolder : function(entryId) { var entryIdObj = Zarafa.core.EntryId.createEntryIdObj(entryId); return entryIdObj.uniqueId === STATIC_GUID_FAVORITE; }, /** * Checks if the passed folder entryid is root public folder. * @param {String} entryId folder entryid * @return {Boolean} true of folder is a root public folder else false */ isPublicRootFolder : function(entryId) { var entryIdObj = Zarafa.core.EntryId.createEntryIdObj(entryId); return entryIdObj.uniqueId === STATIC_GUID_PUBLICFOLDER; }, /** * Checks if the passed folder entryid is public subtree folder. * @param {String} entryId folder entryid * @return {Boolean} true of folder is a root public folder else false */ isPublicSubtreeFolder : function(entryId) { var entryIdObj = Zarafa.core.EntryId.createEntryIdObj(entryId); return entryIdObj.uniqueId === STATIC_GUID_FAVSUBTREE; }, /** * Checks if the GUID part of the entryid is of the Contact Provider. * @param {String} entryId Address Book entryid * @return {Boolean} true if guid matches the Contact Provider else false */ hasContactProviderGUID : function(entryId) { var entryIdObj = Zarafa.core.EntryId.createEntryIdObj(entryId); return entryIdObj.guid === MUIDZCSAB; }, /** * Checks if the GUID part of the entryid is of the Global Addressbook. * @param {String} entryId Address Book entryid * @return {Boolean} true if guid matches the Global Addressbook else false */ hasAddressBookGUID : function(entryId) { var entryIdObj = Zarafa.core.EntryId.createABEntryIdObj(entryId); return entryIdObj.guid === MUIDECSAB; }, /** * Checks if the GUID part of the entryid is of the Global Addressbook Container. * @param {String} entryId Address Book entryid * @return {Boolean} true if guid matches the Global Addressbook Container else false */ isGlobalAddressbookContainer : function(entryId) { // check for global addressbook entryid if(Zarafa.core.EntryId.hasAddressBookGUID(entryId) === false) { return false; } var entryIdObj = Zarafa.core.EntryId.createABEntryIdObj(entryId); // check for object_type == MAPI_ABCONT and id == 1 return (entryIdObj.type === '04000000' && entryIdObj.id === ZARAFA_UID_GLOBAL_ADDRESS_BOOK); } }; })();