Thu, 15 Jan 2015 15:59:08 +0100
Implement a real Private Browsing Mode condition by changing the API/ABI;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
michael@0 | 1 | /* run some tests on the file:// protocol handler */ |
michael@0 | 2 | |
michael@0 | 3 | const PR_RDONLY = 0x1; // see prio.h |
michael@0 | 4 | |
michael@0 | 5 | const special_type = "application/x-our-special-type"; |
michael@0 | 6 | |
michael@0 | 7 | [ |
michael@0 | 8 | test_read_file, |
michael@0 | 9 | test_read_dir_1, |
michael@0 | 10 | test_read_dir_2, |
michael@0 | 11 | test_upload_file, |
michael@0 | 12 | test_load_replace, |
michael@0 | 13 | do_test_finished |
michael@0 | 14 | ].forEach(add_test); |
michael@0 | 15 | |
michael@0 | 16 | function getFile(key) { |
michael@0 | 17 | var dirSvc = Components.classes["@mozilla.org/file/directory_service;1"] |
michael@0 | 18 | .getService(Components.interfaces.nsIProperties); |
michael@0 | 19 | return dirSvc.get(key, Components.interfaces.nsILocalFile); |
michael@0 | 20 | } |
michael@0 | 21 | |
michael@0 | 22 | function new_file_input_stream(file, buffered) { |
michael@0 | 23 | var stream = |
michael@0 | 24 | Cc["@mozilla.org/network/file-input-stream;1"]. |
michael@0 | 25 | createInstance(Ci.nsIFileInputStream); |
michael@0 | 26 | stream.init(file, PR_RDONLY, 0, 0); |
michael@0 | 27 | if (!buffered) |
michael@0 | 28 | return stream; |
michael@0 | 29 | |
michael@0 | 30 | var buffer = |
michael@0 | 31 | Cc["@mozilla.org/network/buffered-input-stream;1"]. |
michael@0 | 32 | createInstance(Ci.nsIBufferedInputStream); |
michael@0 | 33 | buffer.init(stream, 4096); |
michael@0 | 34 | return buffer; |
michael@0 | 35 | } |
michael@0 | 36 | |
michael@0 | 37 | function new_file_channel(file) { |
michael@0 | 38 | var ios = |
michael@0 | 39 | Cc["@mozilla.org/network/io-service;1"]. |
michael@0 | 40 | getService(Ci.nsIIOService); |
michael@0 | 41 | return ios.newChannelFromURI(ios.newFileURI(file)); |
michael@0 | 42 | } |
michael@0 | 43 | |
michael@0 | 44 | /* |
michael@0 | 45 | * stream listener |
michael@0 | 46 | * this listener has some additional file-specific tests, so we can't just use |
michael@0 | 47 | * ChannelListener here. |
michael@0 | 48 | */ |
michael@0 | 49 | function FileStreamListener(closure) { |
michael@0 | 50 | this._closure = closure; |
michael@0 | 51 | } |
michael@0 | 52 | FileStreamListener.prototype = { |
michael@0 | 53 | _closure: null, |
michael@0 | 54 | _buffer: "", |
michael@0 | 55 | _got_onstartrequest: false, |
michael@0 | 56 | _got_onstoprequest: false, |
michael@0 | 57 | _contentLen: -1, |
michael@0 | 58 | |
michael@0 | 59 | _isDir: function(request) { |
michael@0 | 60 | request.QueryInterface(Ci.nsIFileChannel); |
michael@0 | 61 | return request.file.isDirectory(); |
michael@0 | 62 | }, |
michael@0 | 63 | |
michael@0 | 64 | QueryInterface: function(iid) { |
michael@0 | 65 | if (iid.equals(Ci.nsIStreamListener) || |
michael@0 | 66 | iid.equals(Ci.nsIRequestObserver) || |
michael@0 | 67 | iid.equals(Ci.nsISupports)) |
michael@0 | 68 | return this; |
michael@0 | 69 | throw Cr.NS_ERROR_NO_INTERFACE; |
michael@0 | 70 | }, |
michael@0 | 71 | |
michael@0 | 72 | onStartRequest: function(request, context) { |
michael@0 | 73 | if (this._got_onstartrequest) |
michael@0 | 74 | do_throw("Got second onStartRequest event!"); |
michael@0 | 75 | this._got_onstartrequest = true; |
michael@0 | 76 | |
michael@0 | 77 | if (!this._isDir(request)) { |
michael@0 | 78 | request.QueryInterface(Ci.nsIChannel); |
michael@0 | 79 | this._contentLen = request.contentLength; |
michael@0 | 80 | if (this._contentLen == -1) |
michael@0 | 81 | do_throw("Content length is unknown in onStartRequest!"); |
michael@0 | 82 | } |
michael@0 | 83 | }, |
michael@0 | 84 | |
michael@0 | 85 | onDataAvailable: function(request, context, stream, offset, count) { |
michael@0 | 86 | if (!this._got_onstartrequest) |
michael@0 | 87 | do_throw("onDataAvailable without onStartRequest event!"); |
michael@0 | 88 | if (this._got_onstoprequest) |
michael@0 | 89 | do_throw("onDataAvailable after onStopRequest event!"); |
michael@0 | 90 | if (!request.isPending()) |
michael@0 | 91 | do_throw("request reports itself as not pending from onStartRequest!"); |
michael@0 | 92 | |
michael@0 | 93 | this._buffer = this._buffer.concat(read_stream(stream, count)); |
michael@0 | 94 | }, |
michael@0 | 95 | |
michael@0 | 96 | onStopRequest: function(request, context, status) { |
michael@0 | 97 | if (!this._got_onstartrequest) |
michael@0 | 98 | do_throw("onStopRequest without onStartRequest event!"); |
michael@0 | 99 | if (this._got_onstoprequest) |
michael@0 | 100 | do_throw("Got second onStopRequest event!"); |
michael@0 | 101 | this._got_onstoprequest = true; |
michael@0 | 102 | if (!Components.isSuccessCode(status)) |
michael@0 | 103 | do_throw("Failed to load file: " + status.toString(16)); |
michael@0 | 104 | if (status != request.status) |
michael@0 | 105 | do_throw("request.status does not match status arg to onStopRequest!"); |
michael@0 | 106 | if (request.isPending()) |
michael@0 | 107 | do_throw("request reports itself as pending from onStopRequest!"); |
michael@0 | 108 | if (this._contentLen != -1 && this._buffer.length != this._contentLen) |
michael@0 | 109 | do_throw("did not read nsIChannel.contentLength number of bytes!"); |
michael@0 | 110 | |
michael@0 | 111 | this._closure(this._buffer); |
michael@0 | 112 | } |
michael@0 | 113 | }; |
michael@0 | 114 | |
michael@0 | 115 | function test_read_file() { |
michael@0 | 116 | dump("*** test_read_file\n"); |
michael@0 | 117 | |
michael@0 | 118 | var file = do_get_file("../unit/data/test_readline6.txt"); |
michael@0 | 119 | var chan = new_file_channel(file); |
michael@0 | 120 | |
michael@0 | 121 | function on_read_complete(data) { |
michael@0 | 122 | dump("*** test_read_file.on_read_complete\n"); |
michael@0 | 123 | |
michael@0 | 124 | // bug 326693 |
michael@0 | 125 | if (chan.contentType != special_type) |
michael@0 | 126 | do_throw("Type mismatch! Is <" + chan.contentType + ">, should be <" + |
michael@0 | 127 | special_type + ">") |
michael@0 | 128 | |
michael@0 | 129 | /* read completed successfully. now read data directly from file, |
michael@0 | 130 | and compare the result. */ |
michael@0 | 131 | var stream = new_file_input_stream(file, false); |
michael@0 | 132 | var result = read_stream(stream, stream.available()); |
michael@0 | 133 | if (result != data) |
michael@0 | 134 | do_throw("Stream contents do not match with direct read!"); |
michael@0 | 135 | run_next_test(); |
michael@0 | 136 | } |
michael@0 | 137 | |
michael@0 | 138 | chan.contentType = special_type; |
michael@0 | 139 | chan.asyncOpen(new FileStreamListener(on_read_complete), null); |
michael@0 | 140 | } |
michael@0 | 141 | |
michael@0 | 142 | function do_test_read_dir(set_type, expected_type) { |
michael@0 | 143 | dump("*** test_read_dir(" + set_type + ", " + expected_type + ")\n"); |
michael@0 | 144 | |
michael@0 | 145 | var file = do_get_tempdir(); |
michael@0 | 146 | var chan = new_file_channel(file); |
michael@0 | 147 | |
michael@0 | 148 | function on_read_complete(data) { |
michael@0 | 149 | dump("*** test_read_dir.on_read_complete(" + set_type + ", " + expected_type + ")\n"); |
michael@0 | 150 | |
michael@0 | 151 | // bug 326693 |
michael@0 | 152 | if (chan.contentType != expected_type) |
michael@0 | 153 | do_throw("Type mismatch! Is <" + chan.contentType + ">, should be <" + |
michael@0 | 154 | expected_type + ">") |
michael@0 | 155 | |
michael@0 | 156 | run_next_test(); |
michael@0 | 157 | } |
michael@0 | 158 | |
michael@0 | 159 | if (set_type) |
michael@0 | 160 | chan.contentType = expected_type; |
michael@0 | 161 | chan.asyncOpen(new FileStreamListener(on_read_complete), null); |
michael@0 | 162 | } |
michael@0 | 163 | |
michael@0 | 164 | function test_read_dir_1() { |
michael@0 | 165 | return do_test_read_dir(false, "application/http-index-format"); |
michael@0 | 166 | } |
michael@0 | 167 | |
michael@0 | 168 | function test_read_dir_2() { |
michael@0 | 169 | return do_test_read_dir(true, special_type); |
michael@0 | 170 | } |
michael@0 | 171 | |
michael@0 | 172 | function test_upload_file() { |
michael@0 | 173 | dump("*** test_upload_file\n"); |
michael@0 | 174 | |
michael@0 | 175 | var file = do_get_file("../unit/data/test_readline6.txt"); // file to upload |
michael@0 | 176 | var dest = do_get_tempdir(); // file upload destination |
michael@0 | 177 | dest.append("junk.dat"); |
michael@0 | 178 | dest.createUnique(dest.NORMAL_FILE_TYPE, 0600); |
michael@0 | 179 | |
michael@0 | 180 | var uploadstream = new_file_input_stream(file, true); |
michael@0 | 181 | |
michael@0 | 182 | var chan = new_file_channel(dest); |
michael@0 | 183 | chan.QueryInterface(Ci.nsIUploadChannel); |
michael@0 | 184 | chan.setUploadStream(uploadstream, "", file.fileSize); |
michael@0 | 185 | |
michael@0 | 186 | function on_upload_complete(data) { |
michael@0 | 187 | dump("*** test_upload_file.on_upload_complete\n"); |
michael@0 | 188 | |
michael@0 | 189 | // bug 326693 |
michael@0 | 190 | if (chan.contentType != special_type) |
michael@0 | 191 | do_throw("Type mismatch! Is <" + chan.contentType + ">, should be <" + |
michael@0 | 192 | special_type + ">") |
michael@0 | 193 | |
michael@0 | 194 | /* upload of file completed successfully. */ |
michael@0 | 195 | if (data.length != 0) |
michael@0 | 196 | do_throw("Upload resulted in data!"); |
michael@0 | 197 | |
michael@0 | 198 | var oldstream = new_file_input_stream(file, false); |
michael@0 | 199 | var newstream = new_file_input_stream(dest, false); |
michael@0 | 200 | var olddata = read_stream(oldstream, oldstream.available()); |
michael@0 | 201 | var newdata = read_stream(newstream, newstream.available()); |
michael@0 | 202 | if (olddata != newdata) |
michael@0 | 203 | do_throw("Stream contents do not match after file copy!"); |
michael@0 | 204 | oldstream.close(); |
michael@0 | 205 | newstream.close(); |
michael@0 | 206 | |
michael@0 | 207 | /* cleanup... also ensures that the destination file is not in |
michael@0 | 208 | use when OnStopRequest is called. */ |
michael@0 | 209 | try { |
michael@0 | 210 | dest.remove(false); |
michael@0 | 211 | } catch (e) { |
michael@0 | 212 | dump(e + "\n"); |
michael@0 | 213 | do_throw("Unable to remove uploaded file!\n"); |
michael@0 | 214 | } |
michael@0 | 215 | |
michael@0 | 216 | run_next_test(); |
michael@0 | 217 | } |
michael@0 | 218 | |
michael@0 | 219 | chan.contentType = special_type; |
michael@0 | 220 | chan.asyncOpen(new FileStreamListener(on_upload_complete), null); |
michael@0 | 221 | } |
michael@0 | 222 | |
michael@0 | 223 | function test_load_replace() { |
michael@0 | 224 | // lnk files should resolve to their targets |
michael@0 | 225 | const isWindows = ("@mozilla.org/windows-registry-key;1" in Cc); |
michael@0 | 226 | if (isWindows) { |
michael@0 | 227 | dump("*** test_load_replace\n"); |
michael@0 | 228 | file = do_get_file("data/system_root.lnk", false); |
michael@0 | 229 | var chan = new_file_channel(file); |
michael@0 | 230 | |
michael@0 | 231 | // The LOAD_REPLACE flag should be set |
michael@0 | 232 | do_check_eq(chan.loadFlags & chan.LOAD_REPLACE, chan.LOAD_REPLACE); |
michael@0 | 233 | |
michael@0 | 234 | // The original URI path should differ from the URI path |
michael@0 | 235 | do_check_neq(chan.URI.path, chan.originalURI.path); |
michael@0 | 236 | |
michael@0 | 237 | // The original URI path should be the same as the lnk file path |
michael@0 | 238 | var ios = Cc["@mozilla.org/network/io-service;1"]. |
michael@0 | 239 | getService(Ci.nsIIOService); |
michael@0 | 240 | do_check_eq(chan.originalURI.path, ios.newFileURI(file).path); |
michael@0 | 241 | } |
michael@0 | 242 | run_next_test(); |
michael@0 | 243 | } |
michael@0 | 244 | |
michael@0 | 245 | function run_test() { |
michael@0 | 246 | run_next_test(); |
michael@0 | 247 | } |