2 * Copyright 2004-2007 Aaron Boodman
3 * Portions copyright 2015 Ketmar Dark <ketmar@ketmar.no-ip.org>
4 * Contributors: See contributors list in install.rdf and CREDITS
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
13 * Note that this license applies only to the Greasemonkey extension source
14 * files, not to the user scripts which it runs. User scripts are licensed
15 * separately by their authors.
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25 * The above copyright notice and this permission notice shall be included in all
26 * copies or substantial portions of the Software.
28 ////////////////////////////////////////////////////////////////////////////////
29 uses("resource://gre/modules/PrivateBrowsingUtils.jsm");
32 ////////////////////////////////////////////////////////////////////////////////
33 let ioSvc = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
36 function uriFromUrl (url, base) {
38 if (typeof(base) === "string") {
39 baseUri = uriFromUrl(base);
44 return ioSvc.newURI(url, null, baseUri);
52 * Accessing windows that are closed can be dangerous after http://bugzil.la/695480
53 * this routine takes care of being careful to not trigger any of those broken edge cases
55 function windowIsClosed (aWin) {
57 // if isDeadWrapper (Firefox 15+ only) tells us the window is dead
58 if (Cu.isDeadWrapper && Cu.isDeadWrapper(aWin)) return true;
59 // if we can access the .closed property and it is true, or there is any problem accessing that property
61 if (aWin.closed) return true;
66 logException("windowIsClosed", e);
67 // failsafe: in case of any failure, destroy the command to avoid leaks
74 ////////////////////////////////////////////////////////////////////////////////
75 function GuerillaXmlHttpReqester (wrappedContentWin, originUrl, sandbox) {
76 this.wrappedContentWin = wrappedContentWin;
77 this.originUrl = originUrl;
78 this.sandbox = sandbox;
79 //TODO: add `Cu.getObjectPrincipal(sandbox)` when Pale Moon get this API
80 this.sandboxPrincipal = null;
81 //debuglog("GuerillaXmlHttpReqester created (", originUrl, ")");
86 ////////////////////////////////////////////////////////////////////////////////
87 // this function gets called by user scripts in content security scope to
88 // start a cross-domain xmlhttp request.
90 // details should look like:
91 // {method,url,onload,onerror,onreadystatechange,headers,data}
92 // headers should be in the form {name:value,name:value,etc}
93 // can't support mimetype because i think it's only used for forcing
94 // text/xml and we can't support that
95 GuerillaXmlHttpReqester.prototype.contentStartRequest = function (details) {
96 //debuglog("GuerillaXmlHttpReqester called (", details.url, ")");
99 // Validate and parse the (possibly relative) given URL.
100 uri = uriFromUrl(details.url, this.originUrl);
103 // A malformed URL won't be parsed properly.
104 logException("invalid url '"+details.url+"'", e);
105 throw new Error("invalid url: "+details.url);
108 // we can check `uri.scheme` here, but i'm allowing full access,
109 // 'cause guerilla scripts shouldn't be picked from arbitrary web trashsites
110 let req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
111 tieto(this, "chromeStartRequest", url, details, req)();
119 responseHeaders: "r",
124 abort: function () { return req.abort(); },
127 responseHeaders: null,
133 if (!!details.synchronous) {
134 rv.finalUrl = req.finalUrl;
135 rv.readyState = req.readyState;
136 rv.responseHeaders = req.getAllResponseHeaders();
138 rv.responseText = req.responseText;
140 // some response types don't have .responseText (but do have e.g. blob.response): ignore
142 rv.status = req.status;
143 rv.statusText = req.statusText;
150 ////////////////////////////////////////////////////////////////////////////////
151 // this function is intended to be called in chrome's security context, so
152 // that it can access other domains without security warning
153 GuerillaXmlHttpReqester.prototype.chromeStartRequest = function (safeUrl, details, req) {
154 this.setupReferer(details, req);
156 let setupRequestEvent = tieto(this, "setupRequestEvent", this.wrappedContentWin, this.sandbox);
158 setupRequestEvent(req, "abort", details);
159 setupRequestEvent(req, "error", details);
160 setupRequestEvent(req, "load", details);
161 setupRequestEvent(req, "loadend", details);
162 setupRequestEvent(req, "loadstart", details);
163 setupRequestEvent(req, "progress", details);
164 setupRequestEvent(req, "readystatechange", details);
165 setupRequestEvent(req, "timeout", details);
166 if (details.upload) {
167 setupRequestEvent(req.upload, "abort", details.upload);
168 setupRequestEvent(req.upload, "error", details.upload);
169 setupRequestEvent(req.upload, "load", details.upload);
170 setupRequestEvent(req.upload, "loadend", details.upload);
171 setupRequestEvent(req.upload, "progress", details.upload);
172 setupRequestEvent(req.upload, "timeout", details.upload);
175 req.mozBackgroundRequest = !!details.mozBackgroundRequest;
177 req.open(details.method, safeUrl, !details.synchronous, details.user||"", details.password||"");
181 let isPrivate = PrivateBrowsingUtils.isWindowPrivate(this.wrappedContentWin);
184 channel = req.channel.QueryInterface(Ci.nsIPrivateBrowsingChannel);
185 channel.setPrivate(true);
188 channel = req.channel.QueryInterface(Ci.nsIHttpChannelInternal);
189 channel.forceAllowThirdPartyCookie = true;
191 if (details.overrideMimeType) req.overrideMimeType(details.overrideMimeType);
192 if (details.responseType) req.responseType = details.responseType;
194 if (details.timeout) req.timeout = details.timeout;
196 if ("redirectionLimit" in details) {
198 let httpChannel = req.channel.QueryInterface(Ci.nsIHttpChannel);
199 httpChannel.redirectionLimit = details.redirectionLimit;
203 if (details.headers) {
204 let headers = details.headers;
205 for (let prop in headers) {
206 if (Object.prototype.hasOwnProperty.call(headers, prop)) req.setRequestHeader(prop, headers[prop]);
210 let body = (details.data ? details.data : null);
211 if (details.binary) {
212 req.sendAsBinary(body);
219 ////////////////////////////////////////////////////////////////////////////////
220 // sets the "Referer" HTTP header for this GM_XHR request.
221 // Firefox does not let chrome JS set the "Referer" HTTP header via XHR
222 // directly. However, we can still set it indirectly via an
223 // http-on-modify-request observer.
224 GuerillaXmlHttpReqester.prototype.setupReferer = function (details, req) {
225 if (!details.headers || !details.headers.Referer) return;
226 let observerService = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
227 let requestObserver = {};
228 requestObserver.observe = function (subject, topic, data) {
229 observerService.removeObserver(requestObserver, "http-on-modify-request");
230 let channel = subject.QueryInterface(Ci.nsIChannel);
231 if (channel == req.channel) {
232 debuglog("setting referer ", details.headers.Referer);
233 let httpChannel = subject.QueryInterface(Ci.nsIHttpChannel);
234 httpChannel.setRequestHeader("Referer", details.headers.Referer, false);
238 observerService.addObserver(requestObserver, "http-on-modify-request", false);
243 ////////////////////////////////////////////////////////////////////////////////
244 // arranges for the specified `event` on xmlhttprequest `req` to call the
245 // method by the same name which is a property of `details` in the content
246 // window's security context.
247 GuerillaXmlHttpReqester.prototype.setupRequestEvent = function (wrappedContentWin, sandbox, req, event, details) {
248 // Waive Xrays so that we can read callback function properties ...
249 details = Cu.waiveXrays(details);
250 let eventCallback = details["on"+event];
251 if (!eventCallback) return;
253 //TODO: add principals checking when Pale Moon get this API
255 req.addEventListener(event, function (evt) {
256 let responseState = {
260 lengthComputable: "r",
264 responseHeaders: "r",
271 context: details.context || null,
273 lengthComputable: null,
275 readyState: req.readyState,
276 response: req.response,
277 responseHeaders: null,
286 responseState.responseText = req.responseText;
288 // some response types don't have .responseText (but do have e.g. blob.response): ignore
291 let responseXML = null;
293 responseXML = req.responseXML;
295 // ignore failure; at least in responseType blob case, this access fails
298 // clone the XML object into a content-window-scoped document
299 let xmlDoc = new wrappedContentWin.Document();
300 let clone = xmlDoc.importNode(responseXML.documentElement, true);
301 xmlDoc.appendChild(clone);
302 responseState.responseXML = xmlDoc;
304 responseState.responseXML = "";
309 responseState.lengthComputable = evt.lengthComputable;
310 responseState.loaded = evt.loaded;
311 responseState.total = evt.total;
316 if (4 != req.readyState) break;
317 responseState.responseHeaders = req.getAllResponseHeaders();
318 responseState.status = req.status;
319 responseState.statusText = req.statusText;
320 responseState.finalUrl = req.channel.URI.spec;
324 if (windowIsClosed(wrappedContentWin)) return;
326 // Pop back onto browser thread and call event handler.
327 // Have to use nested function here instead of GM_util.tieto because
328 // otherwise details[event].apply can point to window.setTimeout, which
329 // can be abused to get increased privileges.
330 new XPCNativeWrapper(wrappedContentWin, "setTimeout()").setTimeout(function () { eventCallback.call(details, responseState); }, 0);
335 ////////////////////////////////////////////////////////////////////////////////
336 exports.GuerillaXmlHttpReqester = GuerillaXmlHttpReqester;