Sat, 03 Jan 2015 20:18:00 +0100
Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.
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 file, |
michael@0 | 3 | * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | "use strict"; |
michael@0 | 6 | |
michael@0 | 7 | const Cu = Components.utils; |
michael@0 | 8 | const Cc = Components.classes; |
michael@0 | 9 | const Ci = Components.interfaces; |
michael@0 | 10 | const CC = Components.Constructor; |
michael@0 | 11 | |
michael@0 | 12 | this.EXPORTED_SYMBOLS = ["OfflineCacheInstaller"]; |
michael@0 | 13 | |
michael@0 | 14 | Cu.import("resource://gre/modules/Services.jsm"); |
michael@0 | 15 | Cu.import("resource://gre/modules/AppsUtils.jsm"); |
michael@0 | 16 | Cu.import("resource://gre/modules/NetUtil.jsm"); |
michael@0 | 17 | |
michael@0 | 18 | let Namespace = CC('@mozilla.org/network/application-cache-namespace;1', |
michael@0 | 19 | 'nsIApplicationCacheNamespace', |
michael@0 | 20 | 'init'); |
michael@0 | 21 | let makeFile = CC('@mozilla.org/file/local;1', |
michael@0 | 22 | 'nsIFile', |
michael@0 | 23 | 'initWithPath'); |
michael@0 | 24 | let MutableArray = CC('@mozilla.org/array;1', 'nsIMutableArray'); |
michael@0 | 25 | |
michael@0 | 26 | const nsICache = Ci.nsICache; |
michael@0 | 27 | const nsIApplicationCache = Ci.nsIApplicationCache; |
michael@0 | 28 | const applicationCacheService = |
michael@0 | 29 | Cc['@mozilla.org/network/application-cache-service;1'] |
michael@0 | 30 | .getService(Ci.nsIApplicationCacheService); |
michael@0 | 31 | |
michael@0 | 32 | |
michael@0 | 33 | function debug(aMsg) { |
michael@0 | 34 | //dump("-*-*- OfflineCacheInstaller.jsm : " + aMsg + "\n"); |
michael@0 | 35 | } |
michael@0 | 36 | |
michael@0 | 37 | |
michael@0 | 38 | function enableOfflineCacheForApp(origin, appId) { |
michael@0 | 39 | let principal = Services.scriptSecurityManager.getAppCodebasePrincipal( |
michael@0 | 40 | origin, appId, false); |
michael@0 | 41 | Services.perms.addFromPrincipal(principal, 'offline-app', |
michael@0 | 42 | Ci.nsIPermissionManager.ALLOW_ACTION); |
michael@0 | 43 | // Prevent cache from being evicted: |
michael@0 | 44 | Services.perms.addFromPrincipal(principal, 'pin-app', |
michael@0 | 45 | Ci.nsIPermissionManager.ALLOW_ACTION); |
michael@0 | 46 | } |
michael@0 | 47 | |
michael@0 | 48 | |
michael@0 | 49 | function storeCache(applicationCache, url, file, itemType) { |
michael@0 | 50 | let session = Services.cache.createSession(applicationCache.clientID, |
michael@0 | 51 | nsICache.STORE_OFFLINE, true); |
michael@0 | 52 | session.asyncOpenCacheEntry(url, nsICache.ACCESS_WRITE, { |
michael@0 | 53 | onCacheEntryAvailable: function (cacheEntry, accessGranted, status) { |
michael@0 | 54 | cacheEntry.setMetaDataElement('request-method', 'GET'); |
michael@0 | 55 | cacheEntry.setMetaDataElement('response-head', 'HTTP/1.1 200 OK\r\n'); |
michael@0 | 56 | |
michael@0 | 57 | let outputStream = cacheEntry.openOutputStream(0); |
michael@0 | 58 | |
michael@0 | 59 | // Input-Output stream machinery in order to push nsIFile content into cache |
michael@0 | 60 | let inputStream = Cc['@mozilla.org/network/file-input-stream;1'] |
michael@0 | 61 | .createInstance(Ci.nsIFileInputStream); |
michael@0 | 62 | inputStream.init(file, 1, -1, null); |
michael@0 | 63 | let bufferedOutputStream = Cc['@mozilla.org/network/buffered-output-stream;1'] |
michael@0 | 64 | .createInstance(Ci.nsIBufferedOutputStream); |
michael@0 | 65 | bufferedOutputStream.init(outputStream, 1024); |
michael@0 | 66 | bufferedOutputStream.writeFrom(inputStream, inputStream.available()); |
michael@0 | 67 | bufferedOutputStream.flush(); |
michael@0 | 68 | bufferedOutputStream.close(); |
michael@0 | 69 | inputStream.close(); |
michael@0 | 70 | |
michael@0 | 71 | cacheEntry.markValid(); |
michael@0 | 72 | debug (file.path + ' -> ' + url + ' (' + itemType + ')'); |
michael@0 | 73 | applicationCache.markEntry(url, itemType); |
michael@0 | 74 | cacheEntry.close(); |
michael@0 | 75 | } |
michael@0 | 76 | }); |
michael@0 | 77 | } |
michael@0 | 78 | |
michael@0 | 79 | function readFile(aFile, aCallback) { |
michael@0 | 80 | let channel = NetUtil.newChannel(aFile); |
michael@0 | 81 | channel.contentType = "plain/text"; |
michael@0 | 82 | NetUtil.asyncFetch(channel, function(aStream, aResult) { |
michael@0 | 83 | if (!Components.isSuccessCode(aResult)) { |
michael@0 | 84 | Cu.reportError("OfflineCacheInstaller: Could not read file " + aFile.path); |
michael@0 | 85 | if (aCallback) |
michael@0 | 86 | aCallback(null); |
michael@0 | 87 | return; |
michael@0 | 88 | } |
michael@0 | 89 | |
michael@0 | 90 | // Obtain a converter to read from a UTF-8 encoded input stream. |
michael@0 | 91 | let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"] |
michael@0 | 92 | .createInstance(Ci.nsIScriptableUnicodeConverter); |
michael@0 | 93 | converter.charset = "UTF-8"; |
michael@0 | 94 | |
michael@0 | 95 | let data = NetUtil.readInputStreamToString(aStream, |
michael@0 | 96 | aStream.available()); |
michael@0 | 97 | aCallback(converter.ConvertToUnicode(data)); |
michael@0 | 98 | }); |
michael@0 | 99 | } |
michael@0 | 100 | |
michael@0 | 101 | function parseCacheLine(app, urls, line) { |
michael@0 | 102 | try { |
michael@0 | 103 | let url = Services.io.newURI(line, null, app.origin); |
michael@0 | 104 | urls.push(url.spec); |
michael@0 | 105 | } catch(e) { |
michael@0 | 106 | throw new Error('Unable to parse cache line: ' + line + '(' + e + ')'); |
michael@0 | 107 | } |
michael@0 | 108 | } |
michael@0 | 109 | |
michael@0 | 110 | function parseFallbackLine(app, urls, namespaces, fallbacks, line) { |
michael@0 | 111 | let split = line.split(/[ \t]+/); |
michael@0 | 112 | if (split.length != 2) { |
michael@0 | 113 | throw new Error('Should be made of two URLs seperated with spaces') |
michael@0 | 114 | } |
michael@0 | 115 | let type = Ci.nsIApplicationCacheNamespace.NAMESPACE_FALLBACK; |
michael@0 | 116 | let [ namespace, fallback ] = split; |
michael@0 | 117 | |
michael@0 | 118 | // Prepend webapp origin in case of absolute path |
michael@0 | 119 | try { |
michael@0 | 120 | namespace = Services.io.newURI(namespace, null, app.origin).spec; |
michael@0 | 121 | fallback = Services.io.newURI(fallback, null, app.origin).spec; |
michael@0 | 122 | } catch(e) { |
michael@0 | 123 | throw new Error('Unable to parse fallback line: ' + line + '(' + e + ')'); |
michael@0 | 124 | } |
michael@0 | 125 | |
michael@0 | 126 | namespaces.push([type, namespace, fallback]); |
michael@0 | 127 | fallbacks.push(fallback); |
michael@0 | 128 | urls.push(fallback); |
michael@0 | 129 | } |
michael@0 | 130 | |
michael@0 | 131 | function parseNetworkLine(namespaces, line) { |
michael@0 | 132 | let type = Ci.nsIApplicationCacheNamespace.NAMESPACE_BYPASS; |
michael@0 | 133 | if (line[0] == '*' && (line.length == 1 || line[1] == ' ' |
michael@0 | 134 | || line[1] == '\t')) { |
michael@0 | 135 | namespaces.push([type, '', '']); |
michael@0 | 136 | } else { |
michael@0 | 137 | namespaces.push([type, namespace, '']); |
michael@0 | 138 | } |
michael@0 | 139 | } |
michael@0 | 140 | |
michael@0 | 141 | function parseAppCache(app, path, content) { |
michael@0 | 142 | let lines = content.split(/\r?\n/); |
michael@0 | 143 | |
michael@0 | 144 | let urls = []; |
michael@0 | 145 | let namespaces = []; |
michael@0 | 146 | let fallbacks = []; |
michael@0 | 147 | |
michael@0 | 148 | let currentSection = 'CACHE'; |
michael@0 | 149 | for (let i = 0; i < lines.length; i++) { |
michael@0 | 150 | let line = lines[i]; |
michael@0 | 151 | |
michael@0 | 152 | // Ignore comments |
michael@0 | 153 | if (/^#/.test(line) || !line.length) |
michael@0 | 154 | continue; |
michael@0 | 155 | |
michael@0 | 156 | // Process section headers |
michael@0 | 157 | if (line == 'CACHE MANIFEST') |
michael@0 | 158 | continue; |
michael@0 | 159 | if (line == 'CACHE:') { |
michael@0 | 160 | currentSection = 'CACHE'; |
michael@0 | 161 | continue; |
michael@0 | 162 | } else if (line == 'NETWORK:') { |
michael@0 | 163 | currentSection = 'NETWORK'; |
michael@0 | 164 | continue; |
michael@0 | 165 | } else if (line == 'FALLBACK:') { |
michael@0 | 166 | currentSection = 'FALLBACK'; |
michael@0 | 167 | continue; |
michael@0 | 168 | } |
michael@0 | 169 | |
michael@0 | 170 | // Process cache, network and fallback rules |
michael@0 | 171 | try { |
michael@0 | 172 | if (currentSection == 'CACHE') { |
michael@0 | 173 | parseCacheLine(app, urls, line); |
michael@0 | 174 | } else if (currentSection == 'NETWORK') { |
michael@0 | 175 | parseNetworkLine(namespaces, line); |
michael@0 | 176 | } else if (currentSection == 'FALLBACK') { |
michael@0 | 177 | parseFallbackLine(app, urls, namespaces, fallbacks, line); |
michael@0 | 178 | } |
michael@0 | 179 | } catch(e) { |
michael@0 | 180 | throw new Error('Invalid ' + currentSection + ' line in appcache ' + |
michael@0 | 181 | 'manifest:\n' + e.message + |
michael@0 | 182 | '\nFrom: ' + path + |
michael@0 | 183 | '\nLine ' + i + ': ' + line); |
michael@0 | 184 | } |
michael@0 | 185 | } |
michael@0 | 186 | |
michael@0 | 187 | return { |
michael@0 | 188 | urls: urls, |
michael@0 | 189 | namespaces: namespaces, |
michael@0 | 190 | fallbacks: fallbacks |
michael@0 | 191 | }; |
michael@0 | 192 | } |
michael@0 | 193 | |
michael@0 | 194 | function installCache(app) { |
michael@0 | 195 | if (!app.cachePath) { |
michael@0 | 196 | return; |
michael@0 | 197 | } |
michael@0 | 198 | |
michael@0 | 199 | let cacheDir = makeFile(app.cachePath) |
michael@0 | 200 | cacheDir.append(app.appId); |
michael@0 | 201 | cacheDir.append('cache'); |
michael@0 | 202 | if (!cacheDir.exists()) |
michael@0 | 203 | return; |
michael@0 | 204 | |
michael@0 | 205 | let cacheManifest = cacheDir.clone(); |
michael@0 | 206 | cacheManifest.append('manifest.appcache'); |
michael@0 | 207 | if (!cacheManifest.exists()) |
michael@0 | 208 | return; |
michael@0 | 209 | |
michael@0 | 210 | enableOfflineCacheForApp(app.origin, app.localId); |
michael@0 | 211 | |
michael@0 | 212 | // Get the url for the manifest. |
michael@0 | 213 | let appcacheURL = app.appcache_path; |
michael@0 | 214 | |
michael@0 | 215 | // The group ID contains application id and 'f' for not being hosted in |
michael@0 | 216 | // a browser element, but a mozbrowser iframe. |
michael@0 | 217 | // See netwerk/cache/nsDiskCacheDeviceSQL.cpp: AppendJARIdentifier |
michael@0 | 218 | let groupID = appcacheURL + '#' + app.localId+ '+f'; |
michael@0 | 219 | let applicationCache = applicationCacheService.createApplicationCache(groupID); |
michael@0 | 220 | applicationCache.activate(); |
michael@0 | 221 | |
michael@0 | 222 | readFile(cacheManifest, function readAppCache(content) { |
michael@0 | 223 | let entries = parseAppCache(app, cacheManifest.path, content); |
michael@0 | 224 | |
michael@0 | 225 | entries.urls.forEach(function processCachedFile(url) { |
michael@0 | 226 | // Get this nsIFile from cache folder for this URL |
michael@0 | 227 | // We have absolute urls, so remove the origin part to locate the |
michael@0 | 228 | // files. |
michael@0 | 229 | let path = url.replace(app.origin.spec, ''); |
michael@0 | 230 | let file = cacheDir.clone(); |
michael@0 | 231 | let paths = path.split('/'); |
michael@0 | 232 | paths.forEach(file.append); |
michael@0 | 233 | |
michael@0 | 234 | if (!file.exists()) { |
michael@0 | 235 | let msg = 'File ' + file.path + ' exists in the manifest but does ' + |
michael@0 | 236 | 'not points to a real file.'; |
michael@0 | 237 | throw new Error(msg); |
michael@0 | 238 | } |
michael@0 | 239 | |
michael@0 | 240 | let itemType = nsIApplicationCache.ITEM_EXPLICIT; |
michael@0 | 241 | storeCache(applicationCache, url, file, itemType); |
michael@0 | 242 | }); |
michael@0 | 243 | |
michael@0 | 244 | let array = new MutableArray(); |
michael@0 | 245 | entries.namespaces.forEach(function processNamespace([type, spec, data]) { |
michael@0 | 246 | debug('add namespace: ' + type + ' - ' + spec + ' - ' + data + '\n'); |
michael@0 | 247 | array.appendElement(new Namespace(type, spec, data), false); |
michael@0 | 248 | }); |
michael@0 | 249 | applicationCache.addNamespaces(array); |
michael@0 | 250 | |
michael@0 | 251 | entries.fallbacks.forEach(function processFallback(url) { |
michael@0 | 252 | debug('add fallback: ' + url + '\n'); |
michael@0 | 253 | let type = nsIApplicationCache.ITEM_FALLBACK; |
michael@0 | 254 | applicationCache.markEntry(url, type); |
michael@0 | 255 | }); |
michael@0 | 256 | |
michael@0 | 257 | storeCache(applicationCache, appcacheURL, cacheManifest, |
michael@0 | 258 | nsIApplicationCache.ITEM_MANIFEST); |
michael@0 | 259 | }); |
michael@0 | 260 | } |
michael@0 | 261 | |
michael@0 | 262 | |
michael@0 | 263 | // Public API |
michael@0 | 264 | |
michael@0 | 265 | this.OfflineCacheInstaller = { |
michael@0 | 266 | installCache: installCache |
michael@0 | 267 | }; |
michael@0 | 268 |