mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-09 19:35:51 +00:00
Merge fx-sync
This commit is contained in:
commit
4012ec2906
@ -45,6 +45,10 @@ Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
Components.utils.import("resource://gre/modules/ctypes.jsm");
|
||||
|
||||
const ALGORITHM = Ci.IWeaveCrypto.AES_256_CBC;
|
||||
const KEYSIZE_AES_256 = 32;
|
||||
const KEY_DERIVATION_ITERATIONS = 4096; // PKCS#5 recommends at least 1000.
|
||||
|
||||
function WeaveCrypto() {
|
||||
this.init();
|
||||
}
|
||||
@ -82,12 +86,38 @@ WeaveCrypto.prototype = {
|
||||
this.debug = this.prefBranch.getBoolPref("cryptoDebug");
|
||||
|
||||
this.initNSS();
|
||||
this.initAlgorithmSettings(); // Depends on NSS.
|
||||
this.initIVSECItem();
|
||||
} catch (e) {
|
||||
this.log("init failed: " + e);
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Set a bunch of NSS values once, at init-time. These are:
|
||||
* - .blockSize
|
||||
* - .mechanism
|
||||
* - .keygenMechanism
|
||||
* - .padMechanism
|
||||
* - .keySize
|
||||
*
|
||||
* See also the constant ALGORITHM.
|
||||
*/
|
||||
initAlgorithmSettings: function() {
|
||||
this.mechanism = this.nss.PK11_AlgtagToMechanism(ALGORITHM);
|
||||
this.blockSize = this.nss.PK11_GetBlockSize(this.mechanism, null);
|
||||
this.ivLength = this.nss.PK11_GetIVLength(this.mechanism);
|
||||
this.keySize = KEYSIZE_AES_256;
|
||||
this.keygenMechanism = this.nss.CKM_AES_KEY_GEN; // Always the same!
|
||||
|
||||
// Determine which (padded) PKCS#11 mechanism to use.
|
||||
// E.g., AES_256_CBC --> CKM_AES_CBC --> CKM_AES_CBC_PAD
|
||||
this.padMechanism = this.nss.PK11_GetPadMechanism(this.mechanism);
|
||||
if (this.padMechanism == this.nss.CKM_INVALID_MECHANISM)
|
||||
throw Components.Exception("invalid algorithm (can't pad)", Cr.NS_ERROR_FAILURE);
|
||||
},
|
||||
|
||||
log : function (message) {
|
||||
if (!this.debug)
|
||||
return;
|
||||
@ -171,52 +201,11 @@ WeaveCrypto.prototype = {
|
||||
"SECItem", [{ type: this.nss_t.SECItemType },
|
||||
{ data: ctypes.unsigned_char.ptr },
|
||||
{ len : ctypes.int }]);
|
||||
// security/nss/lib/softoken/secmodt.h#65
|
||||
// typedef struct PK11RSAGenParamsStr --> def'n on line 139
|
||||
this.nss_t.PK11RSAGenParams = ctypes.StructType(
|
||||
"PK11RSAGenParams", [{ keySizeInBits: ctypes.int },
|
||||
{ pe : ctypes.unsigned_long }]);
|
||||
// security/nss/lib/cryptohi/keythi.h#233
|
||||
// typedef struct SECKEYPrivateKeyStr SECKEYPrivateKey; --> def'n right above it
|
||||
this.nss_t.SECKEYPrivateKey = ctypes.StructType(
|
||||
"SECKEYPrivateKey", [{ arena: this.nss_t.PLArenaPool.ptr },
|
||||
{ keyType: this.nss_t.KeyType },
|
||||
{ pkcs11Slot: this.nss_t.PK11SlotInfo.ptr },
|
||||
{ pkcs11ID: this.nss_t.CK_OBJECT_HANDLE },
|
||||
{ pkcs11IsTemp: this.nss_t.PRBool },
|
||||
{ wincx: ctypes.voidptr_t },
|
||||
{ staticflags: ctypes.unsigned_int }]);
|
||||
// security/nss/lib/cryptohi/keythi.h#78
|
||||
// typedef struct SECKEYRSAPublicKeyStr --> def'n right above it
|
||||
this.nss_t.SECKEYRSAPublicKey = ctypes.StructType(
|
||||
"SECKEYRSAPublicKey", [{ arena: this.nss_t.PLArenaPool.ptr },
|
||||
{ modulus: this.nss_t.SECItem },
|
||||
{ publicExponent: this.nss_t.SECItem }]);
|
||||
// security/nss/lib/cryptohi/keythi.h#189
|
||||
// typedef struct SECKEYPublicKeyStr SECKEYPublicKey; --> def'n right above it
|
||||
this.nss_t.SECKEYPublicKey = ctypes.StructType(
|
||||
"SECKEYPublicKey", [{ arena: this.nss_t.PLArenaPool.ptr },
|
||||
{ keyType: this.nss_t.KeyType },
|
||||
{ pkcs11Slot: this.nss_t.PK11SlotInfo.ptr },
|
||||
{ pkcs11ID: this.nss_t.CK_OBJECT_HANDLE },
|
||||
{ rsa: this.nss_t.SECKEYRSAPublicKey } ]);
|
||||
// XXX: "rsa" et al into a union here!
|
||||
// { dsa: SECKEYDSAPublicKey },
|
||||
// { dh: SECKEYDHPublicKey },
|
||||
// { kea: SECKEYKEAPublicKey },
|
||||
// { fortezza: SECKEYFortezzaPublicKey },
|
||||
// { ec: SECKEYECPublicKey } ]);
|
||||
// security/nss/lib/util/secoidt.h#52
|
||||
// typedef struct SECAlgorithmIDStr --> def'n right below it
|
||||
this.nss_t.SECAlgorithmID = ctypes.StructType(
|
||||
"SECAlgorithmID", [{ algorithm: this.nss_t.SECItem },
|
||||
{ parameters: this.nss_t.SECItem }]);
|
||||
// security/nss/lib/certdb/certt.h#98
|
||||
// typedef struct CERTSubjectPublicKeyInfoStrA --> def'n on line 160
|
||||
this.nss_t.CERTSubjectPublicKeyInfo = ctypes.StructType(
|
||||
"CERTSubjectPublicKeyInfo", [{ arena: this.nss_t.PLArenaPool.ptr },
|
||||
{ algorithm: this.nss_t.SECAlgorithmID },
|
||||
{ subjectPublicKey: this.nss_t.SECItem }]);
|
||||
|
||||
|
||||
// security/nss/lib/util/pkcs11t.h
|
||||
@ -225,7 +214,6 @@ WeaveCrypto.prototype = {
|
||||
this.nss.CKM_AES_KEY_GEN = 0x1080;
|
||||
this.nss.CKA_ENCRYPT = 0x104;
|
||||
this.nss.CKA_DECRYPT = 0x105;
|
||||
this.nss.CKA_UNWRAP = 0x107;
|
||||
|
||||
// security/nss/lib/softoken/secmodt.h
|
||||
this.nss.PK11_ATTR_SESSION = 0x02;
|
||||
@ -320,19 +308,6 @@ WeaveCrypto.prototype = {
|
||||
ctypes.default_abi, this.nss_t.SECStatus,
|
||||
this.nss_t.PK11Context.ptr, ctypes.unsigned_char.ptr,
|
||||
ctypes.unsigned_int.ptr, ctypes.unsigned_int);
|
||||
// security/nss/lib/pk11wrap/pk11pub.h#507
|
||||
// SECKEYPrivateKey *PK11_GenerateKeyPairWithFlags(PK11SlotInfo *slot,
|
||||
// CK_MECHANISM_TYPE type, void *param, SECKEYPublicKey **pubk,
|
||||
// PK11AttrFlags attrFlags, void *wincx);
|
||||
this.nss.PK11_GenerateKeyPairWithFlags = nsslib.declare("PK11_GenerateKeyPairWithFlags",
|
||||
ctypes.default_abi, this.nss_t.SECKEYPrivateKey.ptr,
|
||||
this.nss_t.PK11SlotInfo.ptr, this.nss_t.CK_MECHANISM_TYPE, ctypes.voidptr_t,
|
||||
this.nss_t.SECKEYPublicKey.ptr.ptr, this.nss_t.PK11AttrFlags, ctypes.voidptr_t);
|
||||
// security/nss/lib/pk11wrap/pk11pub.h#466
|
||||
// SECStatus PK11_SetPrivateKeyNickname(SECKEYPrivateKey *privKey, const char *nickname);
|
||||
this.nss.PK11_SetPrivateKeyNickname = nsslib.declare("PK11_SetPrivateKeyNickname",
|
||||
ctypes.default_abi, this.nss_t.SECStatus,
|
||||
this.nss_t.SECKEYPrivateKey.ptr, ctypes.char.ptr);
|
||||
// security/nss/lib/pk11wrap/pk11pub.h#731
|
||||
// SECAlgorithmID * PK11_CreatePBEV2AlgorithmID(SECOidTag pbeAlgTag, SECOidTag cipherAlgTag,
|
||||
// SECOidTag prfAlgTag, int keyLength, int iteration,
|
||||
@ -347,60 +322,6 @@ WeaveCrypto.prototype = {
|
||||
ctypes.default_abi, this.nss_t.PK11SymKey.ptr,
|
||||
this.nss_t.PK11SlotInfo.ptr, this.nss_t.SECAlgorithmID.ptr,
|
||||
this.nss_t.SECItem.ptr, this.nss_t.PRBool, ctypes.voidptr_t);
|
||||
// security/nss/lib/pk11wrap/pk11pub.h#574
|
||||
// SECStatus PK11_WrapPrivKey(PK11SlotInfo *slot, PK11SymKey *wrappingKey,
|
||||
// SECKEYPrivateKey *privKey, CK_MECHANISM_TYPE wrapType,
|
||||
// SECItem *param, SECItem *wrappedKey, void *wincx);
|
||||
this.nss.PK11_WrapPrivKey = nsslib.declare("PK11_WrapPrivKey",
|
||||
ctypes.default_abi, this.nss_t.SECStatus,
|
||||
this.nss_t.PK11SlotInfo.ptr, this.nss_t.PK11SymKey.ptr,
|
||||
this.nss_t.SECKEYPrivateKey.ptr, this.nss_t.CK_MECHANISM_TYPE,
|
||||
this.nss_t.SECItem.ptr, this.nss_t.SECItem.ptr, ctypes.voidptr_t);
|
||||
// security/nss/lib/cryptohi/keyhi.h#159
|
||||
// SECItem* SECKEY_EncodeDERSubjectPublicKeyInfo(SECKEYPublicKey *pubk);
|
||||
this.nss.SECKEY_EncodeDERSubjectPublicKeyInfo = nsslib.declare("SECKEY_EncodeDERSubjectPublicKeyInfo",
|
||||
ctypes.default_abi, this.nss_t.SECItem.ptr,
|
||||
this.nss_t.SECKEYPublicKey.ptr);
|
||||
// security/nss/lib/cryptohi/keyhi.h#165
|
||||
// CERTSubjectPublicKeyInfo * SECKEY_DecodeDERSubjectPublicKeyInfo(SECItem *spkider);
|
||||
this.nss.SECKEY_DecodeDERSubjectPublicKeyInfo = nsslib.declare("SECKEY_DecodeDERSubjectPublicKeyInfo",
|
||||
ctypes.default_abi, this.nss_t.CERTSubjectPublicKeyInfo.ptr,
|
||||
this.nss_t.SECItem.ptr);
|
||||
// security/nss/lib/cryptohi/keyhi.h#179
|
||||
// SECKEYPublicKey * SECKEY_ExtractPublicKey(CERTSubjectPublicKeyInfo *);
|
||||
this.nss.SECKEY_ExtractPublicKey = nsslib.declare("SECKEY_ExtractPublicKey",
|
||||
ctypes.default_abi, this.nss_t.SECKEYPublicKey.ptr,
|
||||
this.nss_t.CERTSubjectPublicKeyInfo.ptr);
|
||||
// security/nss/lib/pk11wrap/pk11pub.h#377
|
||||
// SECStatus PK11_PubWrapSymKey(CK_MECHANISM_TYPE type, SECKEYPublicKey *pubKey,
|
||||
// PK11SymKey *symKey, SECItem *wrappedKey);
|
||||
this.nss.PK11_PubWrapSymKey = nsslib.declare("PK11_PubWrapSymKey",
|
||||
ctypes.default_abi, this.nss_t.SECStatus,
|
||||
this.nss_t.CK_MECHANISM_TYPE, this.nss_t.SECKEYPublicKey.ptr,
|
||||
this.nss_t.PK11SymKey.ptr, this.nss_t.SECItem.ptr);
|
||||
// security/nss/lib/pk11wrap/pk11pub.h#568
|
||||
// SECKEYPrivateKey *PK11_UnwrapPrivKey(PK11SlotInfo *slot,
|
||||
// PK11SymKey *wrappingKey, CK_MECHANISM_TYPE wrapType,
|
||||
// SECItem *param, SECItem *wrappedKey, SECItem *label,
|
||||
// SECItem *publicValue, PRBool token, PRBool sensitive,
|
||||
// CK_KEY_TYPE keyType, CK_ATTRIBUTE_TYPE *usage, int usageCount,
|
||||
// void *wincx);
|
||||
this.nss.PK11_UnwrapPrivKey = nsslib.declare("PK11_UnwrapPrivKey",
|
||||
ctypes.default_abi, this.nss_t.SECKEYPrivateKey.ptr,
|
||||
this.nss_t.PK11SlotInfo.ptr, this.nss_t.PK11SymKey.ptr,
|
||||
this.nss_t.CK_MECHANISM_TYPE, this.nss_t.SECItem.ptr,
|
||||
this.nss_t.SECItem.ptr, this.nss_t.SECItem.ptr,
|
||||
this.nss_t.SECItem.ptr, this.nss_t.PRBool,
|
||||
this.nss_t.PRBool, this.nss_t.CK_KEY_TYPE,
|
||||
this.nss_t.CK_ATTRIBUTE_TYPE.ptr, ctypes.int,
|
||||
ctypes.voidptr_t);
|
||||
// security/nss/lib/pk11wrap/pk11pub.h#447
|
||||
// PK11SymKey *PK11_PubUnwrapSymKey(SECKEYPrivateKey *key, SECItem *wrapppedKey,
|
||||
// CK_MECHANISM_TYPE target, CK_ATTRIBUTE_TYPE operation, int keySize);
|
||||
this.nss.PK11_PubUnwrapSymKey = nsslib.declare("PK11_PubUnwrapSymKey",
|
||||
ctypes.default_abi, this.nss_t.PK11SymKey.ptr,
|
||||
this.nss_t.SECKEYPrivateKey.ptr, this.nss_t.SECItem.ptr,
|
||||
this.nss_t.CK_MECHANISM_TYPE, this.nss_t.CK_ATTRIBUTE_TYPE, ctypes.int);
|
||||
// security/nss/lib/pk11wrap/pk11pub.h#675
|
||||
// void PK11_DestroyContext(PK11Context *context, PRBool freeit);
|
||||
this.nss.PK11_DestroyContext = nsslib.declare("PK11_DestroyContext",
|
||||
@ -432,26 +353,11 @@ WeaveCrypto.prototype = {
|
||||
this.nss.SECITEM_FreeItem = nsslib.declare("SECITEM_FreeItem",
|
||||
ctypes.default_abi, ctypes.void_t,
|
||||
this.nss_t.SECItem.ptr, this.nss_t.PRBool);
|
||||
// security/nss/lib/cryptohi/keyhi.h#193
|
||||
// extern void SECKEY_DestroyPublicKey(SECKEYPublicKey *key);
|
||||
this.nss.SECKEY_DestroyPublicKey = nsslib.declare("SECKEY_DestroyPublicKey",
|
||||
ctypes.default_abi, ctypes.void_t,
|
||||
this.nss_t.SECKEYPublicKey.ptr);
|
||||
// security/nss/lib/cryptohi/keyhi.h#186
|
||||
// extern void SECKEY_DestroyPrivateKey(SECKEYPrivateKey *key);
|
||||
this.nss.SECKEY_DestroyPrivateKey = nsslib.declare("SECKEY_DestroyPrivateKey",
|
||||
ctypes.default_abi, ctypes.void_t,
|
||||
this.nss_t.SECKEYPrivateKey.ptr);
|
||||
// security/nss/lib/util/secoid.h#103
|
||||
// extern void SECOID_DestroyAlgorithmID(SECAlgorithmID *aid, PRBool freeit);
|
||||
this.nss.SECOID_DestroyAlgorithmID = nsslib.declare("SECOID_DestroyAlgorithmID",
|
||||
ctypes.default_abi, ctypes.void_t,
|
||||
this.nss_t.SECAlgorithmID.ptr, this.nss_t.PRBool);
|
||||
// security/nss/lib/cryptohi/keyhi.h#58
|
||||
// extern void SECKEY_DestroySubjectPublicKeyInfo(CERTSubjectPublicKeyInfo *spki);
|
||||
this.nss.SECKEY_DestroySubjectPublicKeyInfo = nsslib.declare("SECKEY_DestroySubjectPublicKeyInfo",
|
||||
ctypes.default_abi, ctypes.void_t,
|
||||
this.nss_t.CERTSubjectPublicKeyInfo.ptr);
|
||||
},
|
||||
|
||||
|
||||
@ -459,9 +365,6 @@ WeaveCrypto.prototype = {
|
||||
// IWeaveCrypto interfaces
|
||||
//
|
||||
|
||||
|
||||
algorithm : Ci.IWeaveCrypto.AES_256_CBC,
|
||||
|
||||
encrypt : function(clearTextUCS2, symmetricKey, iv) {
|
||||
this.log("encrypt() called");
|
||||
|
||||
@ -473,9 +376,7 @@ WeaveCrypto.prototype = {
|
||||
// When using CBC padding, the output size is the input size rounded
|
||||
// up to the nearest block. If the input size is exactly on a block
|
||||
// boundary, the output is 1 extra block long.
|
||||
let mech = this.nss.PK11_AlgtagToMechanism(this.algorithm);
|
||||
let blockSize = this.nss.PK11_GetBlockSize(mech, null);
|
||||
let outputBufferSize = inputBuffer.length + blockSize;
|
||||
let outputBufferSize = inputBuffer.length + this.blockSize;
|
||||
let outputBuffer = new ctypes.ArrayType(ctypes.unsigned_char, outputBufferSize)();
|
||||
|
||||
outputBuffer = this._commonCrypt(inputBuffer, outputBuffer, symmetricKey, iv, this.nss.CKA_ENCRYPT);
|
||||
@ -494,8 +395,12 @@ WeaveCrypto.prototype = {
|
||||
// We can't have js-ctypes create the buffer directly from the string
|
||||
// (as in encrypt()), because we do _not_ want it to do UTF8
|
||||
// conversion... We've got random binary data in the input's low byte.
|
||||
let input = new ctypes.ArrayType(ctypes.unsigned_char, inputUCS2.length)();
|
||||
this.byteCompress(inputUCS2, input);
|
||||
//
|
||||
// Compress a JS string (2-byte chars) into a normal C string (1-byte chars).
|
||||
let len = inputUCS2.length;
|
||||
let input = new ctypes.ArrayType(ctypes.unsigned_char, len)();
|
||||
let ints = ctypes.cast(input, ctypes.uint8_t.array(len));
|
||||
this.byteCompressInts(inputUCS2, ints, len);
|
||||
|
||||
let outputBuffer = new ctypes.ArrayType(ctypes.unsigned_char, input.length)();
|
||||
|
||||
@ -510,32 +415,24 @@ WeaveCrypto.prototype = {
|
||||
|
||||
_commonCrypt : function (input, output, symmetricKey, iv, operation) {
|
||||
this.log("_commonCrypt() called");
|
||||
// Get rid of the base64 encoding and convert to SECItems.
|
||||
let keyItem = this.makeSECItem(symmetricKey, true);
|
||||
let ivItem = this.makeSECItem(iv, true);
|
||||
iv = atob(iv);
|
||||
|
||||
// Determine which (padded) PKCS#11 mechanism to use.
|
||||
// EG: AES_128_CBC --> CKM_AES_CBC --> CKM_AES_CBC_PAD
|
||||
let mechanism = this.nss.PK11_AlgtagToMechanism(this.algorithm);
|
||||
mechanism = this.nss.PK11_GetPadMechanism(mechanism);
|
||||
if (mechanism == this.nss.CKM_INVALID_MECHANISM)
|
||||
throw Components.Exception("invalid algorithm (can't pad)", Cr.NS_ERROR_FAILURE);
|
||||
// We never want an IV longer than the block size, which is 16 bytes
|
||||
// for AES.
|
||||
if (iv.length > this.blockSize)
|
||||
iv = iv.slice(0, this.blockSize);
|
||||
|
||||
let ctx, symKey, slot, ivParam;
|
||||
// We use a single IV SECItem for the sake of efficiency. Fill it here.
|
||||
this.byteCompressInts(iv, this._ivSECItemContents, iv.length);
|
||||
|
||||
let ctx, symKey, ivParam;
|
||||
try {
|
||||
ivParam = this.nss.PK11_ParamFromIV(mechanism, ivItem);
|
||||
ivParam = this.nss.PK11_ParamFromIV(this.padMechanism, this._ivSECItem);
|
||||
if (ivParam.isNull())
|
||||
throw Components.Exception("can't convert IV to param", Cr.NS_ERROR_FAILURE);
|
||||
|
||||
slot = this.nss.PK11_GetInternalKeySlot();
|
||||
if (slot.isNull())
|
||||
throw Components.Exception("can't get internal key slot", Cr.NS_ERROR_FAILURE);
|
||||
|
||||
symKey = this.nss.PK11_ImportSymKey(slot, mechanism, this.nss.PK11_OriginUnwrap, operation, keyItem, null);
|
||||
if (symKey.isNull())
|
||||
throw Components.Exception("symkey import failed", Cr.NS_ERROR_FAILURE);
|
||||
|
||||
ctx = this.nss.PK11_CreateContextBySymKey(mechanism, operation, symKey, ivParam);
|
||||
symKey = this.importSymKey(symmetricKey, operation);
|
||||
ctx = this.nss.PK11_CreateContextBySymKey(this.padMechanism, operation, symKey, ivParam);
|
||||
if (ctx.isNull())
|
||||
throw Components.Exception("couldn't create context for symkey", Cr.NS_ERROR_FAILURE);
|
||||
|
||||
@ -565,50 +462,24 @@ WeaveCrypto.prototype = {
|
||||
} finally {
|
||||
if (ctx && !ctx.isNull())
|
||||
this.nss.PK11_DestroyContext(ctx, true);
|
||||
if (symKey && !symKey.isNull())
|
||||
this.nss.PK11_FreeSymKey(symKey);
|
||||
if (slot && !slot.isNull())
|
||||
this.nss.PK11_FreeSlot(slot);
|
||||
if (ivParam && !ivParam.isNull())
|
||||
this.nss.SECITEM_FreeItem(ivParam, true);
|
||||
this.freeSECItem(keyItem);
|
||||
this.freeSECItem(ivItem);
|
||||
|
||||
// Note that we do not free the IV SECItem; we reuse it.
|
||||
// Neither do we free the symKey, because that's memoized.
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
generateRandomKey : function() {
|
||||
this.log("generateRandomKey() called");
|
||||
let encodedKey, keygenMech, keySize;
|
||||
|
||||
// Doesn't NSS have a lookup function to do this?
|
||||
switch(this.algorithm) {
|
||||
case Ci.IWeaveCrypto.AES_128_CBC:
|
||||
keygenMech = this.nss.CKM_AES_KEY_GEN;
|
||||
keySize = 16;
|
||||
break;
|
||||
|
||||
case Ci.IWeaveCrypto.AES_192_CBC:
|
||||
keygenMech = this.nss.CKM_AES_KEY_GEN;
|
||||
keySize = 24;
|
||||
break;
|
||||
|
||||
case Ci.IWeaveCrypto.AES_256_CBC:
|
||||
keygenMech = this.nss.CKM_AES_KEY_GEN;
|
||||
keySize = 32;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw Components.Exception("unknown algorithm", Cr.NS_ERROR_FAILURE);
|
||||
}
|
||||
|
||||
let slot, randKey, keydata;
|
||||
try {
|
||||
slot = this.nss.PK11_GetInternalSlot();
|
||||
if (slot.isNull())
|
||||
throw Components.Exception("couldn't get internal slot", Cr.NS_ERROR_FAILURE);
|
||||
|
||||
randKey = this.nss.PK11_KeyGen(slot, keygenMech, null, keySize, null);
|
||||
randKey = this.nss.PK11_KeyGen(slot, this.keygenMechanism, null, this.keySize, null);
|
||||
if (randKey.isNull())
|
||||
throw Components.Exception("PK11_KeyGen failed.", Cr.NS_ERROR_FAILURE);
|
||||
|
||||
@ -633,16 +504,7 @@ WeaveCrypto.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
generateRandomIV : function() {
|
||||
this.log("generateRandomIV() called");
|
||||
|
||||
let mech = this.nss.PK11_AlgtagToMechanism(this.algorithm);
|
||||
let size = this.nss.PK11_GetIVLength(mech);
|
||||
|
||||
return this.generateRandomBytes(size);
|
||||
},
|
||||
|
||||
generateRandomIV : function() this.generateRandomBytes(this.ivLength),
|
||||
|
||||
generateRandomBytes : function(byteCount) {
|
||||
this.log("generateRandomBytes() called");
|
||||
@ -655,18 +517,78 @@ WeaveCrypto.prototype = {
|
||||
return this.encodeBase64(scratch.address(), scratch.length);
|
||||
},
|
||||
|
||||
//
|
||||
// PK11SymKey memoization.
|
||||
//
|
||||
|
||||
// Memoize the lookup of symmetric keys. We do this by using the base64
|
||||
// string itself as a key -- the overhead of SECItem creation during the
|
||||
// initial population is negligible, so that phase is not memoized.
|
||||
_encryptionSymKeyMemo: {},
|
||||
_decryptionSymKeyMemo: {},
|
||||
importSymKey: function importSymKey(encodedKeyString, operation) {
|
||||
let memo;
|
||||
|
||||
// We use two separate memos for thoroughness: operation is an input to
|
||||
// key import.
|
||||
switch (operation) {
|
||||
case this.nss.CKA_ENCRYPT:
|
||||
memo = this._encryptionSymKeyMemo;
|
||||
break;
|
||||
case this.nss.CKA_DECRYPT:
|
||||
memo = this._decryptionSymKeyMemo;
|
||||
break;
|
||||
default:
|
||||
throw "Unsupported operation in importSymKey.";
|
||||
}
|
||||
|
||||
if (encodedKeyString in memo)
|
||||
return memo[encodedKeyString];
|
||||
|
||||
let keyItem, slot;
|
||||
try {
|
||||
keyItem = this.makeSECItem(encodedKeyString, true);
|
||||
slot = this.nss.PK11_GetInternalKeySlot();
|
||||
if (slot.isNull())
|
||||
throw Components.Exception("can't get internal key slot",
|
||||
Cr.NS_ERROR_FAILURE);
|
||||
|
||||
let symKey = this.nss.PK11_ImportSymKey(slot, this.padMechanism,
|
||||
this.nss.PK11_OriginUnwrap,
|
||||
operation, keyItem, null);
|
||||
if (!symKey || symKey.isNull())
|
||||
throw Components.Exception("symkey import failed",
|
||||
Cr.NS_ERROR_FAILURE);
|
||||
|
||||
return memo[encodedKeyString] = symKey;
|
||||
} finally {
|
||||
if (slot && !slot.isNull())
|
||||
this.nss.PK11_FreeSlot(slot);
|
||||
this.freeSECItem(keyItem);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
//
|
||||
// Utility functions
|
||||
//
|
||||
|
||||
/**
|
||||
* Compress a JS string into a C uint8 array. count is the number of
|
||||
* elements in the destination array. If the array is smaller than the
|
||||
* string, the string is effectively truncated. If the string is smaller
|
||||
* than the array, the array is 0-padded.
|
||||
*/
|
||||
byteCompressInts : function byteCompressInts (jsString, intArray, count) {
|
||||
let len = jsString.length;
|
||||
let end = Math.min(len, count);
|
||||
|
||||
// Compress a JS string (2-byte chars) into a normal C string (1-byte chars)
|
||||
// EG, for "ABC", 0x0041, 0x0042, 0x0043 --> 0x41, 0x42, 0x43
|
||||
byteCompress : function (jsString, charArray) {
|
||||
let intArray = ctypes.cast(charArray, ctypes.uint8_t.array(charArray.length));
|
||||
for (let i = 0; i < jsString.length; i++)
|
||||
for (let i = 0; i < end; i++)
|
||||
intArray[i] = jsString.charCodeAt(i) % 256; // convert to bytes
|
||||
|
||||
// Must zero-pad.
|
||||
for (let i = len; i < count; i++)
|
||||
intArray[i] = 0;
|
||||
},
|
||||
|
||||
// Expand a normal C string (1-byte chars) into a JS string (2-byte chars)
|
||||
@ -695,28 +617,54 @@ WeaveCrypto.prototype = {
|
||||
},
|
||||
|
||||
// Returns a filled SECItem *, as returned by SECITEM_AllocItem.
|
||||
//
|
||||
//
|
||||
// Note that this must be released with freeSECItem, which will also
|
||||
// deallocate the internal buffer.
|
||||
makeSECItem : function(input, isEncoded) {
|
||||
if (isEncoded)
|
||||
input = atob(input);
|
||||
|
||||
|
||||
let len = input.length;
|
||||
let item = this.nss.SECITEM_AllocItem(null, null, len);
|
||||
if (item.isNull())
|
||||
throw "SECITEM_AllocItem failed.";
|
||||
|
||||
let dest = ctypes.cast(item.contents.data, ctypes.unsigned_char.array(len).ptr);
|
||||
this.byteCompress(input, dest.contents);
|
||||
throw "SECITEM_AllocItem failed.";
|
||||
|
||||
let ptr = ctypes.cast(item.contents.data,
|
||||
ctypes.unsigned_char.array(len).ptr);
|
||||
let dest = ctypes.cast(ptr.contents, ctypes.uint8_t.array(len));
|
||||
this.byteCompressInts(input, dest, len);
|
||||
return item;
|
||||
},
|
||||
|
||||
|
||||
freeSECItem : function(zap) {
|
||||
if (zap && !zap.isNull())
|
||||
this.nss.SECITEM_ZfreeItem(zap, true);
|
||||
},
|
||||
|
||||
// We only ever handle one IV at a time, and they're always different.
|
||||
// Consequently, we maintain a single SECItem, and a handy pointer into its
|
||||
// contents to avoid repetitive and expensive casts.
|
||||
_ivSECItem: null,
|
||||
_ivSECItemContents: null,
|
||||
|
||||
initIVSECItem: function initIVSECItem() {
|
||||
if (this._ivSECItem) {
|
||||
this._ivSECItemContents = null;
|
||||
this.freeSECItem(this._ivSECItem);
|
||||
}
|
||||
|
||||
let item = this.nss.SECITEM_AllocItem(null, null, this.blockSize);
|
||||
if (item.isNull())
|
||||
throw "SECITEM_AllocItem failed.";
|
||||
|
||||
let ptr = ctypes.cast(item.contents.data,
|
||||
ctypes.unsigned_char.array(this.blockSize).ptr);
|
||||
let contents = ctypes.cast(ptr.contents,
|
||||
ctypes.uint8_t.array(this.blockSize));
|
||||
this._ivSECItem = item;
|
||||
this._ivSECItemContents = contents;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the expanded data string for the derived key.
|
||||
*/
|
||||
@ -725,17 +673,19 @@ WeaveCrypto.prototype = {
|
||||
let passItem = this.makeSECItem(passphrase, false);
|
||||
let saltItem = this.makeSECItem(salt, true);
|
||||
|
||||
let pbeAlg = this.algorithm;
|
||||
let cipherAlg = this.algorithm; // ignored by callee when pbeAlg != a pkcs5 mech.
|
||||
let prfAlg = this.nss.SEC_OID_HMAC_SHA1; // callee picks if SEC_OID_UNKNOWN, but only SHA1 is supported
|
||||
let pbeAlg = ALGORITHM;
|
||||
let cipherAlg = ALGORITHM; // Ignored by callee when pbeAlg != a pkcs5 mech.
|
||||
|
||||
// Callee picks if SEC_OID_UNKNOWN, but only SHA1 is supported.
|
||||
let prfAlg = this.nss.SEC_OID_HMAC_SHA1;
|
||||
|
||||
let keyLength = keyLength || 0; // 0 = Callee will pick.
|
||||
let iterations = 4096; // PKCS#5 recommends at least 1000.
|
||||
let iterations = KEY_DERIVATION_ITERATIONS;
|
||||
|
||||
let algid, slot, symKey, keyData;
|
||||
try {
|
||||
algid = this.nss.PK11_CreatePBEV2AlgorithmID(pbeAlg, cipherAlg, prfAlg,
|
||||
keyLength, iterations,
|
||||
keyLength, iterations,
|
||||
saltItem);
|
||||
if (algid.isNull())
|
||||
throw Components.Exception("PK11_CreatePBEV2AlgorithmID failed", Cr.NS_ERROR_FAILURE);
|
||||
@ -772,7 +722,7 @@ WeaveCrypto.prototype = {
|
||||
this.nss.PK11_FreeSlot(slot);
|
||||
if (symKey && !symKey.isNull())
|
||||
this.nss.PK11_FreeSymKey(symKey);
|
||||
|
||||
|
||||
this.freeSECItem(passItem);
|
||||
this.freeSECItem(saltItem);
|
||||
}
|
||||
|
@ -19,15 +19,49 @@ function run_test() {
|
||||
}
|
||||
test_bug_617650();
|
||||
test_encrypt_decrypt();
|
||||
test_SECItem_byteCompressInts();
|
||||
test_key_memoization();
|
||||
if (this.gczeal)
|
||||
gczeal(0);
|
||||
}
|
||||
|
||||
function test_key_memoization() {
|
||||
let oldImport = cryptoSvc.nss && cryptoSvc.nss.PK11_ImportSymKey;
|
||||
if (!oldImport) {
|
||||
_("Couldn't swizzle PK11_ImportSymKey; returning.");
|
||||
return;
|
||||
}
|
||||
|
||||
let iv = cryptoSvc.generateRandomIV();
|
||||
let key = cryptoSvc.generateRandomKey();
|
||||
let c = 0;
|
||||
cryptoSvc.nss.PK11_ImportSymKey = function(slot, type, origin, operation, key, wincx) {
|
||||
c++;
|
||||
return oldImport(slot, type, origin, operation, key, wincx);
|
||||
}
|
||||
|
||||
// Encryption should cause a single counter increment.
|
||||
do_check_eq(c, 0);
|
||||
let cipherText = cryptoSvc.encrypt("Hello, world.", key, iv);
|
||||
do_check_eq(c, 1);
|
||||
let cipherText = cryptoSvc.encrypt("Hello, world.", key, iv);
|
||||
do_check_eq(c, 1);
|
||||
|
||||
// ... as should decryption.
|
||||
cryptoSvc.decrypt(cipherText, key, iv);
|
||||
cryptoSvc.decrypt(cipherText, key, iv);
|
||||
cryptoSvc.decrypt(cipherText, key, iv);
|
||||
do_check_eq(c, 2);
|
||||
|
||||
// Un-swizzle.
|
||||
cryptoSvc.nss.PK11_ImportSymKey = oldImport;
|
||||
}
|
||||
|
||||
function multiple_decrypts(iterations) {
|
||||
let iv = cryptoSvc.generateRandomIV();
|
||||
let key = cryptoSvc.generateRandomKey();
|
||||
let cipherText = cryptoSvc.encrypt("Hello, world.", key, iv);
|
||||
|
||||
|
||||
for (let i = 0; i < iterations; ++i) {
|
||||
let clearText = cryptoSvc.decrypt(cipherText, key, iv);
|
||||
do_check_eq(clearText + " " + i, "Hello, world. " + i);
|
||||
@ -52,7 +86,7 @@ function test_bug_617650() {
|
||||
// Just verify that it gets populated with the correct bytes.
|
||||
function test_makeSECItem() {
|
||||
Components.utils.import("resource://gre/modules/ctypes.jsm");
|
||||
|
||||
|
||||
let item1 = cryptoSvc.makeSECItem("abcdefghi", false);
|
||||
do_check_true(!item1.isNull());
|
||||
let intData = ctypes.cast(item1.contents.data, ctypes.uint8_t.array(8).ptr).contents;
|
||||
@ -60,6 +94,24 @@ function test_makeSECItem() {
|
||||
do_check_eq(intData[i], "abcdefghi".charCodeAt(i));
|
||||
}
|
||||
|
||||
function test_SECItem_byteCompressInts() {
|
||||
Components.utils.import("resource://gre/modules/ctypes.jsm");
|
||||
|
||||
let item1 = cryptoSvc.makeSECItem("abcdefghi", false);
|
||||
do_check_true(!item1.isNull());
|
||||
let intData = ctypes.cast(item1.contents.data, ctypes.uint8_t.array(8).ptr).contents;
|
||||
|
||||
// Fill it too short.
|
||||
cryptoSvc.byteCompressInts("MMM", intData, 8);
|
||||
for (let i = 0; i < 8; ++i)
|
||||
do_check_eq(intData[i], [77, 77, 77, 0, 0, 0, 0, 0, 0][i]);
|
||||
|
||||
// Fill it too much. Doesn't buffer overrun.
|
||||
cryptoSvc.byteCompressInts("NNNNNNNNNNNNNNNN", intData, 8);
|
||||
for (let i = 0; i < 8; ++i)
|
||||
do_check_eq(intData[i], "NNNNNNNNNNNNNNNN".charCodeAt(i));
|
||||
}
|
||||
|
||||
function test_encrypt_decrypt() {
|
||||
|
||||
// First, do a normal run with expected usage... Generate a random key and
|
||||
@ -83,7 +135,6 @@ function test_encrypt_decrypt() {
|
||||
|
||||
|
||||
// Do some more tests with a fixed key/iv, to check for reproducable results.
|
||||
cryptoSvc.algorithm = Ci.IWeaveCrypto.AES_128_CBC;
|
||||
key = "St1tFCor7vQEJNug/465dQ==";
|
||||
iv = "oLjkfrLIOnK2bDRvW4kXYA==";
|
||||
|
||||
@ -146,8 +197,6 @@ function test_encrypt_decrypt() {
|
||||
do_check_eq(clearText, mySecret);
|
||||
|
||||
|
||||
// Test with 192 bit key.
|
||||
cryptoSvc.algorithm = Ci.IWeaveCrypto.AES_192_CBC;
|
||||
key = "iz35tuIMq4/H+IYw2KTgow==";
|
||||
iv = "TJYrvva2KxvkM8hvOIvWp3xgjTXgq5Ss";
|
||||
mySecret = "i like pie";
|
||||
@ -157,8 +206,6 @@ function test_encrypt_decrypt() {
|
||||
do_check_eq(cipherText, "DLGx8BWqSCLGG7i/xwvvxg==");
|
||||
do_check_eq(clearText, mySecret);
|
||||
|
||||
// Test with 256 bit key.
|
||||
cryptoSvc.algorithm = Ci.IWeaveCrypto.AES_256_CBC;
|
||||
key = "c5hG3YG+NC61FFy8NOHQak1ZhMEWO79bwiAfar2euzI=";
|
||||
iv = "gsgLRDaxWvIfKt75RjuvFWERt83FFsY2A0TW+0b2iVk=";
|
||||
mySecret = "i like pie";
|
||||
@ -168,9 +215,6 @@ function test_encrypt_decrypt() {
|
||||
do_check_eq(cipherText, "o+ADtdMd8ubzNWurS6jt0Q==");
|
||||
do_check_eq(clearText, mySecret);
|
||||
|
||||
|
||||
// Test with bogus inputs
|
||||
cryptoSvc.algorithm = Ci.IWeaveCrypto.AES_128_CBC;
|
||||
key = "St1tFCor7vQEJNug/465dQ==";
|
||||
iv = "oLjkfrLIOnK2bDRvW4kXYA==";
|
||||
mySecret = "does thunder read testcases?";
|
||||
@ -213,5 +257,4 @@ function test_encrypt_decrypt() {
|
||||
failure = true;
|
||||
}
|
||||
do_check_true(failure);
|
||||
|
||||
}
|
||||
|
@ -59,22 +59,6 @@ function run_test() {
|
||||
iv = cryptoSvc.generateRandomIV();
|
||||
do_check_eq(iv.length, 24);
|
||||
|
||||
cryptoSvc.algorithm = Ci.IWeaveCrypto.AES_192_CBC;
|
||||
keydata = cryptoSvc.generateRandomKey();
|
||||
do_check_eq(keydata.length, 32);
|
||||
keydata2 = cryptoSvc.generateRandomKey();
|
||||
do_check_neq(keydata, keydata2); // sanity check for randomness
|
||||
iv = cryptoSvc.generateRandomIV();
|
||||
do_check_eq(iv.length, 24);
|
||||
|
||||
cryptoSvc.algorithm = Ci.IWeaveCrypto.AES_128_CBC;
|
||||
keydata = cryptoSvc.generateRandomKey();
|
||||
do_check_eq(keydata.length, 24);
|
||||
keydata2 = cryptoSvc.generateRandomKey();
|
||||
do_check_neq(keydata, keydata2); // sanity check for randomness
|
||||
iv = cryptoSvc.generateRandomIV();
|
||||
do_check_eq(iv.length, 24);
|
||||
|
||||
if (this.gczeal)
|
||||
gczeal(0);
|
||||
}
|
||||
|
@ -374,86 +374,7 @@ Engine.prototype = {
|
||||
if (!this._sync)
|
||||
throw "engine does not implement _sync method";
|
||||
|
||||
let times = {};
|
||||
let wrapped = {};
|
||||
// Find functions in any point of the prototype chain
|
||||
for (let _name in this) {
|
||||
let name = _name;
|
||||
|
||||
// Ignore certain constructors/functions
|
||||
if (name.search(/^_(.+Obj|notify)$/) == 0)
|
||||
continue;
|
||||
|
||||
// Only track functions but skip the constructors
|
||||
if (typeof this[name] == "function") {
|
||||
times[name] = [];
|
||||
wrapped[name] = this[name];
|
||||
|
||||
// Wrap the original function with a start/stop timer
|
||||
this[name] = function() {
|
||||
let start = Date.now();
|
||||
try {
|
||||
return wrapped[name].apply(this, arguments);
|
||||
}
|
||||
finally {
|
||||
times[name].push(Date.now() - start);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
this._notify("sync", this.name, this._sync)();
|
||||
}
|
||||
finally {
|
||||
// Restore original unwrapped functionality
|
||||
for (let [name, func] in Iterator(wrapped))
|
||||
this[name] = func;
|
||||
|
||||
let stats = {};
|
||||
for (let [name, time] in Iterator(times)) {
|
||||
// Figure out stats on the times unless there's nothing
|
||||
let num = time.length;
|
||||
if (num == 0)
|
||||
continue;
|
||||
|
||||
// Track the min/max/sum of the values
|
||||
let stat = {
|
||||
num: num,
|
||||
sum: 0
|
||||
};
|
||||
time.forEach(function(val) {
|
||||
if (stat.min == null || val < stat.min)
|
||||
stat.min = val;
|
||||
if (stat.max == null || val > stat.max)
|
||||
stat.max = val;
|
||||
stat.sum += val;
|
||||
});
|
||||
|
||||
stat.avg = Number((stat.sum / num).toFixed(2));
|
||||
stats[name] = stat;
|
||||
}
|
||||
|
||||
stats.toString = function() {
|
||||
let sums = [];
|
||||
for (let [name, stat] in Iterator(this))
|
||||
if (stat.sum != null)
|
||||
sums.push(name.replace(/^_/, "") + " " + stat.sum);
|
||||
|
||||
// Order certain functions first before any other random ones
|
||||
let nameOrder = ["sync", "processIncoming", "uploadOutgoing",
|
||||
"syncStartup", "syncFinish"];
|
||||
let getPos = function(str) {
|
||||
let pos = nameOrder.indexOf(str.split(" ")[0]);
|
||||
return pos != -1 ? pos : Infinity;
|
||||
};
|
||||
let order = function(a, b) getPos(a) > getPos(b);
|
||||
|
||||
return "Total (ms): " + sums.sort(order).join(", ");
|
||||
};
|
||||
|
||||
this._log.debug(stats);
|
||||
}
|
||||
this._notify("sync", this.name, this._sync)();
|
||||
},
|
||||
|
||||
/**
|
||||
@ -702,13 +623,16 @@ SyncEngine.prototype = {
|
||||
}
|
||||
}
|
||||
|
||||
newitems.recordHandler = Utils.bind2(this, function(item) {
|
||||
// Not binding this method to 'this' for performance reasons. It gets
|
||||
// called for every incoming record.
|
||||
let self = this;
|
||||
newitems.recordHandler = function(item) {
|
||||
// Grab a later last modified if possible
|
||||
if (this.lastModified == null || item.modified > this.lastModified)
|
||||
this.lastModified = item.modified;
|
||||
if (self.lastModified == null || item.modified > self.lastModified)
|
||||
self.lastModified = item.modified;
|
||||
|
||||
// Track the collection for the WBO.
|
||||
item.collection = this.name;
|
||||
item.collection = self.name;
|
||||
|
||||
// Remember which records were processed
|
||||
handled.push(item.id);
|
||||
@ -717,25 +641,25 @@ SyncEngine.prototype = {
|
||||
try {
|
||||
item.decrypt();
|
||||
} catch (ex if (Utils.isHMACMismatch(ex) &&
|
||||
this.handleHMACMismatch(item))) {
|
||||
self.handleHMACMismatch(item))) {
|
||||
// Let's try handling it.
|
||||
// If the callback returns true, try decrypting again, because
|
||||
// we've got new keys.
|
||||
this._log.info("Trying decrypt again...");
|
||||
self._log.info("Trying decrypt again...");
|
||||
item.decrypt();
|
||||
}
|
||||
} catch (ex) {
|
||||
this._log.warn("Error decrypting record: " + Utils.exceptionStr(ex));
|
||||
self._log.warn("Error decrypting record: " + Utils.exceptionStr(ex));
|
||||
failed.push(item.id);
|
||||
return;
|
||||
}
|
||||
|
||||
let shouldApply;
|
||||
try {
|
||||
shouldApply = this._reconcile(item);
|
||||
shouldApply = self._reconcile(item);
|
||||
} catch (ex) {
|
||||
this._log.warn("Failed to reconcile incoming record " + item.id);
|
||||
this._log.warn("Encountered exception: " + Utils.exceptionStr(ex));
|
||||
self._log.warn("Failed to reconcile incoming record " + item.id);
|
||||
self._log.warn("Encountered exception: " + Utils.exceptionStr(ex));
|
||||
failed.push(item.id);
|
||||
return;
|
||||
}
|
||||
@ -745,14 +669,14 @@ SyncEngine.prototype = {
|
||||
applyBatch.push(item);
|
||||
} else {
|
||||
count.reconciled++;
|
||||
this._log.trace("Skipping reconciled incoming item " + item.id);
|
||||
self._log.trace("Skipping reconciled incoming item " + item.id);
|
||||
}
|
||||
|
||||
if (applyBatch.length == this.applyIncomingBatchSize) {
|
||||
doApplyBatch.call(this);
|
||||
if (applyBatch.length == self.applyIncomingBatchSize) {
|
||||
doApplyBatch.call(self);
|
||||
}
|
||||
this._sleep(0);
|
||||
});
|
||||
self._sleep(0);
|
||||
};
|
||||
|
||||
// Only bother getting data from the server if there's new things
|
||||
if (this.lastModified == null || this.lastModified > this.lastSync) {
|
||||
@ -929,6 +853,7 @@ SyncEngine.prototype = {
|
||||
|
||||
// Upload outgoing records
|
||||
_uploadOutgoing: function SyncEngine__uploadOutgoing() {
|
||||
this._log.trace("Uploading local changes to server.");
|
||||
if (this._modifiedIDs.length) {
|
||||
this._log.trace("Preparing " + this._modifiedIDs.length +
|
||||
" outgoing records");
|
||||
|
@ -47,11 +47,23 @@ const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
const GUID_ANNO = "sync/guid";
|
||||
const MOBILE_ANNO = "mobile/bookmarksRoot";
|
||||
const PARENT_ANNO = "sync/parent";
|
||||
const SERVICE_NOT_SUPPORTED = "Service not supported on this platform";
|
||||
const ALLBOOKMARKS_ANNO = "AllBookmarks";
|
||||
const DESCRIPTION_ANNO = "bookmarkProperties/description";
|
||||
const SIDEBAR_ANNO = "bookmarkProperties/loadInSidebar";
|
||||
const STATICTITLE_ANNO = "bookmarks/staticTitle";
|
||||
const FEEDURI_ANNO = "livemark/feedURI";
|
||||
const SITEURI_ANNO = "livemark/siteURI";
|
||||
const GENERATORURI_ANNO = "microsummary/generatorURI";
|
||||
const MOBILEROOT_ANNO = "mobile/bookmarksRoot";
|
||||
const MOBILE_ANNO = "MobileBookmarks";
|
||||
const EXCLUDEBACKUP_ANNO = "places/excludeFromBackup";
|
||||
const SMART_BOOKMARKS_ANNO = "Places/SmartBookmark";
|
||||
const GUID_ANNO = "sync/guid";
|
||||
const PARENT_ANNO = "sync/parent";
|
||||
const ANNOS_TO_TRACK = [DESCRIPTION_ANNO, SIDEBAR_ANNO, STATICTITLE_ANNO,
|
||||
FEEDURI_ANNO, SITEURI_ANNO, GENERATORURI_ANNO];
|
||||
|
||||
const SERVICE_NOT_SUPPORTED = "Service not supported on this platform";
|
||||
const FOLDER_SORTINDEX = 1000000;
|
||||
|
||||
try {
|
||||
@ -72,9 +84,9 @@ function PlacesItem(collection, id, type) {
|
||||
this.type = type || "item";
|
||||
}
|
||||
PlacesItem.prototype = {
|
||||
decrypt: function PlacesItem_decrypt() {
|
||||
decrypt: function PlacesItem_decrypt(keyBundle) {
|
||||
// Do the normal CryptoWrapper decrypt, but change types before returning
|
||||
let clear = CryptoWrapper.prototype.decrypt.apply(this, arguments);
|
||||
let clear = CryptoWrapper.prototype.decrypt.call(this, keyBundle);
|
||||
|
||||
// Convert the abstract places item to the actual object type
|
||||
if (!this.deleted)
|
||||
@ -187,20 +199,20 @@ let kSpecialIds = {
|
||||
// Special IDs. Note that mobile can attempt to create a record on
|
||||
// dereference; special accessors are provided to prevent recursion within
|
||||
// observers.
|
||||
get guids()
|
||||
["menu", "places", "tags", "toolbar", "unfiled", "mobile"],
|
||||
guids: ["menu", "places", "tags", "toolbar", "unfiled", "mobile"],
|
||||
|
||||
// Create the special mobile folder to store mobile bookmarks.
|
||||
createMobileRoot: function createMobileRoot() {
|
||||
let root = Svc.Bookmark.placesRoot;
|
||||
let mRoot = Svc.Bookmark.createFolder(root, "mobile", -1);
|
||||
Utils.anno(mRoot, MOBILE_ANNO, 1);
|
||||
Svc.Annos.setItemAnnotation(mRoot, MOBILEROOT_ANNO, 1, 0,
|
||||
Svc.Annos.EXPIRE_NEVER);
|
||||
return mRoot;
|
||||
},
|
||||
|
||||
findMobileRoot: function findMobileRoot(create) {
|
||||
// Use the (one) mobile root if it already exists.
|
||||
let root = Svc.Annos.getItemsWithAnnotation(MOBILE_ANNO, {});
|
||||
let root = Svc.Annos.getItemsWithAnnotation(MOBILEROOT_ANNO, {});
|
||||
if (root.length != 0)
|
||||
return root[0];
|
||||
|
||||
@ -272,7 +284,7 @@ BookmarksEngine.prototype = {
|
||||
// Smart bookmarks map to their annotation value.
|
||||
let queryId;
|
||||
try {
|
||||
queryId = Utils.anno(id, SMART_BOOKMARKS_ANNO);
|
||||
queryId = Svc.Annos.getItemAnnotation(id, SMART_BOOKMARKS_ANNO);
|
||||
} catch(ex) {}
|
||||
|
||||
if (queryId)
|
||||
@ -544,7 +556,7 @@ BookmarksStore.prototype = {
|
||||
applyIncoming: function BStore_applyIncoming(record) {
|
||||
// Don't bother with pre and post-processing for deletions.
|
||||
if (record.deleted) {
|
||||
Store.prototype.applyIncoming.apply(this, arguments);
|
||||
Store.prototype.applyIncoming.call(this, record);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -577,7 +589,7 @@ BookmarksStore.prototype = {
|
||||
}
|
||||
|
||||
// Do the normal processing of incoming records
|
||||
Store.prototype.applyIncoming.apply(this, arguments);
|
||||
Store.prototype.applyIncoming.call(this, record);
|
||||
|
||||
// Do some post-processing if we have an item
|
||||
let itemId = this.idForGUID(record.id);
|
||||
@ -591,8 +603,10 @@ BookmarksStore.prototype = {
|
||||
}
|
||||
|
||||
// Create an annotation to remember that it needs reparenting.
|
||||
if (record._orphan)
|
||||
Utils.anno(itemId, PARENT_ANNO, parentGUID);
|
||||
if (record._orphan) {
|
||||
Svc.Annos.setItemAnnotation(itemId, PARENT_ANNO, parentGUID, 0,
|
||||
Svc.Annos.EXPIRE_NEVER);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -601,7 +615,7 @@ BookmarksStore.prototype = {
|
||||
*/
|
||||
_findAnnoItems: function BStore__findAnnoItems(anno, val) {
|
||||
return Svc.Annos.getItemsWithAnnotation(anno, {}).filter(function(id)
|
||||
Utils.anno(id, anno) == val);
|
||||
Svc.Annos.getItemAnnotation(id, anno) == val);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -683,27 +697,35 @@ BookmarksStore.prototype = {
|
||||
let uri = Utils.makeURI(record.bmkUri);
|
||||
newId = this._bms.insertBookmark(record._parent, uri,
|
||||
Svc.Bookmark.DEFAULT_INDEX, record.title);
|
||||
this._log.debug(["created bookmark", newId, "under", record._parent,
|
||||
"as", record.title, record.bmkUri].join(" "));
|
||||
this._log.debug("created bookmark " + newId + " under " + record._parent
|
||||
+ " as " + record.title + " " + record.bmkUri);
|
||||
|
||||
// Smart bookmark annotations are strings.
|
||||
if (record.queryId) {
|
||||
Utils.anno(newId, SMART_BOOKMARKS_ANNO, record.queryId);
|
||||
Svc.Annos.setItemAnnotation(newId, SMART_BOOKMARKS_ANNO, record.queryId,
|
||||
0, Svc.Annos.EXPIRE_NEVER);
|
||||
}
|
||||
|
||||
if (Utils.isArray(record.tags)) {
|
||||
this._tagURI(uri, record.tags);
|
||||
}
|
||||
this._bms.setKeywordForBookmark(newId, record.keyword);
|
||||
if (record.description)
|
||||
Utils.anno(newId, "bookmarkProperties/description", record.description);
|
||||
if (record.description) {
|
||||
Svc.Annos.setItemAnnotation(newId, DESCRIPTION_ANNO,
|
||||
record.description, 0,
|
||||
Svc.Annos.EXPIRE_NEVER);
|
||||
}
|
||||
|
||||
if (record.loadInSidebar)
|
||||
Utils.anno(newId, "bookmarkProperties/loadInSidebar", true);
|
||||
if (record.loadInSidebar) {
|
||||
Svc.Annos.setItemAnnotation(newId, SIDEBAR_ANNO, true, 0,
|
||||
Svc.Annos.EXPIRE_NEVER);
|
||||
}
|
||||
|
||||
if (record.type == "microsummary") {
|
||||
this._log.debug(" \-> is a microsummary");
|
||||
Utils.anno(newId, "bookmarks/staticTitle", record.staticTitle || "");
|
||||
Svc.Annos.setItemAnnotation(newId, STATICTITLE_ANNO,
|
||||
record.staticTitle || "", 0,
|
||||
Svc.Annos.EXPIRE_NEVER);
|
||||
let genURI = Utils.makeURI(record.generatorUri);
|
||||
if (this._ms) {
|
||||
try {
|
||||
@ -719,11 +741,13 @@ BookmarksStore.prototype = {
|
||||
case "folder":
|
||||
newId = this._bms.createFolder(record._parent, record.title,
|
||||
Svc.Bookmark.DEFAULT_INDEX);
|
||||
this._log.debug(["created folder", newId, "under", record._parent,
|
||||
"as", record.title].join(" "));
|
||||
this._log.debug("created folder " + newId + " under " + record._parent
|
||||
+ " as " + record.title);
|
||||
|
||||
if (record.description)
|
||||
Utils.anno(newId, "bookmarkProperties/description", record.description);
|
||||
if (record.description) {
|
||||
Svc.Annos.setItemAnnotation(newId, DESCRIPTION_ANNO, record.description,
|
||||
0, Svc.Annos.EXPIRE_NEVER);
|
||||
}
|
||||
|
||||
// record.children will be dealt with in _orderChildren.
|
||||
break;
|
||||
@ -754,8 +778,7 @@ BookmarksStore.prototype = {
|
||||
case "separator":
|
||||
newId = this._bms.insertSeparator(record._parent,
|
||||
Svc.Bookmark.DEFAULT_INDEX);
|
||||
this._log.debug(["created separator", newId, "under", record._parent]
|
||||
.join(" "));
|
||||
this._log.debug("created separator " + newId + " under " + record._parent);
|
||||
break;
|
||||
case "item":
|
||||
this._log.debug(" -> got a generic places item.. do nothing?");
|
||||
@ -857,13 +880,16 @@ BookmarksStore.prototype = {
|
||||
break;
|
||||
case "description":
|
||||
val = val || "";
|
||||
Utils.anno(itemId, "bookmarkProperties/description", val);
|
||||
Svc.Annos.setItemAnnotation(itemId, DESCRIPTION_ANNO, val, 0,
|
||||
Svc.Annos.EXPIRE_NEVER);
|
||||
break;
|
||||
case "loadInSidebar":
|
||||
if (val)
|
||||
Utils.anno(itemId, "bookmarkProperties/loadInSidebar", true);
|
||||
else
|
||||
Svc.Annos.removeItemAnnotation(itemId, "bookmarkProperties/loadInSidebar");
|
||||
if (val) {
|
||||
Svc.Annos.setItemAnnotation(itemId, SIDEBAR_ANNO, true, 0,
|
||||
Svc.Annos.EXPIRE_NEVER);
|
||||
} else {
|
||||
Svc.Annos.removeItemAnnotation(itemId, SIDEBAR_ANNO);
|
||||
}
|
||||
break;
|
||||
case "generatorUri": {
|
||||
try {
|
||||
@ -880,7 +906,8 @@ BookmarksStore.prototype = {
|
||||
}
|
||||
} break;
|
||||
case "queryId":
|
||||
Utils.anno(itemId, SMART_BOOKMARKS_ANNO, val);
|
||||
Svc.Annos.setItemAnnotation(itemId, SMART_BOOKMARKS_ANNO, val, 0,
|
||||
Svc.Annos.EXPIRE_NEVER);
|
||||
break;
|
||||
case "siteUri":
|
||||
this._ls.setSiteURI(itemId, Utils.makeURI(val));
|
||||
@ -948,19 +975,19 @@ BookmarksStore.prototype = {
|
||||
|
||||
_getDescription: function BStore__getDescription(id) {
|
||||
try {
|
||||
return Utils.anno(id, "bookmarkProperties/description");
|
||||
return Svc.Annos.getItemAnnotation(id, DESCRIPTION_ANNO);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
_isLoadInSidebar: function BStore__isLoadInSidebar(id) {
|
||||
return Svc.Annos.itemHasAnnotation(id, "bookmarkProperties/loadInSidebar");
|
||||
return Svc.Annos.itemHasAnnotation(id, SIDEBAR_ANNO);
|
||||
},
|
||||
|
||||
_getStaticTitle: function BStore__getStaticTitle(id) {
|
||||
try {
|
||||
return Utils.anno(id, "bookmarks/staticTitle");
|
||||
return Svc.Annos.getItemAnnotation(id, STATICTITLE_ANNO);
|
||||
} catch (e) {
|
||||
return "";
|
||||
}
|
||||
@ -1045,7 +1072,7 @@ BookmarksStore.prototype = {
|
||||
|
||||
// Persist the Smart Bookmark anno, if found.
|
||||
try {
|
||||
let anno = Utils.anno(placeId, SMART_BOOKMARKS_ANNO);
|
||||
let anno = Svc.Annos.getItemAnnotation(placeId, SMART_BOOKMARKS_ANNO);
|
||||
if (anno != null) {
|
||||
this._log.trace("query anno: " + SMART_BOOKMARKS_ANNO +
|
||||
" = " + anno);
|
||||
@ -1199,7 +1226,7 @@ BookmarksStore.prototype = {
|
||||
|
||||
// Some helper functions to handle GUIDs
|
||||
_setGUID: function _setGUID(id, guid) {
|
||||
if (arguments.length == 1)
|
||||
if (!guid)
|
||||
guid = Utils.makeGUID();
|
||||
|
||||
// If we can, set the GUID on moz_bookmarks and do not do any other work.
|
||||
@ -1334,8 +1361,8 @@ BookmarksStore.prototype = {
|
||||
stmt.params.guid = guid.toString();
|
||||
|
||||
let results = Utils.queryAsync(stmt, this._idForGUIDCols);
|
||||
this._log.trace("Rows matching GUID " + guid + ": " +
|
||||
results.map(function(x) x.item_id));
|
||||
this._log.trace("Number of rows matching GUID " + guid + ": "
|
||||
+ results.length);
|
||||
|
||||
// Here's the one we care about: the first.
|
||||
let result = results[0];
|
||||
@ -1570,7 +1597,7 @@ BookmarksTracker.prototype = {
|
||||
this._ensureMobileQuery();
|
||||
|
||||
// Make sure to remove items that have the exclude annotation
|
||||
if (Svc.Annos.itemHasAnnotation(itemId, "places/excludeFromBackup")) {
|
||||
if (Svc.Annos.itemHasAnnotation(itemId, EXCLUDEBACKUP_ANNO)) {
|
||||
this.removeChangedID(this._GUIDForId(itemId));
|
||||
return true;
|
||||
}
|
||||
@ -1614,17 +1641,17 @@ BookmarksTracker.prototype = {
|
||||
_ensureMobileQuery: function _ensureMobileQuery() {
|
||||
let anno = "PlacesOrganizer/OrganizerQuery";
|
||||
let find = function(val) Svc.Annos.getItemsWithAnnotation(anno, {}).filter(
|
||||
function(id) Utils.anno(id, anno) == val);
|
||||
function(id) Svc.Annos.getItemAnnotation(id, anno) == val);
|
||||
|
||||
// Don't continue if the Library isn't ready
|
||||
let all = find("AllBookmarks");
|
||||
let all = find(ALLBOOKMARKS_ANNO);
|
||||
if (all.length == 0)
|
||||
return;
|
||||
|
||||
// Disable handling of notifications while changing the mobile query
|
||||
this.ignoreAll = true;
|
||||
|
||||
let mobile = find("MobileBookmarks");
|
||||
let mobile = find(MOBILE_ANNO);
|
||||
let queryURI = Utils.makeURI("place:folder=" + kSpecialIds.mobile);
|
||||
let title = Str.sync.get("mobile.label");
|
||||
|
||||
@ -1636,8 +1663,10 @@ BookmarksTracker.prototype = {
|
||||
// Add the mobile bookmarks query if it doesn't exist
|
||||
else if (mobile.length == 0) {
|
||||
let query = Svc.Bookmark.insertBookmark(all[0], queryURI, -1, title);
|
||||
Utils.anno(query, anno, "MobileBookmarks");
|
||||
Utils.anno(query, "places/excludeFromBackup", 1);
|
||||
Svc.Annos.setItemAnnotation(query, anno, MOBILE_ANNO, 0,
|
||||
Svc.Annos.EXPIRE_NEVER);
|
||||
Svc.Annos.setItemAnnotation(query, EXCLUDEBACKUP_ANNO, 1, 0,
|
||||
Svc.Annos.EXPIRE_NEVER);
|
||||
}
|
||||
// Make sure the existing title is correct
|
||||
else if (Svc.Bookmark.getItemTitle(mobile[0]) != title)
|
||||
@ -1661,10 +1690,7 @@ BookmarksTracker.prototype = {
|
||||
}
|
||||
|
||||
// ignore annotations except for the ones that we sync
|
||||
let annos = ["bookmarkProperties/description",
|
||||
"bookmarkProperties/loadInSidebar", "bookmarks/staticTitle",
|
||||
"livemark/feedURI", "livemark/siteURI", "microsummary/generatorURI"];
|
||||
if (isAnno && annos.indexOf(property) == -1)
|
||||
if (isAnno && ANNOS_TO_TRACK.indexOf(property) == -1)
|
||||
return;
|
||||
|
||||
// Ignore favicon changes to avoid unnecessary churn
|
||||
|
@ -223,7 +223,7 @@ HistoryStore.prototype = {
|
||||
setGUID: function setGUID(uri, guid) {
|
||||
uri = uri.spec ? uri.spec : uri;
|
||||
|
||||
if (arguments.length == 1)
|
||||
if (!guid)
|
||||
guid = Utils.makeGUID();
|
||||
|
||||
// If we can, set the GUID on moz_places and do not do any other work.
|
||||
|
@ -310,33 +310,6 @@ let Utils = {
|
||||
return !!guid && this._base64url_regex.test(guid);
|
||||
},
|
||||
|
||||
anno: function anno(id, anno, val, expire) {
|
||||
// Figure out if we have a bookmark or page
|
||||
let annoFunc = (typeof id == "number" ? "Item" : "Page") + "Annotation";
|
||||
|
||||
// Convert to a nsIURI if necessary
|
||||
if (typeof id == "string")
|
||||
id = Utils.makeURI(id);
|
||||
|
||||
if (id == null)
|
||||
throw "Null id for anno! (invalid uri)";
|
||||
|
||||
switch (arguments.length) {
|
||||
case 2:
|
||||
// Get the annotation with 2 args
|
||||
return Svc.Annos["get" + annoFunc](id, anno);
|
||||
case 3:
|
||||
expire = "NEVER";
|
||||
// Fallthrough!
|
||||
case 4:
|
||||
// Convert to actual EXPIRE value
|
||||
expire = Svc.Annos["EXPIRE_" + expire];
|
||||
|
||||
// Set the annotation with 3 or 4 args
|
||||
return Svc.Annos["set" + annoFunc](id, anno, val, 0, expire);
|
||||
}
|
||||
},
|
||||
|
||||
ensureOneOpen: let (windows = {}) function ensureOneOpen(window) {
|
||||
// Close the other window if it exists
|
||||
let url = window.location.href;
|
||||
|
@ -88,7 +88,8 @@ function test_livemark_descriptions(next) {
|
||||
|
||||
// Attempt to provoke an error by adding a bad description anno.
|
||||
let id = store.idForGUID(record.id);
|
||||
Utils.anno(id, DESCRIPTION_ANNO, "");
|
||||
Svc.Annos.setItemAnnotation(id, DESCRIPTION_ANNO, "", 0,
|
||||
Svc.Annos.EXPIRE_NEVER);
|
||||
|
||||
next();
|
||||
}
|
||||
|
@ -83,7 +83,8 @@ function test_annotation_uploaded() {
|
||||
_("New item ID: " + mostVisitedID);
|
||||
do_check_true(!!mostVisitedID);
|
||||
|
||||
let annoValue = Utils.anno(mostVisitedID, SMART_BOOKMARKS_ANNO);
|
||||
let annoValue = Svc.Annos.getItemAnnotation(mostVisitedID,
|
||||
SMART_BOOKMARKS_ANNO);
|
||||
_("Anno: " + annoValue);
|
||||
do_check_eq("MostVisited", annoValue);
|
||||
|
||||
@ -157,7 +158,7 @@ function test_annotation_uploaded() {
|
||||
|
||||
_("Find by GUID and verify that it's annotated.");
|
||||
let newID = store.idForGUID(serverGUID);
|
||||
let newAnnoValue = Utils.anno(newID, SMART_BOOKMARKS_ANNO);
|
||||
let newAnnoValue = Svc.Annos.getItemAnnotation(newID, SMART_BOOKMARKS_ANNO);
|
||||
do_check_eq(newAnnoValue, "MostVisited");
|
||||
do_check_eq(Svc.Bookmark.getBookmarkURI(newID).spec, uri.spec);
|
||||
|
||||
@ -166,7 +167,8 @@ function test_annotation_uploaded() {
|
||||
do_check_eq(newRecord.queryId, newAnnoValue);
|
||||
newRecord.queryId = "LeastVisited";
|
||||
store.update(newRecord);
|
||||
do_check_eq("LeastVisited", Utils.anno(newID, SMART_BOOKMARKS_ANNO));
|
||||
do_check_eq("LeastVisited",
|
||||
Svc.Annos.getItemAnnotation(newID, SMART_BOOKMARKS_ANNO));
|
||||
|
||||
|
||||
} finally {
|
||||
|
@ -35,7 +35,7 @@ function test_bookmark_create() {
|
||||
do_check_eq(Svc.Bookmark.getItemType(id), Svc.Bookmark.TYPE_BOOKMARK);
|
||||
do_check_true(Svc.Bookmark.getBookmarkURI(id).equals(fxuri));
|
||||
do_check_eq(Svc.Bookmark.getItemTitle(id), fxrecord.title);
|
||||
do_check_eq(Utils.anno(id, "bookmarkProperties/description"),
|
||||
do_check_eq(Svc.Annos.getItemAnnotation(id, "bookmarkProperties/description"),
|
||||
fxrecord.description);
|
||||
do_check_eq(Svc.Bookmark.getFolderIdForItem(id),
|
||||
Svc.Bookmark.toolbarFolder);
|
||||
@ -69,7 +69,7 @@ function test_bookmark_create() {
|
||||
do_check_eq(Svc.Bookmark.getItemTitle(id), null);
|
||||
let error;
|
||||
try {
|
||||
Utils.anno(id, "bookmarkProperties/description");
|
||||
Svc.Annos.getItemAnnotation(id, "bookmarkProperties/description");
|
||||
} catch(ex) {
|
||||
error = ex;
|
||||
}
|
||||
@ -89,7 +89,8 @@ function test_bookmark_update() {
|
||||
let bmk1_id = Svc.Bookmark.insertBookmark(
|
||||
Svc.Bookmark.toolbarFolder, fxuri, Svc.Bookmark.DEFAULT_INDEX,
|
||||
"Get Firefox!");
|
||||
Utils.anno(bmk1_id, "bookmarkProperties/description", "Firefox is awesome.");
|
||||
Svc.Annos.setItemAnnotation(bmk1_id, "bookmarkProperties/description",
|
||||
"Firefox is awesome.", 0, Svc.Annos.EXPIRE_NEVER);
|
||||
Svc.Bookmark.setKeywordForBookmark(bmk1_id, "firefox");
|
||||
let bmk1_guid = store.GUIDForId(bmk1_id);
|
||||
|
||||
@ -102,7 +103,7 @@ function test_bookmark_update() {
|
||||
store.applyIncoming(record);
|
||||
|
||||
_("Verify that the values have been cleared.");
|
||||
do_check_eq(Utils.anno(bmk1_id, "bookmarkProperties/description"), "");
|
||||
do_check_eq(Svc.Annos.getItemAnnotation(bmk1_id, "bookmarkProperties/description"), "");
|
||||
do_check_eq(Svc.Bookmark.getItemTitle(bmk1_id), "");
|
||||
do_check_eq(Svc.Bookmark.getKeywordForBookmark(bmk1_id), null);
|
||||
} finally {
|
||||
@ -306,7 +307,7 @@ function test_orphan() {
|
||||
do_check_eq(Svc.Bookmark.getFolderIdForItem(bmk1_id), Svc.Bookmark.toolbarFolder);
|
||||
let error;
|
||||
try {
|
||||
Utils.anno(bmk1_id, PARENT_ANNO);
|
||||
Svc.Annos.getItemAnnotation(bmk1_id, PARENT_ANNO);
|
||||
} catch(ex) {
|
||||
error = ex;
|
||||
}
|
||||
@ -319,7 +320,7 @@ function test_orphan() {
|
||||
|
||||
_("Verify that bookmark has been flagged as orphan, has not moved.");
|
||||
do_check_eq(Svc.Bookmark.getFolderIdForItem(bmk1_id), Svc.Bookmark.toolbarFolder);
|
||||
do_check_eq(Utils.anno(bmk1_id, PARENT_ANNO), "non-existent");
|
||||
do_check_eq(Svc.Annos.getItemAnnotation(bmk1_id, PARENT_ANNO), "non-existent");
|
||||
|
||||
} finally {
|
||||
_("Clean up.");
|
||||
@ -334,14 +335,16 @@ function test_reparentOrphans() {
|
||||
let folder1_guid = store.GUIDForId(folder1_id);
|
||||
|
||||
_("Create a bogus orphan record and write the record back to the store to trigger _reparentOrphans.");
|
||||
Utils.anno(folder1_id, PARENT_ANNO, folder1_guid);
|
||||
Svc.Annos.setItemAnnotation(folder1_id, PARENT_ANNO, folder1_guid, 0,
|
||||
Svc.Annos.EXPIRE_NEVER);
|
||||
let record = store.createRecord(folder1_guid);
|
||||
record.title = "New title for Folder 1";
|
||||
store._childrenToOrder = {};
|
||||
store.applyIncoming(record);
|
||||
|
||||
_("Verify that is has been marked as an orphan even though it couldn't be moved into itself.");
|
||||
do_check_eq(Utils.anno(folder1_id, PARENT_ANNO), folder1_guid);
|
||||
do_check_eq(Svc.Annos.getItemAnnotation(folder1_id, PARENT_ANNO),
|
||||
folder1_guid);
|
||||
|
||||
} finally {
|
||||
_("Clean up.");
|
||||
@ -378,7 +381,7 @@ function test_copying_avoid_duplicate_guids() {
|
||||
let id = store.idForGUID(fxrecord.id);
|
||||
do_check_eq(store.GUIDForId(id), fxrecord.id);
|
||||
do_check_true(Svc.Bookmark.getBookmarkURI(id).equals(fxuri));
|
||||
do_check_eq(Utils.anno(id, "bookmarkProperties/description"),
|
||||
do_check_eq(Svc.Annos.getItemAnnotation(id, "bookmarkProperties/description"),
|
||||
fxrecord.description);
|
||||
|
||||
_("Copy the record as happens in the UI: with the same GUID.");
|
||||
@ -397,8 +400,9 @@ function test_copying_avoid_duplicate_guids() {
|
||||
do_check_neq(store.GUIDForId(copy), store.GUIDForId(id));
|
||||
|
||||
_("Verify that the anno itself has changed.");
|
||||
do_check_neq(Utils.anno(copy, "sync/guid"), fxrecord.id);
|
||||
do_check_eq(Utils.anno(copy, "sync/guid"), store.GUIDForId(copy));
|
||||
do_check_neq(Svc.Annos.getItemAnnotation(copy, "sync/guid"), fxrecord.id);
|
||||
do_check_eq(Svc.Annos.getItemAnnotation(copy, "sync/guid"),
|
||||
store.GUIDForId(copy));
|
||||
|
||||
} finally {
|
||||
_("Clean up.");
|
||||
|
@ -283,7 +283,8 @@ function test_guid_stripping() {
|
||||
let victimGUID = store.GUIDForId(victim);
|
||||
_("Set the GUID on one entry to be the same as another.");
|
||||
do_check_neq(suspectGUID, victimGUID);
|
||||
Utils.anno(suspect, SYNC_GUID_ANNO, store.GUIDForId(victim));
|
||||
Svc.Annos.setItemAnnotation(suspect, SYNC_GUID_ANNO, store.GUIDForId(victim),
|
||||
0, Svc.Annos.EXPIRE_NEVER);
|
||||
|
||||
_("Tracker changed it to something else.");
|
||||
let newGUID = store.GUIDForId(suspect);
|
||||
|
@ -1,41 +0,0 @@
|
||||
_("Make sure various combinations of anno arguments do the right get/set for pages/items");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function run_test() {
|
||||
_("create a bookmark to a url so it exists");
|
||||
let url = "about:";
|
||||
let bmkid = Svc.Bookmark.insertBookmark(Svc.Bookmark.unfiledBookmarksFolder,
|
||||
Utils.makeURI(url), -1, "");
|
||||
|
||||
_("set an anno on the bookmark ");
|
||||
Utils.anno(bmkid, "anno", "hi");
|
||||
do_check_eq(Utils.anno(bmkid, "anno"), "hi");
|
||||
|
||||
_("set an anno on a url");
|
||||
Utils.anno(url, "tation", "hello");
|
||||
do_check_eq(Utils.anno(url, "tation"), "hello");
|
||||
|
||||
_("make sure getting it also works with a nsIURI");
|
||||
let uri = Utils.makeURI(url);
|
||||
do_check_eq(Utils.anno(uri, "tation"), "hello");
|
||||
|
||||
_("make sure annotations get updated");
|
||||
Utils.anno(uri, "tation", "bye!");
|
||||
do_check_eq(Utils.anno(url, "tation"), "bye!");
|
||||
|
||||
_("sanity check that the item anno is still there");
|
||||
do_check_eq(Utils.anno(bmkid, "anno"), "hi");
|
||||
|
||||
_("invalid uris don't get annos");
|
||||
let didThrow = false;
|
||||
try {
|
||||
Utils.anno("foo/bar/baz", "bad");
|
||||
}
|
||||
catch(ex) {
|
||||
didThrow = true;
|
||||
}
|
||||
do_check_true(didThrow);
|
||||
|
||||
_("cleaning up the bookmark we created");
|
||||
Svc.Bookmark.removeItem(bmkid);
|
||||
}
|
Loading…
Reference in New Issue
Block a user