Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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 | * validateManifest() warns of the following errors: |
michael@0 | 7 | * - No manifest specified in page |
michael@0 | 8 | * - Manifest is not utf-8 |
michael@0 | 9 | * - Manifest mimetype not text/cache-manifest |
michael@0 | 10 | * - Manifest does not begin with "CACHE MANIFEST" |
michael@0 | 11 | * - Page modified since appcache last changed |
michael@0 | 12 | * - Duplicate entries |
michael@0 | 13 | * - Conflicting entries e.g. in both CACHE and NETWORK sections or in cache |
michael@0 | 14 | * but blocked by FALLBACK namespace |
michael@0 | 15 | * - Detect referenced files that are not available |
michael@0 | 16 | * - Detect referenced files that have cache-control set to no-store |
michael@0 | 17 | * - Wildcards used in a section other than NETWORK |
michael@0 | 18 | * - Spaces in URI not replaced with %20 |
michael@0 | 19 | * - Completely invalid URIs |
michael@0 | 20 | * - Too many dot dot slash operators |
michael@0 | 21 | * - SETTINGS section is valid |
michael@0 | 22 | * - Invalid section name |
michael@0 | 23 | * - etc. |
michael@0 | 24 | */ |
michael@0 | 25 | |
michael@0 | 26 | "use strict"; |
michael@0 | 27 | |
michael@0 | 28 | const { classes: Cc, interfaces: Ci, utils: Cu } = Components; |
michael@0 | 29 | |
michael@0 | 30 | let { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {}); |
michael@0 | 31 | let { Services } = Cu.import("resource://gre/modules/Services.jsm", {}); |
michael@0 | 32 | let { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {}); |
michael@0 | 33 | |
michael@0 | 34 | this.EXPORTED_SYMBOLS = ["AppCacheUtils"]; |
michael@0 | 35 | |
michael@0 | 36 | function AppCacheUtils(documentOrUri) { |
michael@0 | 37 | this._parseManifest = this._parseManifest.bind(this); |
michael@0 | 38 | |
michael@0 | 39 | if (documentOrUri) { |
michael@0 | 40 | if (typeof documentOrUri == "string") { |
michael@0 | 41 | this.uri = documentOrUri; |
michael@0 | 42 | } |
michael@0 | 43 | if (/HTMLDocument/.test(documentOrUri.toString())) { |
michael@0 | 44 | this.doc = documentOrUri; |
michael@0 | 45 | } |
michael@0 | 46 | } |
michael@0 | 47 | } |
michael@0 | 48 | |
michael@0 | 49 | AppCacheUtils.prototype = { |
michael@0 | 50 | get cachePath() { |
michael@0 | 51 | return ""; |
michael@0 | 52 | }, |
michael@0 | 53 | |
michael@0 | 54 | validateManifest: function ACU_validateManifest() { |
michael@0 | 55 | let deferred = promise.defer(); |
michael@0 | 56 | this.errors = []; |
michael@0 | 57 | // Check for missing manifest. |
michael@0 | 58 | this._getManifestURI().then(manifestURI => { |
michael@0 | 59 | this.manifestURI = manifestURI; |
michael@0 | 60 | |
michael@0 | 61 | if (!this.manifestURI) { |
michael@0 | 62 | this._addError(0, "noManifest"); |
michael@0 | 63 | deferred.resolve(this.errors); |
michael@0 | 64 | } |
michael@0 | 65 | |
michael@0 | 66 | this._getURIInfo(this.manifestURI).then(uriInfo => { |
michael@0 | 67 | this._parseManifest(uriInfo).then(() => { |
michael@0 | 68 | // Sort errors by line number. |
michael@0 | 69 | this.errors.sort(function(a, b) { |
michael@0 | 70 | return a.line - b.line; |
michael@0 | 71 | }); |
michael@0 | 72 | deferred.resolve(this.errors); |
michael@0 | 73 | }); |
michael@0 | 74 | }); |
michael@0 | 75 | }); |
michael@0 | 76 | |
michael@0 | 77 | return deferred.promise; |
michael@0 | 78 | }, |
michael@0 | 79 | |
michael@0 | 80 | _parseManifest: function ACU__parseManifest(uriInfo) { |
michael@0 | 81 | let deferred = promise.defer(); |
michael@0 | 82 | let manifestName = uriInfo.name; |
michael@0 | 83 | let manifestLastModified = new Date(uriInfo.responseHeaders["Last-Modified"]); |
michael@0 | 84 | |
michael@0 | 85 | if (uriInfo.charset.toLowerCase() != "utf-8") { |
michael@0 | 86 | this._addError(0, "notUTF8", uriInfo.charset); |
michael@0 | 87 | } |
michael@0 | 88 | |
michael@0 | 89 | if (uriInfo.mimeType != "text/cache-manifest") { |
michael@0 | 90 | this._addError(0, "badMimeType", uriInfo.mimeType); |
michael@0 | 91 | } |
michael@0 | 92 | |
michael@0 | 93 | let parser = new ManifestParser(uriInfo.text, this.manifestURI); |
michael@0 | 94 | let parsed = parser.parse(); |
michael@0 | 95 | |
michael@0 | 96 | if (parsed.errors.length > 0) { |
michael@0 | 97 | this.errors.push.apply(this.errors, parsed.errors); |
michael@0 | 98 | } |
michael@0 | 99 | |
michael@0 | 100 | // Check for duplicate entries. |
michael@0 | 101 | let dupes = {}; |
michael@0 | 102 | for (let parsedUri of parsed.uris) { |
michael@0 | 103 | dupes[parsedUri.uri] = dupes[parsedUri.uri] || []; |
michael@0 | 104 | dupes[parsedUri.uri].push({ |
michael@0 | 105 | line: parsedUri.line, |
michael@0 | 106 | section: parsedUri.section, |
michael@0 | 107 | original: parsedUri.original |
michael@0 | 108 | }); |
michael@0 | 109 | } |
michael@0 | 110 | for (let [uri, value] of Iterator(dupes)) { |
michael@0 | 111 | if (value.length > 1) { |
michael@0 | 112 | this._addError(0, "duplicateURI", uri, JSON.stringify(value)); |
michael@0 | 113 | } |
michael@0 | 114 | } |
michael@0 | 115 | |
michael@0 | 116 | // Loop through network entries making sure that fallback and cache don't |
michael@0 | 117 | // contain uris starting with the network uri. |
michael@0 | 118 | for (let neturi of parsed.uris) { |
michael@0 | 119 | if (neturi.section == "NETWORK") { |
michael@0 | 120 | for (let parsedUri of parsed.uris) { |
michael@0 | 121 | if (parsedUri.uri.startsWith(neturi.uri)) { |
michael@0 | 122 | this._addError(neturi.line, "networkBlocksURI", neturi.line, |
michael@0 | 123 | neturi.original, parsedUri.line, parsedUri.original, |
michael@0 | 124 | parsedUri.section); |
michael@0 | 125 | } |
michael@0 | 126 | } |
michael@0 | 127 | } |
michael@0 | 128 | } |
michael@0 | 129 | |
michael@0 | 130 | // Loop through fallback entries making sure that fallback and cache don't |
michael@0 | 131 | // contain uris starting with the network uri. |
michael@0 | 132 | for (let fb of parsed.fallbacks) { |
michael@0 | 133 | for (let parsedUri of parsed.uris) { |
michael@0 | 134 | if (parsedUri.uri.startsWith(fb.namespace)) { |
michael@0 | 135 | this._addError(fb.line, "fallbackBlocksURI", fb.line, |
michael@0 | 136 | fb.original, parsedUri.line, parsedUri.original, |
michael@0 | 137 | parsedUri.section); |
michael@0 | 138 | } |
michael@0 | 139 | } |
michael@0 | 140 | } |
michael@0 | 141 | |
michael@0 | 142 | // Check that all resources exist and that their cach-control headers are |
michael@0 | 143 | // not set to no-store. |
michael@0 | 144 | let current = -1; |
michael@0 | 145 | for (let i = 0, len = parsed.uris.length; i < len; i++) { |
michael@0 | 146 | let parsedUri = parsed.uris[i]; |
michael@0 | 147 | this._getURIInfo(parsedUri.uri).then(uriInfo => { |
michael@0 | 148 | current++; |
michael@0 | 149 | |
michael@0 | 150 | if (uriInfo.success) { |
michael@0 | 151 | // Check that the resource was not modified after the manifest was last |
michael@0 | 152 | // modified. If it was then the manifest file should be refreshed. |
michael@0 | 153 | let resourceLastModified = |
michael@0 | 154 | new Date(uriInfo.responseHeaders["Last-Modified"]); |
michael@0 | 155 | |
michael@0 | 156 | if (manifestLastModified < resourceLastModified) { |
michael@0 | 157 | this._addError(parsedUri.line, "fileChangedButNotManifest", |
michael@0 | 158 | uriInfo.name, manifestName, parsedUri.line); |
michael@0 | 159 | } |
michael@0 | 160 | |
michael@0 | 161 | // If cache-control: no-store the file will not be added to the |
michael@0 | 162 | // appCache. |
michael@0 | 163 | if (uriInfo.nocache) { |
michael@0 | 164 | this._addError(parsedUri.line, "cacheControlNoStore", |
michael@0 | 165 | parsedUri.original, parsedUri.line); |
michael@0 | 166 | } |
michael@0 | 167 | } else { |
michael@0 | 168 | this._addError(parsedUri.line, "notAvailable", |
michael@0 | 169 | parsedUri.original, parsedUri.line); |
michael@0 | 170 | } |
michael@0 | 171 | |
michael@0 | 172 | if (current == len - 1) { |
michael@0 | 173 | deferred.resolve(); |
michael@0 | 174 | } |
michael@0 | 175 | }); |
michael@0 | 176 | } |
michael@0 | 177 | |
michael@0 | 178 | return deferred.promise; |
michael@0 | 179 | }, |
michael@0 | 180 | |
michael@0 | 181 | _getURIInfo: function ACU__getURIInfo(uri) { |
michael@0 | 182 | let inputStream = Cc["@mozilla.org/scriptableinputstream;1"] |
michael@0 | 183 | .createInstance(Ci.nsIScriptableInputStream); |
michael@0 | 184 | let deferred = promise.defer(); |
michael@0 | 185 | let channelCharset = ""; |
michael@0 | 186 | let buffer = ""; |
michael@0 | 187 | let channel = Services.io.newChannel(uri, null, null); |
michael@0 | 188 | |
michael@0 | 189 | // Avoid the cache: |
michael@0 | 190 | channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE; |
michael@0 | 191 | channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING; |
michael@0 | 192 | |
michael@0 | 193 | channel.asyncOpen({ |
michael@0 | 194 | onStartRequest: function (request, context) { |
michael@0 | 195 | // This empty method is needed in order for onDataAvailable to be |
michael@0 | 196 | // called. |
michael@0 | 197 | }, |
michael@0 | 198 | |
michael@0 | 199 | onDataAvailable: function (request, context, stream, offset, count) { |
michael@0 | 200 | request.QueryInterface(Ci.nsIHttpChannel); |
michael@0 | 201 | inputStream.init(stream); |
michael@0 | 202 | buffer = buffer.concat(inputStream.read(count)); |
michael@0 | 203 | }, |
michael@0 | 204 | |
michael@0 | 205 | onStopRequest: function onStartRequest(request, context, statusCode) { |
michael@0 | 206 | if (statusCode == 0) { |
michael@0 | 207 | request.QueryInterface(Ci.nsIHttpChannel); |
michael@0 | 208 | |
michael@0 | 209 | let result = { |
michael@0 | 210 | name: request.name, |
michael@0 | 211 | success: request.requestSucceeded, |
michael@0 | 212 | status: request.responseStatus + " - " + request.responseStatusText, |
michael@0 | 213 | charset: request.contentCharset || "utf-8", |
michael@0 | 214 | mimeType: request.contentType, |
michael@0 | 215 | contentLength: request.contentLength, |
michael@0 | 216 | nocache: request.isNoCacheResponse() || request.isNoStoreResponse(), |
michael@0 | 217 | prePath: request.URI.prePath + "/", |
michael@0 | 218 | text: buffer |
michael@0 | 219 | }; |
michael@0 | 220 | |
michael@0 | 221 | result.requestHeaders = {}; |
michael@0 | 222 | request.visitRequestHeaders(function(header, value) { |
michael@0 | 223 | result.requestHeaders[header] = value; |
michael@0 | 224 | }); |
michael@0 | 225 | |
michael@0 | 226 | result.responseHeaders = {}; |
michael@0 | 227 | request.visitResponseHeaders(function(header, value) { |
michael@0 | 228 | result.responseHeaders[header] = value; |
michael@0 | 229 | }); |
michael@0 | 230 | |
michael@0 | 231 | deferred.resolve(result); |
michael@0 | 232 | } else { |
michael@0 | 233 | deferred.resolve({ |
michael@0 | 234 | name: request.name, |
michael@0 | 235 | success: false |
michael@0 | 236 | }); |
michael@0 | 237 | } |
michael@0 | 238 | } |
michael@0 | 239 | }, null); |
michael@0 | 240 | return deferred.promise; |
michael@0 | 241 | }, |
michael@0 | 242 | |
michael@0 | 243 | listEntries: function ACU_show(searchTerm) { |
michael@0 | 244 | if (!Services.prefs.getBoolPref("browser.cache.disk.enable")) { |
michael@0 | 245 | throw new Error(l10n.GetStringFromName("cacheDisabled")); |
michael@0 | 246 | } |
michael@0 | 247 | |
michael@0 | 248 | let entries = []; |
michael@0 | 249 | |
michael@0 | 250 | Services.cache.visitEntries({ |
michael@0 | 251 | visitDevice: function(deviceID, deviceInfo) { |
michael@0 | 252 | return true; |
michael@0 | 253 | }, |
michael@0 | 254 | |
michael@0 | 255 | visitEntry: function(deviceID, entryInfo) { |
michael@0 | 256 | if (entryInfo.deviceID == "offline") { |
michael@0 | 257 | let entry = {}; |
michael@0 | 258 | let lowerKey = entryInfo.key.toLowerCase(); |
michael@0 | 259 | |
michael@0 | 260 | if (searchTerm && lowerKey.indexOf(searchTerm.toLowerCase()) == -1) { |
michael@0 | 261 | return true; |
michael@0 | 262 | } |
michael@0 | 263 | |
michael@0 | 264 | for (let [key, value] of Iterator(entryInfo)) { |
michael@0 | 265 | if (key == "QueryInterface") { |
michael@0 | 266 | continue; |
michael@0 | 267 | } |
michael@0 | 268 | if (key == "clientID") { |
michael@0 | 269 | entry.key = entryInfo.key; |
michael@0 | 270 | } |
michael@0 | 271 | if (key == "expirationTime" || key == "lastFetched" || key == "lastModified") { |
michael@0 | 272 | value = new Date(value * 1000); |
michael@0 | 273 | } |
michael@0 | 274 | entry[key] = value; |
michael@0 | 275 | } |
michael@0 | 276 | entries.push(entry); |
michael@0 | 277 | } |
michael@0 | 278 | return true; |
michael@0 | 279 | } |
michael@0 | 280 | }); |
michael@0 | 281 | |
michael@0 | 282 | if (entries.length == 0) { |
michael@0 | 283 | throw new Error(l10n.GetStringFromName("noResults")); |
michael@0 | 284 | } |
michael@0 | 285 | return entries; |
michael@0 | 286 | }, |
michael@0 | 287 | |
michael@0 | 288 | viewEntry: function ACU_viewEntry(key) { |
michael@0 | 289 | let uri; |
michael@0 | 290 | |
michael@0 | 291 | Services.cache.visitEntries({ |
michael@0 | 292 | visitDevice: function(deviceID, deviceInfo) { |
michael@0 | 293 | return true; |
michael@0 | 294 | }, |
michael@0 | 295 | |
michael@0 | 296 | visitEntry: function(deviceID, entryInfo) { |
michael@0 | 297 | if (entryInfo.deviceID == "offline" && entryInfo.key == key) { |
michael@0 | 298 | uri = "about:cache-entry?client=" + entryInfo.clientID + |
michael@0 | 299 | "&sb=1&key=" + entryInfo.key; |
michael@0 | 300 | return false; |
michael@0 | 301 | } |
michael@0 | 302 | return true; |
michael@0 | 303 | } |
michael@0 | 304 | }); |
michael@0 | 305 | |
michael@0 | 306 | if (uri) { |
michael@0 | 307 | let wm = Cc["@mozilla.org/appshell/window-mediator;1"] |
michael@0 | 308 | .getService(Ci.nsIWindowMediator); |
michael@0 | 309 | let win = wm.getMostRecentWindow("navigator:browser"); |
michael@0 | 310 | win.gBrowser.selectedTab = win.gBrowser.addTab(uri); |
michael@0 | 311 | } else { |
michael@0 | 312 | return l10n.GetStringFromName("entryNotFound"); |
michael@0 | 313 | } |
michael@0 | 314 | }, |
michael@0 | 315 | |
michael@0 | 316 | clearAll: function ACU_clearAll() { |
michael@0 | 317 | Services.cache.evictEntries(Ci.nsICache.STORE_OFFLINE); |
michael@0 | 318 | }, |
michael@0 | 319 | |
michael@0 | 320 | _getManifestURI: function ACU__getManifestURI() { |
michael@0 | 321 | let deferred = promise.defer(); |
michael@0 | 322 | |
michael@0 | 323 | let getURI = node => { |
michael@0 | 324 | let htmlNode = this.doc.querySelector("html[manifest]"); |
michael@0 | 325 | if (htmlNode) { |
michael@0 | 326 | let pageUri = this.doc.location ? this.doc.location.href : this.uri; |
michael@0 | 327 | let origin = pageUri.substr(0, pageUri.lastIndexOf("/") + 1); |
michael@0 | 328 | return origin + htmlNode.getAttribute("manifest"); |
michael@0 | 329 | } |
michael@0 | 330 | }; |
michael@0 | 331 | |
michael@0 | 332 | if (this.doc) { |
michael@0 | 333 | let uri = getURI(this.doc); |
michael@0 | 334 | return promise.resolve(uri); |
michael@0 | 335 | } else { |
michael@0 | 336 | this._getURIInfo(this.uri).then(uriInfo => { |
michael@0 | 337 | if (uriInfo.success) { |
michael@0 | 338 | let html = uriInfo.text; |
michael@0 | 339 | let parser = _DOMParser; |
michael@0 | 340 | this.doc = parser.parseFromString(html, "text/html"); |
michael@0 | 341 | let uri = getURI(this.doc); |
michael@0 | 342 | deferred.resolve(uri); |
michael@0 | 343 | } else { |
michael@0 | 344 | this.errors.push({ |
michael@0 | 345 | line: 0, |
michael@0 | 346 | msg: l10n.GetStringFromName("invalidURI") |
michael@0 | 347 | }); |
michael@0 | 348 | } |
michael@0 | 349 | }); |
michael@0 | 350 | } |
michael@0 | 351 | return deferred.promise; |
michael@0 | 352 | }, |
michael@0 | 353 | |
michael@0 | 354 | _addError: function ACU__addError(line, l10nString, ...params) { |
michael@0 | 355 | let msg; |
michael@0 | 356 | |
michael@0 | 357 | if (params) { |
michael@0 | 358 | msg = l10n.formatStringFromName(l10nString, params, params.length); |
michael@0 | 359 | } else { |
michael@0 | 360 | msg = l10n.GetStringFromName(l10nString); |
michael@0 | 361 | } |
michael@0 | 362 | |
michael@0 | 363 | this.errors.push({ |
michael@0 | 364 | line: line, |
michael@0 | 365 | msg: msg |
michael@0 | 366 | }); |
michael@0 | 367 | }, |
michael@0 | 368 | }; |
michael@0 | 369 | |
michael@0 | 370 | /** |
michael@0 | 371 | * We use our own custom parser because we need far more detailed information |
michael@0 | 372 | * than the system manifest parser provides. |
michael@0 | 373 | * |
michael@0 | 374 | * @param {String} manifestText |
michael@0 | 375 | * The text content of the manifest file. |
michael@0 | 376 | * @param {String} manifestURI |
michael@0 | 377 | * The URI of the manifest file. This is used in calculating the path of |
michael@0 | 378 | * relative URIs. |
michael@0 | 379 | */ |
michael@0 | 380 | function ManifestParser(manifestText, manifestURI) { |
michael@0 | 381 | this.manifestText = manifestText; |
michael@0 | 382 | this.origin = manifestURI.substr(0, manifestURI.lastIndexOf("/") + 1) |
michael@0 | 383 | .replace(" ", "%20"); |
michael@0 | 384 | } |
michael@0 | 385 | |
michael@0 | 386 | ManifestParser.prototype = { |
michael@0 | 387 | parse: function OCIMP_parse() { |
michael@0 | 388 | let lines = this.manifestText.split(/\r?\n/); |
michael@0 | 389 | let fallbacks = this.fallbacks = []; |
michael@0 | 390 | let settings = this.settings = []; |
michael@0 | 391 | let errors = this.errors = []; |
michael@0 | 392 | let uris = this.uris = []; |
michael@0 | 393 | |
michael@0 | 394 | this.currSection = "CACHE"; |
michael@0 | 395 | |
michael@0 | 396 | for (let i = 0; i < lines.length; i++) { |
michael@0 | 397 | let text = this.text = lines[i].replace(/^\s+|\s+$/g); |
michael@0 | 398 | this.currentLine = i + 1; |
michael@0 | 399 | |
michael@0 | 400 | if (i == 0 && text != "CACHE MANIFEST") { |
michael@0 | 401 | this._addError(1, "firstLineMustBeCacheManifest", 1); |
michael@0 | 402 | } |
michael@0 | 403 | |
michael@0 | 404 | // Ignore comments |
michael@0 | 405 | if (/^#/.test(text) || !text.length) { |
michael@0 | 406 | continue; |
michael@0 | 407 | } |
michael@0 | 408 | |
michael@0 | 409 | if (text == "CACHE MANIFEST") { |
michael@0 | 410 | if (this.currentLine != 1) { |
michael@0 | 411 | this._addError(this.currentLine, "cacheManifestOnlyFirstLine2", |
michael@0 | 412 | this.currentLine); |
michael@0 | 413 | } |
michael@0 | 414 | continue; |
michael@0 | 415 | } |
michael@0 | 416 | |
michael@0 | 417 | if (this._maybeUpdateSectionName()) { |
michael@0 | 418 | continue; |
michael@0 | 419 | } |
michael@0 | 420 | |
michael@0 | 421 | switch (this.currSection) { |
michael@0 | 422 | case "CACHE": |
michael@0 | 423 | case "NETWORK": |
michael@0 | 424 | this.parseLine(); |
michael@0 | 425 | break; |
michael@0 | 426 | case "FALLBACK": |
michael@0 | 427 | this.parseFallbackLine(); |
michael@0 | 428 | break; |
michael@0 | 429 | case "SETTINGS": |
michael@0 | 430 | this.parseSettingsLine(); |
michael@0 | 431 | break; |
michael@0 | 432 | } |
michael@0 | 433 | } |
michael@0 | 434 | |
michael@0 | 435 | return { |
michael@0 | 436 | uris: uris, |
michael@0 | 437 | fallbacks: fallbacks, |
michael@0 | 438 | settings: settings, |
michael@0 | 439 | errors: errors |
michael@0 | 440 | }; |
michael@0 | 441 | }, |
michael@0 | 442 | |
michael@0 | 443 | parseLine: function OCIMP_parseLine() { |
michael@0 | 444 | let text = this.text; |
michael@0 | 445 | |
michael@0 | 446 | if (text.indexOf("*") != -1) { |
michael@0 | 447 | if (this.currSection != "NETWORK" || text.length != 1) { |
michael@0 | 448 | this._addError(this.currentLine, "asteriskInWrongSection2", |
michael@0 | 449 | this.currSection, this.currentLine); |
michael@0 | 450 | return; |
michael@0 | 451 | } |
michael@0 | 452 | } |
michael@0 | 453 | |
michael@0 | 454 | if (/\s/.test(text)) { |
michael@0 | 455 | this._addError(this.currentLine, "escapeSpaces", this.currentLine); |
michael@0 | 456 | text = text.replace(/\s/g, "%20") |
michael@0 | 457 | } |
michael@0 | 458 | |
michael@0 | 459 | if (text[0] == "/") { |
michael@0 | 460 | if (text.substr(0, 4) == "/../") { |
michael@0 | 461 | this._addError(this.currentLine, "slashDotDotSlashBad", this.currentLine); |
michael@0 | 462 | } else { |
michael@0 | 463 | this.uris.push(this._wrapURI(this.origin + text.substring(1))); |
michael@0 | 464 | } |
michael@0 | 465 | } else if (text.substr(0, 2) == "./") { |
michael@0 | 466 | this.uris.push(this._wrapURI(this.origin + text.substring(2))); |
michael@0 | 467 | } else if (text.substr(0, 4) == "http") { |
michael@0 | 468 | this.uris.push(this._wrapURI(text)); |
michael@0 | 469 | } else { |
michael@0 | 470 | let origin = this.origin; |
michael@0 | 471 | let path = text; |
michael@0 | 472 | |
michael@0 | 473 | while (path.substr(0, 3) == "../" && /^https?:\/\/.*?\/.*?\//.test(origin)) { |
michael@0 | 474 | let trimIdx = origin.substr(0, origin.length - 1).lastIndexOf("/") + 1; |
michael@0 | 475 | origin = origin.substr(0, trimIdx); |
michael@0 | 476 | path = path.substr(3); |
michael@0 | 477 | } |
michael@0 | 478 | |
michael@0 | 479 | if (path.substr(0, 3) == "../") { |
michael@0 | 480 | this._addError(this.currentLine, "tooManyDotDotSlashes", this.currentLine); |
michael@0 | 481 | return; |
michael@0 | 482 | } |
michael@0 | 483 | |
michael@0 | 484 | if (/^https?:\/\//.test(path)) { |
michael@0 | 485 | this.uris.push(this._wrapURI(path)); |
michael@0 | 486 | return; |
michael@0 | 487 | } |
michael@0 | 488 | this.uris.push(this._wrapURI(origin + path)); |
michael@0 | 489 | } |
michael@0 | 490 | }, |
michael@0 | 491 | |
michael@0 | 492 | parseFallbackLine: function OCIMP_parseFallbackLine() { |
michael@0 | 493 | let split = this.text.split(/\s+/); |
michael@0 | 494 | let origURI = this.text; |
michael@0 | 495 | |
michael@0 | 496 | if (split.length != 2) { |
michael@0 | 497 | this._addError(this.currentLine, "fallbackUseSpaces", this.currentLine); |
michael@0 | 498 | return; |
michael@0 | 499 | } |
michael@0 | 500 | |
michael@0 | 501 | let [ namespace, fallback ] = split; |
michael@0 | 502 | |
michael@0 | 503 | if (namespace.indexOf("*") != -1) { |
michael@0 | 504 | this._addError(this.currentLine, "fallbackAsterisk2", this.currentLine); |
michael@0 | 505 | } |
michael@0 | 506 | |
michael@0 | 507 | if (/\s/.test(namespace)) { |
michael@0 | 508 | this._addError(this.currentLine, "escapeSpaces", this.currentLine); |
michael@0 | 509 | namespace = namespace.replace(/\s/g, "%20") |
michael@0 | 510 | } |
michael@0 | 511 | |
michael@0 | 512 | if (namespace.substr(0, 4) == "/../") { |
michael@0 | 513 | this._addError(this.currentLine, "slashDotDotSlashBad", this.currentLine); |
michael@0 | 514 | } |
michael@0 | 515 | |
michael@0 | 516 | if (namespace.substr(0, 2) == "./") { |
michael@0 | 517 | namespace = this.origin + namespace.substring(2); |
michael@0 | 518 | } |
michael@0 | 519 | |
michael@0 | 520 | if (namespace.substr(0, 4) != "http") { |
michael@0 | 521 | let origin = this.origin; |
michael@0 | 522 | let path = namespace; |
michael@0 | 523 | |
michael@0 | 524 | while (path.substr(0, 3) == "../" && /^https?:\/\/.*?\/.*?\//.test(origin)) { |
michael@0 | 525 | let trimIdx = origin.substr(0, origin.length - 1).lastIndexOf("/") + 1; |
michael@0 | 526 | origin = origin.substr(0, trimIdx); |
michael@0 | 527 | path = path.substr(3); |
michael@0 | 528 | } |
michael@0 | 529 | |
michael@0 | 530 | if (path.substr(0, 3) == "../") { |
michael@0 | 531 | this._addError(this.currentLine, "tooManyDotDotSlashes", this.currentLine); |
michael@0 | 532 | } |
michael@0 | 533 | |
michael@0 | 534 | if (/^https?:\/\//.test(path)) { |
michael@0 | 535 | namespace = path; |
michael@0 | 536 | } else { |
michael@0 | 537 | if (path[0] == "/") { |
michael@0 | 538 | path = path.substring(1); |
michael@0 | 539 | } |
michael@0 | 540 | namespace = origin + path; |
michael@0 | 541 | } |
michael@0 | 542 | } |
michael@0 | 543 | |
michael@0 | 544 | this.text = fallback; |
michael@0 | 545 | this.parseLine(); |
michael@0 | 546 | |
michael@0 | 547 | this.fallbacks.push({ |
michael@0 | 548 | line: this.currentLine, |
michael@0 | 549 | original: origURI, |
michael@0 | 550 | namespace: namespace, |
michael@0 | 551 | fallback: fallback |
michael@0 | 552 | }); |
michael@0 | 553 | }, |
michael@0 | 554 | |
michael@0 | 555 | parseSettingsLine: function OCIMP_parseSettingsLine() { |
michael@0 | 556 | let text = this.text; |
michael@0 | 557 | |
michael@0 | 558 | if (this.settings.length == 1 || !/prefer-online|fast/.test(text)) { |
michael@0 | 559 | this._addError(this.currentLine, "settingsBadValue", this.currentLine); |
michael@0 | 560 | return; |
michael@0 | 561 | } |
michael@0 | 562 | |
michael@0 | 563 | switch (text) { |
michael@0 | 564 | case "prefer-online": |
michael@0 | 565 | this.settings.push(this._wrapURI(text)); |
michael@0 | 566 | break; |
michael@0 | 567 | case "fast": |
michael@0 | 568 | this.settings.push(this._wrapURI(text)); |
michael@0 | 569 | break; |
michael@0 | 570 | } |
michael@0 | 571 | }, |
michael@0 | 572 | |
michael@0 | 573 | _wrapURI: function OCIMP__wrapURI(uri) { |
michael@0 | 574 | return { |
michael@0 | 575 | section: this.currSection, |
michael@0 | 576 | line: this.currentLine, |
michael@0 | 577 | uri: uri, |
michael@0 | 578 | original: this.text |
michael@0 | 579 | }; |
michael@0 | 580 | }, |
michael@0 | 581 | |
michael@0 | 582 | _addError: function OCIMP__addError(line, l10nString, ...params) { |
michael@0 | 583 | let msg; |
michael@0 | 584 | |
michael@0 | 585 | if (params) { |
michael@0 | 586 | msg = l10n.formatStringFromName(l10nString, params, params.length); |
michael@0 | 587 | } else { |
michael@0 | 588 | msg = l10n.GetStringFromName(l10nString); |
michael@0 | 589 | } |
michael@0 | 590 | |
michael@0 | 591 | this.errors.push({ |
michael@0 | 592 | line: line, |
michael@0 | 593 | msg: msg |
michael@0 | 594 | }); |
michael@0 | 595 | }, |
michael@0 | 596 | |
michael@0 | 597 | _maybeUpdateSectionName: function OCIMP__maybeUpdateSectionName() { |
michael@0 | 598 | let text = this.text; |
michael@0 | 599 | |
michael@0 | 600 | if (text == text.toUpperCase() && text.charAt(text.length - 1) == ":") { |
michael@0 | 601 | text = text.substr(0, text.length - 1); |
michael@0 | 602 | |
michael@0 | 603 | switch (text) { |
michael@0 | 604 | case "CACHE": |
michael@0 | 605 | case "NETWORK": |
michael@0 | 606 | case "FALLBACK": |
michael@0 | 607 | case "SETTINGS": |
michael@0 | 608 | this.currSection = text; |
michael@0 | 609 | return true; |
michael@0 | 610 | default: |
michael@0 | 611 | this._addError(this.currentLine, |
michael@0 | 612 | "invalidSectionName", text, this.currentLine); |
michael@0 | 613 | return false; |
michael@0 | 614 | } |
michael@0 | 615 | } |
michael@0 | 616 | }, |
michael@0 | 617 | }; |
michael@0 | 618 | |
michael@0 | 619 | XPCOMUtils.defineLazyGetter(this, "l10n", function() Services.strings |
michael@0 | 620 | .createBundle("chrome://browser/locale/devtools/appcacheutils.properties")); |
michael@0 | 621 | |
michael@0 | 622 | XPCOMUtils.defineLazyGetter(this, "appcacheservice", function() { |
michael@0 | 623 | return Cc["@mozilla.org/network/application-cache-service;1"] |
michael@0 | 624 | .getService(Ci.nsIApplicationCacheService); |
michael@0 | 625 | |
michael@0 | 626 | }); |
michael@0 | 627 | |
michael@0 | 628 | XPCOMUtils.defineLazyGetter(this, "_DOMParser", function() { |
michael@0 | 629 | return Cc["@mozilla.org/xmlextras/domparser;1"].createInstance(Ci.nsIDOMParser); |
michael@0 | 630 | }); |