Fri, 16 Jan 2015 18:13:44 +0100
Integrate suggestion from review to improve consistency with existing code.
michael@0 | 1 | # This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
michael@0 | 4 | |
michael@0 | 5 | |
michael@0 | 6 | // A class that serializes and deserializes opaque key/value string to |
michael@0 | 7 | // string maps to/from maps (trtables). It knows how to create |
michael@0 | 8 | // trtables from the serialized format, so it also understands |
michael@0 | 9 | // meta-information like the name of the table and the table's |
michael@0 | 10 | // version. See docs for the protocol description. |
michael@0 | 11 | // |
michael@0 | 12 | // TODO: wireformatreader: if you have multiple updates for one table |
michael@0 | 13 | // in a call to deserialize, the later ones will be merged |
michael@0 | 14 | // (all but the last will be ignored). To fix, merge instead |
michael@0 | 15 | // of replace when you have an existing table, and only do so once. |
michael@0 | 16 | // TODO must have blank line between successive types -- problem? |
michael@0 | 17 | // TODO doesn't tolerate blank lines very well |
michael@0 | 18 | // |
michael@0 | 19 | // Maybe: These classes could use a LOT more cleanup, but it's not a |
michael@0 | 20 | // priority at the moment. For example, the tablesData/Known |
michael@0 | 21 | // maps should be combined into a single object, the parser |
michael@0 | 22 | // for a given type should be separate from the version info, |
michael@0 | 23 | // and there should be synchronous interfaces for testing. |
michael@0 | 24 | |
michael@0 | 25 | |
michael@0 | 26 | /** |
michael@0 | 27 | * A class that knows how to serialize and deserialize meta-information. |
michael@0 | 28 | * This meta information is the table name and version number, and |
michael@0 | 29 | * in its serialized form looks like the first line below: |
michael@0 | 30 | * |
michael@0 | 31 | * [name-of-table X.Y update?] |
michael@0 | 32 | * ...key/value pairs to add or delete follow... |
michael@0 | 33 | * <blank line ends the table> |
michael@0 | 34 | * |
michael@0 | 35 | * The X.Y is the version number and the optional "update" token means |
michael@0 | 36 | * that the table is a differential from the curent table the extension |
michael@0 | 37 | * has. Its absence means that this is a full, new table. |
michael@0 | 38 | */ |
michael@0 | 39 | function PROT_VersionParser(type, opt_major, opt_minor, opt_requireMac) { |
michael@0 | 40 | this.debugZone = "versionparser"; |
michael@0 | 41 | this.type = type; |
michael@0 | 42 | this.major = 0; |
michael@0 | 43 | this.minor = 0; |
michael@0 | 44 | |
michael@0 | 45 | this.badHeader = false; |
michael@0 | 46 | |
michael@0 | 47 | // Should the wireformatreader compute a mac? |
michael@0 | 48 | this.mac = false; |
michael@0 | 49 | this.macval = ""; |
michael@0 | 50 | this.macFailed = false; |
michael@0 | 51 | this.requireMac = !!opt_requireMac; |
michael@0 | 52 | |
michael@0 | 53 | this.update = false; |
michael@0 | 54 | this.needsUpdate = false; // used by ListManager to determine update policy |
michael@0 | 55 | // Used by ListerManager to see if we have read data for this table from |
michael@0 | 56 | // disk. Once we read a table from disk, we are not going to do so again |
michael@0 | 57 | // but instead update remotely if necessary. |
michael@0 | 58 | this.didRead = false; |
michael@0 | 59 | if (opt_major) |
michael@0 | 60 | this.major = parseInt(opt_major); |
michael@0 | 61 | if (opt_minor) |
michael@0 | 62 | this.minor = parseInt(opt_minor); |
michael@0 | 63 | } |
michael@0 | 64 | |
michael@0 | 65 | /** Import the version information from another VersionParser |
michael@0 | 66 | * @params version a version parser object |
michael@0 | 67 | */ |
michael@0 | 68 | PROT_VersionParser.prototype.ImportVersion = function(version) { |
michael@0 | 69 | this.major = version.major; |
michael@0 | 70 | this.minor = version.minor; |
michael@0 | 71 | |
michael@0 | 72 | this.mac = version.mac; |
michael@0 | 73 | this.macFailed = version.macFailed; |
michael@0 | 74 | this.macval = version.macval; |
michael@0 | 75 | // Don't set requireMac, since we create vparsers from scratch and doesn't |
michael@0 | 76 | // know about it |
michael@0 | 77 | } |
michael@0 | 78 | |
michael@0 | 79 | /** |
michael@0 | 80 | * Creates a string like [goog-white-black 1.1] from internal information |
michael@0 | 81 | * |
michael@0 | 82 | * @returns String |
michael@0 | 83 | */ |
michael@0 | 84 | PROT_VersionParser.prototype.toString = function() { |
michael@0 | 85 | var s = "[" + this.type + " " + this.major + "." + this.minor + "]"; |
michael@0 | 86 | return s; |
michael@0 | 87 | } |
michael@0 | 88 | |
michael@0 | 89 | /** |
michael@0 | 90 | * Creates a string like 1.123 with the version number. This is the |
michael@0 | 91 | * format we store in prefs. |
michael@0 | 92 | * @return String |
michael@0 | 93 | */ |
michael@0 | 94 | PROT_VersionParser.prototype.versionString = function() { |
michael@0 | 95 | return this.major + "." + this.minor; |
michael@0 | 96 | } |
michael@0 | 97 | |
michael@0 | 98 | /** |
michael@0 | 99 | * Creates a string like 1:1 from internal information used for |
michael@0 | 100 | * fetching updates from the server. Called by the listmanager. |
michael@0 | 101 | * |
michael@0 | 102 | * @returns String |
michael@0 | 103 | */ |
michael@0 | 104 | PROT_VersionParser.prototype.toUrl = function() { |
michael@0 | 105 | return this.major + ":" + this.minor; |
michael@0 | 106 | } |
michael@0 | 107 | |
michael@0 | 108 | /** |
michael@0 | 109 | * Process the old format, [type major.minor [update]] |
michael@0 | 110 | * |
michael@0 | 111 | * @returns true if the string could be parsed, false otherwise |
michael@0 | 112 | */ |
michael@0 | 113 | PROT_VersionParser.prototype.processOldFormat_ = function(line) { |
michael@0 | 114 | if (line[0] != '[' || line.slice(-1) != ']') |
michael@0 | 115 | return false; |
michael@0 | 116 | |
michael@0 | 117 | var description = line.slice(1, -1); |
michael@0 | 118 | |
michael@0 | 119 | // Get the type name and version number of this table |
michael@0 | 120 | var tokens = description.split(" "); |
michael@0 | 121 | this.type = tokens[0]; |
michael@0 | 122 | var majorminor = tokens[1].split("."); |
michael@0 | 123 | this.major = parseInt(majorminor[0]); |
michael@0 | 124 | this.minor = parseInt(majorminor[1]); |
michael@0 | 125 | if (isNaN(this.major) || isNaN(this.minor)) |
michael@0 | 126 | return false; |
michael@0 | 127 | |
michael@0 | 128 | if (tokens.length >= 3) { |
michael@0 | 129 | this.update = tokens[2] == "update"; |
michael@0 | 130 | } |
michael@0 | 131 | |
michael@0 | 132 | return true; |
michael@0 | 133 | } |
michael@0 | 134 | |
michael@0 | 135 | /** |
michael@0 | 136 | * Takes a string like [name-of-table 1.1 [update]][mac=MAC] and figures out the |
michael@0 | 137 | * type and corresponding version numbers. |
michael@0 | 138 | * @returns true if the string could be parsed, false otherwise |
michael@0 | 139 | */ |
michael@0 | 140 | PROT_VersionParser.prototype.fromString = function(line) { |
michael@0 | 141 | G_Debug(this, "Calling fromString with line: " + line); |
michael@0 | 142 | if (line[0] != '[' || line.slice(-1) != ']') |
michael@0 | 143 | return false; |
michael@0 | 144 | |
michael@0 | 145 | // There could be two [][], so take care of it |
michael@0 | 146 | var secondBracket = line.indexOf('[', 1); |
michael@0 | 147 | var firstPart = null; |
michael@0 | 148 | var secondPart = null; |
michael@0 | 149 | |
michael@0 | 150 | if (secondBracket != -1) { |
michael@0 | 151 | firstPart = line.substring(0, secondBracket); |
michael@0 | 152 | secondPart = line.substring(secondBracket); |
michael@0 | 153 | G_Debug(this, "First part: " + firstPart + " Second part: " + secondPart); |
michael@0 | 154 | } else { |
michael@0 | 155 | firstPart = line; |
michael@0 | 156 | G_Debug(this, "Old format: " + firstPart); |
michael@0 | 157 | } |
michael@0 | 158 | |
michael@0 | 159 | if (!this.processOldFormat_(firstPart)) |
michael@0 | 160 | return false; |
michael@0 | 161 | |
michael@0 | 162 | if (secondPart && !this.processOptTokens_(secondPart)) |
michael@0 | 163 | return false; |
michael@0 | 164 | |
michael@0 | 165 | return true; |
michael@0 | 166 | } |
michael@0 | 167 | |
michael@0 | 168 | /** |
michael@0 | 169 | * Process optional tokens |
michael@0 | 170 | * |
michael@0 | 171 | * @param line A string [token1=val1 token2=val2...] |
michael@0 | 172 | * @returns true if the string could be parsed, false otherwise |
michael@0 | 173 | */ |
michael@0 | 174 | PROT_VersionParser.prototype.processOptTokens_ = function(line) { |
michael@0 | 175 | if (line[0] != '[' || line.slice(-1) != ']') |
michael@0 | 176 | return false; |
michael@0 | 177 | var description = line.slice(1, -1); |
michael@0 | 178 | // Get the type name and version number of this table |
michael@0 | 179 | var tokens = description.split(" "); |
michael@0 | 180 | |
michael@0 | 181 | for (var i = 0; i < tokens.length; i++) { |
michael@0 | 182 | G_Debug(this, "Processing optional token: " + tokens[i]); |
michael@0 | 183 | var tokenparts = tokens[i].split("="); |
michael@0 | 184 | switch(tokenparts[0]){ |
michael@0 | 185 | case "mac": |
michael@0 | 186 | this.mac = true; |
michael@0 | 187 | if (tokenparts.length < 2) { |
michael@0 | 188 | G_Debug(this, "Found mac flag but not mac value!"); |
michael@0 | 189 | return false; |
michael@0 | 190 | } |
michael@0 | 191 | // The mac value may have "=" in it, so we can't just use tokenparts[1]. |
michael@0 | 192 | // Instead, just take the rest of tokens[i] after the first "=" |
michael@0 | 193 | this.macval = tokens[i].substr(tokens[i].indexOf("=")+1); |
michael@0 | 194 | break; |
michael@0 | 195 | default: |
michael@0 | 196 | G_Debug(this, "Found unrecognized token: " + tokenparts[0]); |
michael@0 | 197 | break; |
michael@0 | 198 | } |
michael@0 | 199 | } |
michael@0 | 200 | |
michael@0 | 201 | return true; |
michael@0 | 202 | } |
michael@0 | 203 | |
michael@0 | 204 | #ifdef DEBUG |
michael@0 | 205 | function TEST_PROT_WireFormat() { |
michael@0 | 206 | if (G_GDEBUG) { |
michael@0 | 207 | var z = "versionparser UNITTEST"; |
michael@0 | 208 | G_Debug(z, "Starting"); |
michael@0 | 209 | |
michael@0 | 210 | var vp = new PROT_VersionParser("dummy"); |
michael@0 | 211 | G_Assert(z, vp.fromString("[foo-bar-url 1.234]"), |
michael@0 | 212 | "failed to parse old format"); |
michael@0 | 213 | G_Assert(z, "foo-bar-url" == vp.type, "failed to parse type"); |
michael@0 | 214 | G_Assert(z, "1" == vp.major, "failed to parse major"); |
michael@0 | 215 | G_Assert(z, "234" == vp.minor, "failed to parse minor"); |
michael@0 | 216 | |
michael@0 | 217 | vp = new PROT_VersionParser("dummy"); |
michael@0 | 218 | G_Assert(z, vp.fromString("[foo-bar-url 1.234][mac=567]"), |
michael@0 | 219 | "failed to parse new format"); |
michael@0 | 220 | G_Assert(z, "foo-bar-url" == vp.type, "failed to parse type"); |
michael@0 | 221 | G_Assert(z, "1" == vp.major, "failed to parse major"); |
michael@0 | 222 | G_Assert(z, "234" == vp.minor, "failed to parse minor"); |
michael@0 | 223 | G_Assert(z, true == vp.mac, "failed to parse mac"); |
michael@0 | 224 | G_Assert(z, "567" == vp.macval, "failed to parse macval"); |
michael@0 | 225 | |
michael@0 | 226 | G_Debug(z, "PASSED"); |
michael@0 | 227 | } |
michael@0 | 228 | } |
michael@0 | 229 | #endif |