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);
}
};
})();