Bug 1648978 - Workaround for a race in OpenPGP key acceptance storage. r=PatrickBrunschwig a=wsmwk
authorKai Engert <kaie@kuix.de>
Sun, 28 Jun 2020 11:42:21 +0200
changeset 39479 7783b3d24305817f20a3ac803be3e4eeb4e15d9a
parent 39478 967707e1200c71b210cc9018dd10e8f773c33adf
child 39480 7b3c0e1440512d505a825df64dc3bd10a40713af
push id402
push userclokep@gmail.com
push dateMon, 29 Jun 2020 20:48:04 +0000
reviewersPatrickBrunschwig, wsmwk
bugs1648978
Bug 1648978 - Workaround for a race in OpenPGP key acceptance storage. r=PatrickBrunschwig a=wsmwk Differential Revision: https://phabricator.services.mozilla.com/D81455
mail/extensions/openpgp/content/modules/sqliteDb.jsm
--- a/mail/extensions/openpgp/content/modules/sqliteDb.jsm
+++ b/mail/extensions/openpgp/content/modules/sqliteDb.jsm
@@ -47,26 +47,36 @@ var PgpSqliteDb2 = {
         `sqliteDb.jsm: PgpSqliteDb2 checkDatabaseStructure: ERROR: ${ex}\n`
       );
       if (conn) {
         await conn.close();
       }
     }
   },
 
+  accCacheFingerprint: "",
+  accCacheValue: "",
+  accCacheEmails: null,
+
   async getFingerprintAcceptance(conn, fingerprint, rv) {
+    fingerprint = fingerprint.toLowerCase();
+    if (fingerprint == this.accCacheFingerprint) {
+      rv.fingerprintAcceptance = this.accCacheValue;
+      return;
+    }
+
     let myConn = false;
 
     try {
       if (!conn) {
         myConn = true;
         conn = await this.openDatabase();
       }
 
-      let qObj = { fpr: fingerprint.toLowerCase() };
+      let qObj = { fpr: fingerprint };
       await conn
         .execute(
           "select decision from acceptance_decision where fpr = :fpr",
           qObj
         )
         .then(result => {
           if (result.length) {
             rv.fingerprintAcceptance = result[0].getResultByName("decision");
@@ -77,29 +87,42 @@ var PgpSqliteDb2 = {
     }
 
     if (myConn && conn) {
       await conn.close();
     }
   },
 
   async getAcceptance(fingerprint, email, rv) {
+    fingerprint = fingerprint.toLowerCase();
+    email = email.toLowerCase();
+
+    if (
+      fingerprint == this.accCacheFingerprint &&
+      this.accCacheEmails &&
+      this.accCacheEmails.has(email)
+    ) {
+      rv.emailDecided = true;
+      rv.fingerprintAcceptance = this.accCacheValue;
+      return;
+    }
+
     rv.emailDecided = false;
     rv.fingerprintAcceptance = "";
 
     let conn;
     try {
       conn = await this.openDatabase();
 
       await this.getFingerprintAcceptance(conn, fingerprint, rv);
 
       if (rv.fingerprintAcceptance) {
         let qObj = {
-          fpr: fingerprint.toLowerCase(),
-          email: email.toLowerCase(),
+          fpr: fingerprint,
+          email,
         };
         await conn
           .execute(
             "select count(*) from acceptance_email where fpr = :fpr and email = :email",
             qObj
           )
           .then(result => {
             if (result.length) {
@@ -123,73 +146,84 @@ var PgpSqliteDb2 = {
     await conn.execute(
       "delete from acceptance_decision where fpr = :fpr",
       delObj
     );
     await conn.execute("delete from acceptance_email where fpr = :fpr", delObj);
   },
 
   async deleteAcceptance(fingerprint) {
+    fingerprint = fingerprint.toLowerCase();
+    this.accCacheFingerprint = fingerprint;
+    this.accCacheValue = "";
+    this.accCacheEmails = null;
     let conn;
     try {
       conn = await this.openDatabase();
       await conn.execute("begin transaction");
-      fingerprint = fingerprint.toLowerCase();
       await this.internalDeleteAcceptanceNoTransaction(conn, fingerprint);
       await conn.execute("commit transaction");
       await conn.close();
     } catch (ex) {
       console.debug(ex);
       if (conn) {
         await conn.close();
       }
     }
   },
 
   async updateAcceptance(fingerprint, emailArray, decision) {
+    fingerprint = fingerprint.toLowerCase();
     let conn;
     try {
+      let uniqueEmails = new Set();
+      if (decision !== "undecided") {
+        if (emailArray) {
+          for (let email of emailArray) {
+            if (!email) {
+              continue;
+            }
+            email = email.toLowerCase();
+            if (uniqueEmails.has(email)) {
+              continue;
+            }
+            uniqueEmails.add(email);
+          }
+        }
+      }
+
+      this.accCacheFingerprint = fingerprint;
+      this.accCacheValue = decision;
+      this.accCacheEmails = uniqueEmails;
+
       conn = await this.openDatabase();
-
       await conn.execute("begin transaction");
-
-      fingerprint = fingerprint.toLowerCase();
       await this.internalDeleteAcceptanceNoTransaction(conn, fingerprint);
 
       if (decision !== "undecided") {
         let decisionObj = {
           fpr: fingerprint,
           decision,
         };
         await conn.execute(
           "insert into acceptance_decision values (:fpr, :decision)",
           decisionObj
         );
 
         /* A key might contain multiple user IDs with the same email
          * address. We add each email only once. */
-        let alreadyAdded = new Set();
         let insertObj = {
           fpr: fingerprint,
         };
-        if (emailArray) {
-          for (let email of emailArray) {
-            if (!email) {
-              continue;
-            }
-            insertObj.email = email.toLowerCase();
-            if (alreadyAdded.has(insertObj.email)) {
-              continue;
-            }
-            alreadyAdded.add(insertObj.email);
-            await conn.execute(
-              "insert into acceptance_email values (:fpr, :email)",
-              insertObj
-            );
-          }
+        for (let email of uniqueEmails) {
+          insertObj.email = email;
+          await conn.execute(
+            "insert into acceptance_email values (:fpr, :email)",
+            insertObj
+          );
         }
       }
       await conn.execute("commit transaction");
       await conn.close();
     } catch (ex) {
       console.debug(ex);
       if (conn) {
         await conn.close();