]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/xul/staff_client/components/oils_protocol.js
Stage 2: Staff Client
[working/Evergreen.git] / Open-ILS / xul / staff_client / components / oils_protocol.js
1 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
2
3 // These components are intended to handle remote XUL requests
4
5 // FIRST, if we don't have bind, add a workaroundish thing.
6 // If we stop caring about firefox/xulrunner < 4 we can ditch this.
7 if (!Function.prototype.bind) {
8   Function.prototype.bind = function (oThis) {
9     if (typeof this !== "function") {
10       // closest thing possible to the ECMAScript 5 internal IsCallable function
11       throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
12     }
13  
14     var aArgs = Array.prototype.slice.call(arguments, 1),
15         fToBind = this,
16         fNOP = function () {},
17         fBound = function () {
18           return fToBind.apply(this instanceof fNOP && oThis
19                                  ? this
20                                  : oThis,
21                                aArgs.concat(Array.prototype.slice.call(arguments)));
22         };
23  
24     fNOP.prototype = this.prototype;
25     fBound.prototype = new fNOP();
26  
27     return fBound;
28   };
29 }
30
31 // First we define a channel wrapper.
32 // We need this to handle redirects properly.
33 // Things we care about:
34 // Intercepting the URI
35 // Intercepting things that get a channel in callbacks
36 //
37 // We define what we need for those.
38 // wrap_channel(_mode) handles all of the other fun we then have to worry about.
39
40 function oilsChannel() {
41 }
42
43 oilsChannel.prototype = {
44     QueryInterface: XPCOMUtils.generateQI([
45         Components.interfaces.nsIChannel,
46         Components.interfaces.nsIHttpChannel,
47         Components.interfaces.nsIHttpChannelInternal,
48         Components.interfaces.nsIRequest,
49         Components.interfaces.nsIInterfaceRequestor,
50         Components.interfaces.nsIChannelEventSink,
51         Components.interfaces.nsIProgressEventSink,
52         Components.interfaces.nsIHttpEventSink,
53         Components.interfaces.nsIStreamListener,
54         Components.interfaces.nsIAuthPrompt2,
55         Components.interfaces.nsIRequestObserver,
56         Components.interfaces.nsIUploadChannel
57     ]),
58     _internal_channel: null,
59     _internal_uri: null,
60     _redirect_notificationCallbacks: null,
61     _redirect_streamListener: null,
62     wrap_channel: function(channel, uri) {
63         this._internal_channel = channel;
64         this._internal_uri = uri;
65         this.wrap_channel_mode(channel.QueryInterface(Components.interfaces.nsIRequest)); // Basic request stuff
66         this.wrap_channel_mode(channel.QueryInterface(Components.interfaces.nsIChannel)); // Basic channel stuff
67         this.wrap_channel_mode(channel.QueryInterface(Components.interfaces.nsIHttpChannel)); // Basic HTTP stuff
68         this.wrap_channel_mode(channel.QueryInterface(Components.interfaces.nsIHttpChannelInternal)); // To pretend we are internal-ish
69         this.wrap_channel_mode(channel.QueryInterface(Components.interfaces.nsIUploadChannel)); // To make POST work
70     },
71     wrap_channel_mode: function(channel) {
72         for( var item in channel ) {
73             try {
74                 if(this[item] || typeof this[item] != 'undefined')
75                     continue;
76             } catch (E) { continue; }
77             try {
78                 var isfunc = false;
79                 try {
80                     isfunc = (/[Ff]unction/.test(typeof channel[item])) || typeof channel[item].bind != 'undefined';
81                 } catch (E) {}
82                 if(isfunc) {
83                     try {
84                         this[item] = (function(thisItem){ return channel[thisItem].bind(channel); })(item);
85                     } catch (E) {}
86                 } else {
87                     try {
88                         this.__defineGetter__(item, (function(thisItem) { return function() { return channel[thisItem]; } })(item));
89                     } catch (E) {}
90                     try {
91                         this.__defineSetter__(item, (function(thisItem) { return function(val) { return channel[thisItem] = val; } })(item));
92                     } catch (E) {}
93                 }
94             } catch (E) {}
95         }
96     },
97     get notificationCallbacks() {
98         // for a number of reasons we don't admit to re-writing these things here
99         return this._redirect_notificationCallbacks;
100     },
101     set notificationCallbacks(val) {
102         if (val) {
103             this._internal_channel.notificationCallbacks = this.QueryInterface(Components.interfaces.nsIInterfaceRequestor);
104             this._redirect_notificationCallbacks = val;
105         } else {
106             this._internal_channel.notificationCallbacks = null;
107             this._redirect_notificationCallbacks = null;
108         }
109         this._internal_channel.notificationCallbacks = val;
110     },
111     get URI() {
112         return this._internal_uri;
113     },
114     asyncOpen: function(aListener, aContext) {
115         this._redirect_streamListener = aListener;
116         this._internal_channel.asyncOpen(this.QueryInterface(Components.interfaces.nsIStreamListener), aContext);
117     },
118     open: function() {
119         return this._internal_channel.open();
120     },
121     getInterface: function(aIID) {
122         try {
123             if (this.QueryInterface(aIID) && this._redirect_notificationCallbacks.getInterface(aIID)) {
124                 return this.QueryInterface(aIID);
125             }
126         } catch(e) {}
127         // Pass onto the forwarding target as a last resort.
128         return this._redirect_notificationCallbacks.getInterface(aIID);
129     },
130     onChannelRedirect: function(oldChannel, newChannel, flags) {
131         var redirect = this._redirect_notificationCallbacks.getInterface(Components.interfaces.nsIChannelEventSink);
132         return redirect.onChannelRedirect(this.QueryInterface(Components.interfaces.nsIChannel), newChannel, flags);
133     },
134     asyncOnChannelRedirect: function(oldChannel, newChannel, flags, callback) {
135         var redirect = this._redirect_notificationCallbacks.getInterface(Components.interfaces.nsIChannelEventSink);
136         return redirect.asyncOnChannelRedirect(this.QueryInterface(Components.interfaces.nsIChannel), newChannel, flags, callback);
137     },
138     onProgress: function(aRequest, aContext, aProgress, aProgressMax) {
139         var redirect = this._redirect_notificationCallbacks.getInterface(Components.interfaces.nsIProgressEventSink);
140         return redirect.onProgress(this.QueryInterface(Components.interfaces.nsIRequest), aContext, aProgress, aProgressMax);
141     },
142     onStatus: function(aRequest, aContext, aStatus, aStatusArg) {
143         var redirect = this._redirect_notificationCallbacks.getInterface(Components.interfaces.nsIProgressEventSink);
144         return redirect.onStatus(this.QueryInterface(Components.interfaces.nsIRequest), aContext, aStatus, aStatusArg);
145     },
146     onRedirect: function(httpChannel, newChannel) {
147         var redirect = this._redirect_notificationCallbacks.getInterface(Components.interfaces.nsIHttpEventSink);
148         return redirect.onRedirect(this.QueryInterface(Components.interfaces.nsIHttpChannel), newChannel);
149     },
150     asyncPromptAuth: function(aChannel, aCallback, aContext, level, authInfo) {
151         var redirect = this._redirect_notificationCallbacks.getInterface(Components.interfaces.nsIAuthPrompt2);
152         return redirect.asyncPromptAuth(this.QueryInterface(Components.interfaces.nsIChannel), aCallback, aContext, level, authInfo);
153     },
154     promptAuth: function(aChannel, level, authInfo) {
155         var redirect = this._redirect_notificationCallbacks.getInterface(Components.interfaces.nsIAuthPrompt2);
156         return redirect.promptAuth(this.QueryInterface(Components.interfaces.nsIChannel), level, authInfo);
157     },
158     onStartRequest: function(aRequest, aContext) {
159         if ( aRequest == this._internal_channel )
160             this._redirect_streamListener.onStartRequest(this.QueryInterface(Components.interfaces.nsIRequest), aContext);
161         else
162             this._redirect_streamListener.onStartRequest(aRequest, aContext);
163     },
164     onStopRequest: function(aRequest, aContext, aStatusCode) {
165         if ( aRequest == this._internal_channel )
166             this._redirect_streamListener.onStopRequest(this.QueryInterface(Components.interfaces.nsIRequest), aContext, aStatusCode);
167         else
168             this._redirect_streamListener.onStopRequest(aRequest, aContext, aStatusCode);
169     },
170     onDataAvailable: function(aRequest, aContext, aInputStream, aOffset, aCount) {
171         if ( aRequest == this._internal_channel )
172             this._redirect_streamListener.onDataAvailable(this.QueryInterface(Components.interfaces.nsIRequest), aContext, aInputStream, aOffset, aCount);
173         else
174             this._redirect_streamListener.onDataAvailable(aRequest, aContext, aInputStream, aOffset, aCount);
175     },
176 }
177
178 // This handles the actual security-related elements of the protocol wrapper
179
180 function oilsProtocol() {}
181
182 oilsProtocol.prototype = {
183     _system_principal: null,
184     scheme: "oils",
185     protocolflags: Components.interfaces.nsIProtocolHandler.URI_DANGEROUS_TO_LOAD |
186                    Components.interfaces.nsIProtocolHandler.URI_INHERITS_SECURITY_CONTEXT,
187     newURI: function(aSpec, aOriginCharset, aBaseURI) {
188         var new_url = Components.classes["@mozilla.org/network/standard-url;1"].createInstance(Components.interfaces.nsIStandardURL);
189         new_url.init(1, -1, aSpec, aOriginCharset, aBaseURI);
190         return new_url.QueryInterface(Components.interfaces.nsIURI);
191     },
192     newChannel: function(aURI) {
193         var ios = Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService);
194         var host;
195         switch(aURI.host) {
196             case 'remote':
197                 var data_cache = Components.classes["@open-ils.org/openils_data_cache;1"].getService().wrappedJSObject.data;
198                 host = data_cache.server_unadorned;
199                 break;
200             case 'selfcheck':
201                 // To allow elevated permissions on a specific host for selfcheck purposes change this from null.
202                 // This is intended for installing an extension on Firefox specifically for selfcheck purposes
203                 // NOTE: I honestly don't know how dangerous this might be, but I can't imagine it is worse than the previous "grant the domain permissions to do anything" model.
204                 host = null;
205                 break;
206         }
207         if(!host)
208             return ios.newChannel("about:blank", null, null); // Bad input. Not really sure what to do. Returning a dummy channel does prevent a crash, though!
209         var chunk = aURI.spec.replace(/^oils:\/\/[^\/]*\//,'');
210         var channel = ios.newChannel("https://" + host + "/" + chunk, null, null).QueryInterface(Components.interfaces.nsIHttpChannel);
211         channel.QueryInterface(Components.interfaces.nsIHttpChannel).setRequestHeader("OILS-Wrapper", "true", false);
212         if(this._system_principal == null) {
213             // We don't have the owner?
214             var chrome_service = Components.classesByID['{61ba33c0-3031-11d3-8cd0-0060b0fc14a3}'].getService().QueryInterface(Components.interfaces.nsIProtocolHandler);
215             var chrome_uri = chrome_service.newURI("chrome://open_ils_staff_client/content/main/main.xul", null, null);
216             var chrome_channel = chrome_service.newChannel(chrome_uri);
217             this._system_principal = chrome_channel.owner;
218             var chrome_request = chrome_channel.QueryInterface(Components.interfaces.nsIRequest);
219             chrome_request.cancel(0x804b0002);
220         }
221         if (this._system_principal) channel.owner = this._system_principal;
222         // This is a workaround.
223         // We can't wrap all the time because XMLHttpRequests are busted by us doing so.
224         // If we don't wrap, redirects in the Template Toolkit OPAC break out of the protocol.
225         // So wrap only if we are in the catalog!
226         if (aURI.path.match(/^\/eg\/[ok]pac/)) {
227             var outChannel = new oilsChannel();
228             outChannel.wrap_channel(channel, aURI);
229             return outChannel;
230         }
231         return channel;
232     },
233     allowPort: function(aPort, aScheme) {
234         return false;
235     },
236     classDescription: "OILS Protocol Handler",
237     contractID: "@mozilla.org/network/protocol;1?name=oils",
238     classID: Components.ID('{51d35450-5e59-11e1-b86c-0800200c9a66}'),
239     QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIProtocolHandler]),
240 }
241
242 if (XPCOMUtils.generateNSGetFactory)
243     var NSGetFactory = XPCOMUtils.generateNSGetFactory([oilsProtocol]);
244 else
245     var NSGetModule = XPCOMUtils.generateNSGetModule([oilsProtocol]);