dom/apps/src/OfflineCacheInstaller.jsm

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

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

mercurial