WWW update
[ubi] / qml / ubi / oauth / oauth.js
1 /*
2  * Copyright 2008 Netflix, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /* Here's some JavaScript software for implementing OAuth.
18
19    This isn't as useful as you might hope.  OAuth is based around
20    allowing tools and websites to talk to each other.  However,
21    JavaScript running in web browsers is hampered by security
22    restrictions that prevent code running on one website from
23    accessing data stored or served on another.
24
25    Before you start hacking, make sure you understand the limitations
26    posed by cross-domain XMLHttpRequest.
27
28    On the bright side, some platforms use JavaScript as their
29    language, but enable the programmer to access other web sites.
30    Examples include Google Gadgets, and Microsoft Vista Sidebar.
31    For those platforms, this library should come in handy.
32 */
33
34 // The HMAC-SHA1 signature method calls b64_hmac_sha1, defined by
35 // http://pajhome.org.uk/crypt/md5/sha1.js
36
37 /* An OAuth message is represented as an object like this:
38    {method: "GET", action: "http://server.com/path", parameters: ...}
39
40    The parameters may be either a map {name: value, name2: value2}
41    or an Array of name-value pairs [[name, value], [name2, value2]].
42    The latter representation is more powerful: it supports parameters
43    in a specific sequence, or several parameters with the same name;
44    for example [["a", 1], ["b", 2], ["a", 3]].
45
46    Parameter names and values are NOT percent-encoded in an object.
47    They must be encoded before transmission and decoded after reception.
48    For example, this message object:
49    {method: "GET", action: "http://server/path", parameters: {p: "x y"}}
50    ... can be transmitted as an HTTP request that begins:
51    GET /path?p=x%20y HTTP/1.0
52    (This isn't a valid OAuth request, since it lacks a signature etc.)
53    Note that the object "x y" is transmitted as x%20y.  To encode
54    parameters, you can call OAuth.addToURL, OAuth.formEncode or
55    OAuth.getAuthorization.
56
57    This message object model harmonizes with the browser object model for
58    input elements of an form, whose value property isn't percent encoded.
59    The browser encodes each value before transmitting it. For example,
60    see consumer.setInputs in example/consumer.js.
61  */
62
63 /* This script needs to know what time it is. By default, it uses the local
64    clock (new Date), which is apt to be inaccurate in browsers. To do
65    better, you can load this script from a URL whose query string contains
66    an oauth_timestamp parameter, whose value is a current Unix timestamp.
67    For example, when generating the enclosing document using PHP:
68
69    <script src="oauth.js?oauth_timestamp=<?=time()?>" ...
70
71    Another option is to call OAuth.correctTimestamp with a Unix timestamp.
72  */
73
74 var OAuth; if (OAuth == null) OAuth = {};
75
76 OAuth.setProperties = function setProperties(into, from) {
77     if (into != null && from != null) {
78         for (var key in from) {
79             into[key] = from[key];
80         }
81     }
82     return into;
83 }
84
85 OAuth.setProperties(OAuth, // utility functions
86 {
87     percentEncode: function percentEncode(s) {
88         if (s == null) {
89             return "";
90         }
91         if (s instanceof Array) {
92             var e = "";
93             for (var i = 0; i < s.length; ++s) {
94                 if (e != "") e += '&';
95                 e += OAuth.percentEncode(s[i]);
96             }
97             return e;
98         }
99         s = encodeURIComponent(s);
100         // Now replace the values which encodeURIComponent doesn't do
101         // encodeURIComponent ignores: - _ . ! ~ * ' ( )
102         // OAuth dictates the only ones you can ignore are: - _ . ~
103         // Source: http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Functions:encodeURIComponent
104         s = s.replace(/\!/g, "%21");
105         s = s.replace(/\*/g, "%2A");
106         s = s.replace(/\'/g, "%27");
107         s = s.replace(/\(/g, "%28");
108         s = s.replace(/\)/g, "%29");
109         return s;
110     }
111 ,
112     decodePercent: function decodePercent(s) {
113         if (s != null) {
114             // Handle application/x-www-form-urlencoded, which is defined by
115             // http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1
116             s = s.replace(/\+/g, " ");
117         }
118         return decodeURIComponent(s);
119     }
120 ,
121     /** Convert the given parameters to an Array of name-value pairs. */
122     getParameterList: function getParameterList(parameters) {
123         if (parameters == null) {
124             return [];
125         }
126         if (typeof parameters != "object") {
127             return OAuth.decodeForm(parameters + "");
128         }
129         if (parameters instanceof Array) {
130             return parameters;
131         }
132         var list = [];
133         for (var p in parameters) {
134             list.push([p, parameters[p]]);
135         }
136         return list;
137     }
138 ,
139     /** Convert the given parameters to a map from name to value. */
140     getParameterMap: function getParameterMap(parameters) {
141         if (parameters == null) {
142             return {};
143         }
144         if (typeof parameters != "object") {
145             return OAuth.getParameterMap(OAuth.decodeForm(parameters + ""));
146         }
147         if (parameters instanceof Array) {
148             var map = {};
149             for (var p = 0; p < parameters.length; ++p) {
150                 var key = parameters[p][0];
151                 if (map[key] === undefined) { // first value wins
152                     map[key] = parameters[p][1];
153                 }
154             }
155             return map;
156         }
157         return parameters;
158     }
159 ,
160     getParameter: function getParameter(parameters, name) {
161         if (parameters instanceof Array) {
162             for (var p = 0; p < parameters.length; ++p) {
163                 if (parameters[p][0] == name) {
164                     return parameters[p][1]; // first value wins
165                 }
166             }
167         } else {
168             return OAuth.getParameterMap(parameters)[name];
169         }
170         return null;
171     }
172 ,
173     formEncode: function formEncode(parameters) {
174         var form = "";
175         var list = OAuth.getParameterList(parameters);
176         for (var p = 0; p < list.length; ++p) {
177             var value = list[p][1];
178             if (value == null) value = "";
179             if (form != "") form += '&';
180             form += OAuth.percentEncode(list[p][0])
181               +'='+ OAuth.percentEncode(value);
182         }
183         return form;
184     }
185 ,
186     decodeForm: function decodeForm(form) {
187         var list = [];
188         var nvps = form.split('&');
189         for (var n = 0; n < nvps.length; ++n) {
190             var nvp = nvps[n];
191             if (nvp == "") {
192                 continue;
193             }
194             var equals = nvp.indexOf('=');
195             var name;
196             var value;
197             if (equals < 0) {
198                 name = OAuth.decodePercent(nvp);
199                 value = null;
200             } else {
201                 name = OAuth.decodePercent(nvp.substring(0, equals));
202                 value = OAuth.decodePercent(nvp.substring(equals + 1));
203             }
204             list.push([name, value]);
205         }
206         return list;
207     }
208 ,
209     setParameter: function setParameter(message, name, value) {
210         var parameters = message.parameters;
211         if (parameters instanceof Array) {
212             for (var p = 0; p < parameters.length; ++p) {
213                 if (parameters[p][0] == name) {
214                     if (value === undefined) {
215                         parameters.splice(p, 1);
216                     } else {
217                         parameters[p][1] = value;
218                         value = undefined;
219                     }
220                 }
221             }
222             if (value !== undefined) {
223                 parameters.push([name, value]);
224             }
225         } else {
226             parameters = OAuth.getParameterMap(parameters);
227             parameters[name] = value;
228             message.parameters = parameters;
229         }
230     }
231 ,
232     setParameters: function setParameters(message, parameters) {
233         var list = OAuth.getParameterList(parameters);
234         for (var i = 0; i < list.length; ++i) {
235             OAuth.setParameter(message, list[i][0], list[i][1]);
236         }
237     }
238 ,
239     /** Fill in parameters to help construct a request message.
240         This function doesn't fill in every parameter.
241         The accessor object should be like:
242         {consumerKey:'foo', consumerSecret:'bar', accessorSecret:'nurn', token:'krelm', tokenSecret:'blah'}
243         The accessorSecret property is optional.
244      */
245     completeRequest: function completeRequest(message, accessor) {
246         if (message.method == null) {
247             message.method = "GET";
248         }
249         var map = OAuth.getParameterMap(message.parameters);
250         if (map.oauth_consumer_key == null) {
251             OAuth.setParameter(message, "oauth_consumer_key", accessor.consumerKey || "");
252         }
253         if (map.oauth_token == null && accessor.token != null) {
254             OAuth.setParameter(message, "oauth_token", accessor.token);
255         }
256         if (map.oauth_version == null) {
257             OAuth.setParameter(message, "oauth_version", "1.0");
258         }
259         if (map.oauth_timestamp == null) {
260             OAuth.setParameter(message, "oauth_timestamp", OAuth.timestamp());
261         }
262         if (map.oauth_nonce == null) {
263             OAuth.setParameter(message, "oauth_nonce", OAuth.nonce(6));
264         }
265         OAuth.SignatureMethod.sign(message, accessor);
266     }
267 ,
268     setTimestampAndNonce: function setTimestampAndNonce(message) {
269         OAuth.setParameter(message, "oauth_timestamp", OAuth.timestamp());
270         OAuth.setParameter(message, "oauth_nonce", OAuth.nonce(6));
271     }
272 ,
273     addToURL: function addToURL(url, parameters) {
274         newURL = url;
275         if (parameters != null) {
276             var toAdd = OAuth.formEncode(parameters);
277             if (toAdd.length > 0) {
278                 var q = url.indexOf('?');
279                 if (q < 0) newURL += '?';
280                 else       newURL += '&';
281                 newURL += toAdd;
282             }
283         }
284         return newURL;
285     }
286 ,
287     /** Construct the value of the Authorization header for an HTTP request. */
288     getAuthorizationHeader: function getAuthorizationHeader(realm, parameters) {
289         var header = 'OAuth realm="' + OAuth.percentEncode(realm) + '"';
290         var list = OAuth.getParameterList(parameters);
291         for (var p = 0; p < list.length; ++p) {
292             var parameter = list[p];
293             var name = parameter[0];
294             if (name.indexOf("oauth_") == 0) {
295                 header += ',' + OAuth.percentEncode(name) + '="' + OAuth.percentEncode(parameter[1]) + '"';
296             }
297         }
298         return header;
299     }
300 ,
301     /** Correct the time using a parameter from the URL from which the last script was loaded. */
302     correctTimestampFromSrc: function correctTimestampFromSrc(parameterName) {
303         parameterName = parameterName || "oauth_timestamp";
304         var scripts = document.getElementsByTagName('script');
305         if (scripts == null || !scripts.length) return;
306         var src = scripts[scripts.length-1].src;
307         if (!src) return;
308         var q = src.indexOf("?");
309         if (q < 0) return;
310         parameters = OAuth.getParameterMap(OAuth.decodeForm(src.substring(q+1)));
311         var t = parameters[parameterName];
312         if (t == null) return;
313         OAuth.correctTimestamp(t);
314     }
315 ,
316     /** Generate timestamps starting with the given value. */
317     correctTimestamp: function correctTimestamp(timestamp) {
318         OAuth.timeCorrectionMsec = (timestamp * 1000) - (new Date()).getTime();
319     }
320 ,
321     /** The difference between the correct time and my clock. */
322     timeCorrectionMsec: 0
323 ,
324     timestamp: function timestamp() {
325         var t = (new Date()).getTime() + OAuth.timeCorrectionMsec;
326         return Math.floor(t / 1000);
327     }
328 ,
329     nonce: function nonce(length) {
330         var chars = OAuth.nonce.CHARS;
331         var result = "";
332         for (var i = 0; i < length; ++i) {
333             var rnum = Math.floor(Math.random() * chars.length);
334             result += chars.substring(rnum, rnum+1);
335         }
336         return result;
337     }
338 });
339
340 OAuth.nonce.CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
341
342 /** Define a constructor function,
343     without causing trouble to anyone who was using it as a namespace.
344     That is, if parent[name] already existed and had properties,
345     copy those properties into the new constructor.
346  */
347 OAuth.declareClass = function declareClass(parent, name, newConstructor) {
348     var previous = parent[name];
349     parent[name] = newConstructor;
350     if (newConstructor != null && previous != null) {
351         for (var key in previous) {
352             if (key != "prototype") {
353                 newConstructor[key] = previous[key];
354             }
355         }
356     }
357     return newConstructor;
358 }
359
360 /** An abstract algorithm for signing messages. */
361 OAuth.declareClass(OAuth, "SignatureMethod", function OAuthSignatureMethod(){});
362
363 OAuth.setProperties(OAuth.SignatureMethod.prototype, // instance members
364 {
365     /** Add a signature to the message. */
366     sign: function sign(message) {
367         var baseString = OAuth.SignatureMethod.getBaseString(message);
368         var signature = this.getSignature(baseString);
369         OAuth.setParameter(message, "oauth_signature", signature);
370         return signature; // just in case someone's interested
371     }
372 ,
373     /** Set the key string for signing. */
374     initialize: function initialize(name, accessor) {
375         var consumerSecret;
376         if (accessor.accessorSecret != null
377             && name.length > 9
378             && name.substring(name.length-9) == "-Accessor")
379         {
380             consumerSecret = accessor.accessorSecret;
381         } else {
382             consumerSecret = accessor.consumerSecret;
383         }
384         this.key = OAuth.percentEncode(consumerSecret)
385              +"&"+ OAuth.percentEncode(accessor.tokenSecret);
386     }
387 });
388
389 /* SignatureMethod expects an accessor object to be like this:
390    {tokenSecret: "lakjsdflkj...", consumerSecret: "QOUEWRI..", accessorSecret: "xcmvzc..."}
391    The accessorSecret property is optional.
392  */
393 // Class members:
394 OAuth.setProperties(OAuth.SignatureMethod, // class members
395 {
396     sign: function sign(message, accessor) {
397         var name = OAuth.getParameterMap(message.parameters).oauth_signature_method;
398         if (name == null || name == "") {
399             name = "HMAC-SHA1";
400             OAuth.setParameter(message, "oauth_signature_method", name);
401         }
402         OAuth.SignatureMethod.newMethod(name, accessor).sign(message);
403     }
404 ,
405     /** Instantiate a SignatureMethod for the given method name. */
406     newMethod: function newMethod(name, accessor) {
407         var impl = OAuth.SignatureMethod.REGISTERED[name];
408         if (impl != null) {
409             var method = new impl();
410             method.initialize(name, accessor);
411             return method;
412         }
413         var err = new Error("signature_method_rejected");
414         var acceptable = "";
415         for (var r in OAuth.SignatureMethod.REGISTERED) {
416             if (acceptable != "") acceptable += '&';
417             acceptable += OAuth.percentEncode(r);
418         }
419         err.oauth_acceptable_signature_methods = acceptable;
420         throw err;
421     }
422 ,
423     /** A map from signature method name to constructor. */
424     REGISTERED : {}
425 ,
426     /** Subsequently, the given constructor will be used for the named methods.
427         The constructor will be called with no parameters.
428         The resulting object should usually implement getSignature(baseString).
429         You can easily define such a constructor by calling makeSubclass, below.
430      */
431     registerMethodClass: function registerMethodClass(names, classConstructor) {
432         for (var n = 0; n < names.length; ++n) {
433             OAuth.SignatureMethod.REGISTERED[names[n]] = classConstructor;
434         }
435     }
436 ,
437     /** Create a subclass of OAuth.SignatureMethod, with the given getSignature function. */
438     makeSubclass: function makeSubclass(getSignatureFunction) {
439         var superClass = OAuth.SignatureMethod;
440         var subClass = function() {
441             superClass.call(this);
442         };
443         subClass.prototype = new superClass();
444         // Delete instance variables from prototype:
445         // delete subclass.prototype... There aren't any.
446         subClass.prototype.getSignature = getSignatureFunction;
447         subClass.prototype.constructor = subClass;
448         return subClass;
449     }
450 ,
451     getBaseString: function getBaseString(message) {
452         var URL = message.action;
453         var q = URL.indexOf('?');
454         var parameters;
455         if (q < 0) {
456             parameters = message.parameters;
457         } else {
458             // Combine the URL query string with the other parameters:
459             parameters = OAuth.decodeForm(URL.substring(q + 1));
460             var toAdd = OAuth.getParameterList(message.parameters);
461             for (var a = 0; a < toAdd.length; ++a) {
462                 parameters.push(toAdd[a]);
463             }
464         }
465
466
467
468         return OAuth.percentEncode(message.method.toUpperCase())
469          +'&'+ OAuth.percentEncode(OAuth.SignatureMethod.normalizeUrl(URL))
470          +'&'+ OAuth.percentEncode(OAuth.SignatureMethod.normalizeParameters(parameters));
471     }
472 ,
473     normalizeUrl: function normalizeUrl(url) {
474         var uri = OAuth.SignatureMethod.parseUri(url);
475         var scheme = uri.protocol.toLowerCase();
476         var authority = uri.authority.toLowerCase();
477         var dropPort = (scheme == "http" && uri.port == 80)
478                     || (scheme == "https" && uri.port == 443);
479         if (dropPort) {
480             // find the last : in the authority
481             var index = authority.lastIndexOf(":");
482             if (index >= 0) {
483                 authority = authority.substring(0, index);
484             }
485         }
486         var path = uri.path;
487         if (!path) {
488             path = "/"; // conforms to RFC 2616 section 3.2.2
489         }
490         // we know that there is no query and no fragment here.
491         return scheme + "://" + authority + path;
492     }
493 ,
494     parseUri: function parseUri (str) {
495         /* This function was adapted from parseUri 1.2.1
496            http://stevenlevithan.com/demo/parseuri/js/assets/parseuri.js
497          */
498         var o = {key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],
499                  parser: {strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/ }};
500         var m = o.parser.strict.exec(str);
501         var uri = {};
502         var i = 14;
503         while (i--) uri[o.key[i]] = m[i] || "";
504         return uri;
505     }
506 ,
507     normalizeParameters: function normalizeParameters(parameters) {
508         if (parameters == null) {
509             return "";
510         }
511         var list = OAuth.getParameterList(parameters);
512         var sortable = [];
513         for (var p = 0; p < list.length; ++p) {
514             var nvp = list[p];
515             if (nvp[0] != "oauth_signature") {
516                 sortable.push([ OAuth.percentEncode(nvp[0])
517                               + " " // because it comes before any character that can appear in a percentEncoded string.
518                               + OAuth.percentEncode(nvp[1])
519                               , nvp]);
520             }
521         }
522         sortable.sort(function(a,b) {
523                           if (a[0] < b[0]) return  -1;
524                           if (a[0] > b[0]) return 1;
525                           return 0;
526                       });
527         var sorted = [];
528         for (var s = 0; s < sortable.length; ++s) {
529             sorted.push(sortable[s][1]);
530         }
531         return OAuth.formEncode(sorted);
532     }
533 });
534
535 OAuth.SignatureMethod.registerMethodClass(["PLAINTEXT", "PLAINTEXT-Accessor"],
536     OAuth.SignatureMethod.makeSubclass(
537         function getSignature(baseString) {
538             return this.key;
539         }
540     ));
541
542 OAuth.SignatureMethod.registerMethodClass(["HMAC-SHA1", "HMAC-SHA1-Accessor"],
543     OAuth.SignatureMethod.makeSubclass(
544         function getSignature(baseString) {
545             b64pad = '=';
546             var signature = b64_hmac_sha1(this.key, baseString);
547             return signature;
548         }
549     ));
550
551 try {
552     OAuth.correctTimestampFromSrc();
553 } catch(e) {
554 }