michael@0: # This Source Code Form is subject to the terms of the Mozilla Public michael@0: # License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: # file, You can obtain one at http://mozilla.org/MPL/2.0/. michael@0: michael@0: michael@0: // A class that serializes and deserializes opaque key/value string to michael@0: // string maps to/from maps (trtables). It knows how to create michael@0: // trtables from the serialized format, so it also understands michael@0: // meta-information like the name of the table and the table's michael@0: // version. See docs for the protocol description. michael@0: // michael@0: // TODO: wireformatreader: if you have multiple updates for one table michael@0: // in a call to deserialize, the later ones will be merged michael@0: // (all but the last will be ignored). To fix, merge instead michael@0: // of replace when you have an existing table, and only do so once. michael@0: // TODO must have blank line between successive types -- problem? michael@0: // TODO doesn't tolerate blank lines very well michael@0: // michael@0: // Maybe: These classes could use a LOT more cleanup, but it's not a michael@0: // priority at the moment. For example, the tablesData/Known michael@0: // maps should be combined into a single object, the parser michael@0: // for a given type should be separate from the version info, michael@0: // and there should be synchronous interfaces for testing. michael@0: michael@0: michael@0: /** michael@0: * A class that knows how to serialize and deserialize meta-information. michael@0: * This meta information is the table name and version number, and michael@0: * in its serialized form looks like the first line below: michael@0: * michael@0: * [name-of-table X.Y update?] michael@0: * ...key/value pairs to add or delete follow... michael@0: * michael@0: * michael@0: * The X.Y is the version number and the optional "update" token means michael@0: * that the table is a differential from the curent table the extension michael@0: * has. Its absence means that this is a full, new table. michael@0: */ michael@0: function PROT_VersionParser(type, opt_major, opt_minor, opt_requireMac) { michael@0: this.debugZone = "versionparser"; michael@0: this.type = type; michael@0: this.major = 0; michael@0: this.minor = 0; michael@0: michael@0: this.badHeader = false; michael@0: michael@0: // Should the wireformatreader compute a mac? michael@0: this.mac = false; michael@0: this.macval = ""; michael@0: this.macFailed = false; michael@0: this.requireMac = !!opt_requireMac; michael@0: michael@0: this.update = false; michael@0: this.needsUpdate = false; // used by ListManager to determine update policy michael@0: // Used by ListerManager to see if we have read data for this table from michael@0: // disk. Once we read a table from disk, we are not going to do so again michael@0: // but instead update remotely if necessary. michael@0: this.didRead = false; michael@0: if (opt_major) michael@0: this.major = parseInt(opt_major); michael@0: if (opt_minor) michael@0: this.minor = parseInt(opt_minor); michael@0: } michael@0: michael@0: /** Import the version information from another VersionParser michael@0: * @params version a version parser object michael@0: */ michael@0: PROT_VersionParser.prototype.ImportVersion = function(version) { michael@0: this.major = version.major; michael@0: this.minor = version.minor; michael@0: michael@0: this.mac = version.mac; michael@0: this.macFailed = version.macFailed; michael@0: this.macval = version.macval; michael@0: // Don't set requireMac, since we create vparsers from scratch and doesn't michael@0: // know about it michael@0: } michael@0: michael@0: /** michael@0: * Creates a string like [goog-white-black 1.1] from internal information michael@0: * michael@0: * @returns String michael@0: */ michael@0: PROT_VersionParser.prototype.toString = function() { michael@0: var s = "[" + this.type + " " + this.major + "." + this.minor + "]"; michael@0: return s; michael@0: } michael@0: michael@0: /** michael@0: * Creates a string like 1.123 with the version number. This is the michael@0: * format we store in prefs. michael@0: * @return String michael@0: */ michael@0: PROT_VersionParser.prototype.versionString = function() { michael@0: return this.major + "." + this.minor; michael@0: } michael@0: michael@0: /** michael@0: * Creates a string like 1:1 from internal information used for michael@0: * fetching updates from the server. Called by the listmanager. michael@0: * michael@0: * @returns String michael@0: */ michael@0: PROT_VersionParser.prototype.toUrl = function() { michael@0: return this.major + ":" + this.minor; michael@0: } michael@0: michael@0: /** michael@0: * Process the old format, [type major.minor [update]] michael@0: * michael@0: * @returns true if the string could be parsed, false otherwise michael@0: */ michael@0: PROT_VersionParser.prototype.processOldFormat_ = function(line) { michael@0: if (line[0] != '[' || line.slice(-1) != ']') michael@0: return false; michael@0: michael@0: var description = line.slice(1, -1); michael@0: michael@0: // Get the type name and version number of this table michael@0: var tokens = description.split(" "); michael@0: this.type = tokens[0]; michael@0: var majorminor = tokens[1].split("."); michael@0: this.major = parseInt(majorminor[0]); michael@0: this.minor = parseInt(majorminor[1]); michael@0: if (isNaN(this.major) || isNaN(this.minor)) michael@0: return false; michael@0: michael@0: if (tokens.length >= 3) { michael@0: this.update = tokens[2] == "update"; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /** michael@0: * Takes a string like [name-of-table 1.1 [update]][mac=MAC] and figures out the michael@0: * type and corresponding version numbers. michael@0: * @returns true if the string could be parsed, false otherwise michael@0: */ michael@0: PROT_VersionParser.prototype.fromString = function(line) { michael@0: G_Debug(this, "Calling fromString with line: " + line); michael@0: if (line[0] != '[' || line.slice(-1) != ']') michael@0: return false; michael@0: michael@0: // There could be two [][], so take care of it michael@0: var secondBracket = line.indexOf('[', 1); michael@0: var firstPart = null; michael@0: var secondPart = null; michael@0: michael@0: if (secondBracket != -1) { michael@0: firstPart = line.substring(0, secondBracket); michael@0: secondPart = line.substring(secondBracket); michael@0: G_Debug(this, "First part: " + firstPart + " Second part: " + secondPart); michael@0: } else { michael@0: firstPart = line; michael@0: G_Debug(this, "Old format: " + firstPart); michael@0: } michael@0: michael@0: if (!this.processOldFormat_(firstPart)) michael@0: return false; michael@0: michael@0: if (secondPart && !this.processOptTokens_(secondPart)) michael@0: return false; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /** michael@0: * Process optional tokens michael@0: * michael@0: * @param line A string [token1=val1 token2=val2...] michael@0: * @returns true if the string could be parsed, false otherwise michael@0: */ michael@0: PROT_VersionParser.prototype.processOptTokens_ = function(line) { michael@0: if (line[0] != '[' || line.slice(-1) != ']') michael@0: return false; michael@0: var description = line.slice(1, -1); michael@0: // Get the type name and version number of this table michael@0: var tokens = description.split(" "); michael@0: michael@0: for (var i = 0; i < tokens.length; i++) { michael@0: G_Debug(this, "Processing optional token: " + tokens[i]); michael@0: var tokenparts = tokens[i].split("="); michael@0: switch(tokenparts[0]){ michael@0: case "mac": michael@0: this.mac = true; michael@0: if (tokenparts.length < 2) { michael@0: G_Debug(this, "Found mac flag but not mac value!"); michael@0: return false; michael@0: } michael@0: // The mac value may have "=" in it, so we can't just use tokenparts[1]. michael@0: // Instead, just take the rest of tokens[i] after the first "=" michael@0: this.macval = tokens[i].substr(tokens[i].indexOf("=")+1); michael@0: break; michael@0: default: michael@0: G_Debug(this, "Found unrecognized token: " + tokenparts[0]); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: function TEST_PROT_WireFormat() { michael@0: if (G_GDEBUG) { michael@0: var z = "versionparser UNITTEST"; michael@0: G_Debug(z, "Starting"); michael@0: michael@0: var vp = new PROT_VersionParser("dummy"); michael@0: G_Assert(z, vp.fromString("[foo-bar-url 1.234]"), michael@0: "failed to parse old format"); michael@0: G_Assert(z, "foo-bar-url" == vp.type, "failed to parse type"); michael@0: G_Assert(z, "1" == vp.major, "failed to parse major"); michael@0: G_Assert(z, "234" == vp.minor, "failed to parse minor"); michael@0: michael@0: vp = new PROT_VersionParser("dummy"); michael@0: G_Assert(z, vp.fromString("[foo-bar-url 1.234][mac=567]"), michael@0: "failed to parse new format"); michael@0: G_Assert(z, "foo-bar-url" == vp.type, "failed to parse type"); michael@0: G_Assert(z, "1" == vp.major, "failed to parse major"); michael@0: G_Assert(z, "234" == vp.minor, "failed to parse minor"); michael@0: G_Assert(z, true == vp.mac, "failed to parse mac"); michael@0: G_Assert(z, "567" == vp.macval, "failed to parse macval"); michael@0: michael@0: G_Debug(z, "PASSED"); michael@0: } michael@0: } michael@0: #endif