blob: 85b27850925e4fc5dd9212465f207111bdf75f1a [file] [log] [blame]
avm9996304def3e2016-11-27 22:53:05 +01001/*
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
74var OAuth; if (OAuth == null) OAuth = {};
75
76OAuth.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
85OAuth.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
340OAuth.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 */
347OAuth.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. */
361OAuth.declareClass(OAuth, "SignatureMethod", function OAuthSignatureMethod(){});
362
363OAuth.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:
394OAuth.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 return OAuth.percentEncode(message.method.toUpperCase())
466 +'&'+ OAuth.percentEncode(OAuth.SignatureMethod.normalizeUrl(URL))
467 +'&'+ OAuth.percentEncode(OAuth.SignatureMethod.normalizeParameters(parameters));
468 }
469,
470 normalizeUrl: function normalizeUrl(url) {
471 var uri = OAuth.SignatureMethod.parseUri(url);
472 var scheme = uri.protocol.toLowerCase();
473 var authority = uri.authority.toLowerCase();
474 var dropPort = (scheme == "http" && uri.port == 80)
475 || (scheme == "https" && uri.port == 443);
476 if (dropPort) {
477 // find the last : in the authority
478 var index = authority.lastIndexOf(":");
479 if (index >= 0) {
480 authority = authority.substring(0, index);
481 }
482 }
483 var path = uri.path;
484 if (!path) {
485 path = "/"; // conforms to RFC 2616 section 3.2.2
486 }
487 // we know that there is no query and no fragment here.
488 return scheme + "://" + authority + path;
489 }
490,
491 parseUri: function parseUri (str) {
492 /* This function was adapted from parseUri 1.2.1
493 http://stevenlevithan.com/demo/parseuri/js/assets/parseuri.js
494 */
495 var o = {key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],
496 parser: {strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/ }};
497 var m = o.parser.strict.exec(str);
498 var uri = {};
499 var i = 14;
500 while (i--) uri[o.key[i]] = m[i] || "";
501 return uri;
502 }
503,
504 normalizeParameters: function normalizeParameters(parameters) {
505 if (parameters == null) {
506 return "";
507 }
508 var list = OAuth.getParameterList(parameters);
509 var sortable = [];
510 for (var p = 0; p < list.length; ++p) {
511 var nvp = list[p];
512 if (nvp[0] != "oauth_signature") {
513 sortable.push([ OAuth.percentEncode(nvp[0])
514 + " " // because it comes before any character that can appear in a percentEncoded string.
515 + OAuth.percentEncode(nvp[1])
516 , nvp]);
517 }
518 }
519 sortable.sort(function(a,b) {
520 if (a[0] < b[0]) return -1;
521 if (a[0] > b[0]) return 1;
522 return 0;
523 });
524 var sorted = [];
525 for (var s = 0; s < sortable.length; ++s) {
526 sorted.push(sortable[s][1]);
527 }
528 return OAuth.formEncode(sorted);
529 }
530});
531
532OAuth.SignatureMethod.registerMethodClass(["PLAINTEXT", "PLAINTEXT-Accessor"],
533 OAuth.SignatureMethod.makeSubclass(
534 function getSignature(baseString) {
535 return this.key;
536 }
537 ));
538
539OAuth.SignatureMethod.registerMethodClass(["HMAC-SHA1", "HMAC-SHA1-Accessor"],
540 OAuth.SignatureMethod.makeSubclass(
541 function getSignature(baseString) {
542 b64pad = '=';
543 var signature = b64_hmac_sha1(this.key, baseString);
544 return signature;
545 }
546 ));
547
548try {
549 OAuth.correctTimestampFromSrc();
550} catch(e) {
551}