;(function() { 'use strict'; // Check the existence of module and module.exports to detect node var isNode = (typeof module != 'undefined' && typeof module.exports != 'undefined'); function OAuthSignature() { } OAuthSignature.prototype.generate = function (httpMethod, url, parameters, consumerSecret, tokenSecret, options) { var signatureBaseString = new SignatureBaseString(httpMethod, url, parameters).generate(); var encodeSignature = true; if (options) { encodeSignature = options.encodeSignature; } return new HmacSha1Signature(signatureBaseString, consumerSecret, tokenSecret).generate(encodeSignature); }; // Specification: http://oauth.net/core/1.0/#anchor14 // url: if the scheme is missing, http will be added automatically function SignatureBaseString(httpMethod, url, parameters) { parameters = new ParametersLoader(parameters).get(); this._httpMethod = new HttpMethodElement(httpMethod).get(); this._url = new UrlElement(url).get(); this._parameters = new ParametersElement(parameters).get(); this._rfc3986 = new Rfc3986(); } SignatureBaseString.prototype = { generate : function () { // HTTP_METHOD & url & parameters return this._rfc3986.encode(this._httpMethod) + '&' + this._rfc3986.encode(this._url) + '&' + this._rfc3986.encode(this._parameters); } }; function HttpMethodElement(httpMethod) { this._httpMethod = httpMethod || ''; } HttpMethodElement.prototype = { get : function () { return this._httpMethod.toUpperCase(); } }; function UrlElement(url) { this._url = url || ''; } UrlElement.prototype = { get : function () { // The following is to prevent js-url from loading the window.location if (!this._url) { return this._url; } // FIXME: Make this behaviour explicit by returning warnings if (this._url.indexOf('://') == -1) { this._url = 'http://' + this._url; } // Handle parsing the url in node or in browser var parsedUrl = isNode ? this.parseInNode() : this.parseInBrowser(), // FIXME: Make this behaviour explicit by returning warnings scheme = (parsedUrl.scheme || 'http').toLowerCase(), // FIXME: Make this behaviour explicit by returning warnings authority = (parsedUrl.authority || '').toLocaleLowerCase(), path = parsedUrl.path || '', port = parsedUrl.port || ''; // FIXME: Make this behaviour explicit by returning warnings if ((port == 80 && scheme == 'http') || (port == 443 && scheme == 'https')) { port = ''; } var baseUrl = scheme + '://' + authority; baseUrl = baseUrl + (!!port ? ':' + port : ''); // FIXME: Make this behaviour explicit by returning warnings if (path == '/' && this._url.indexOf(baseUrl + path) === -1) { path = ''; } this._url = (scheme ? scheme + '://' : '') + authority + (port ? ':' + port : '') + path; return this._url; }, parseInBrowser : function () { return { scheme : url('protocol', this._url).toLowerCase(), authority : url('hostname', this._url).toLocaleLowerCase(), port : url('port', this._url), path :url('path', this._url) }; }, parseInNode : function () { var url = require('uri-js'), parsedUri = url.parse(this._url), scheme = parsedUri.scheme; // strip the ':' at the end of the scheme added by the url module if (scheme.charAt(scheme.length - 1) == ":") { scheme = scheme.substring(0, scheme.length - 1); } return { scheme : scheme, authority : parsedUri.host, port : parsedUri.port, path : parsedUri.path }; } }; function ParametersElement (parameters) { // Parameters format: { 'key': ['value 1', 'value 2'] }; this._parameters = parameters || {}; this._sortedKeys = []; this._normalizedParameters = []; this._rfc3986 = new Rfc3986(); this._sortParameters(); this._concatenateParameters(); } ParametersElement.prototype = { _sortParameters : function () { var key, encodedKey; for (key in this._parameters) { if (this._parameters.hasOwnProperty(key)) { encodedKey = this._rfc3986.encode(key); this._sortedKeys.push(encodedKey); } } this._sortedKeys.sort(); }, _concatenateParameters : function () { var i; for (i = 0; i < this._sortedKeys.length; i++) { this._normalizeParameter(this._sortedKeys[i]); } }, _normalizeParameter : function (encodedKey) { var i, key = this._rfc3986.decode(encodedKey), values = this._parameters[key], encodedValue; values.sort(); for (i = 0; i < values.length; i++) { encodedValue = this._rfc3986.encode(values[i]); this._normalizedParameters.push(encodedKey + '=' + encodedValue) } }, get : function () { return this._normalizedParameters.join('&'); } }; function ParametersLoader (parameters) { // Format: { 'key': ['value 1', 'value 2'] } this._parameters = {}; this._loadParameters(parameters || {}); } ParametersLoader.prototype = { _loadParameters : function (parameters) { if (parameters instanceof Array) { this._loadParametersFromArray(parameters); } else if (typeof parameters === 'object') { this._loadParametersFromObject(parameters); } }, _loadParametersFromArray : function (parameters) { var i; for (i = 0; i < parameters.length; i++) { this._loadParametersFromObject(parameters[i]); } }, _loadParametersFromObject : function (parameters) { var key; for (key in parameters) { if (parameters.hasOwnProperty(key)) { var stringValue = this._getStringFromParameter(parameters[key]); this._loadParameterValue(key, stringValue); } } }, _loadParameterValue : function (key, value) { var i; if (value instanceof Array) { for (i = 0; i < value.length; i++) { var stringValue = this._getStringFromParameter(value[i]); this._addParameter(key, stringValue); } if (value.length == 0) { this._addParameter(key, ''); } } else { this._addParameter(key, value); } }, _getStringFromParameter : function (parameter) { var stringValue = parameter || ''; try { if (typeof parameter === 'number' || typeof parameter === 'boolean') { stringValue = parameter.toString(); } } catch (e) {} return stringValue; }, _addParameter : function (key, value) { if (!this._parameters[key]) { this._parameters[key] = []; } this._parameters[key].push(value); }, get : function () { return this._parameters; } }; function Rfc3986() { } Rfc3986.prototype = { encode : function (decoded) { if (!decoded) { return ''; } // using implementation from: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent?redirectlocale=en-US&redirectslug=JavaScript%2FReference%2FGlobal_Objects%2FencodeURIComponent return encodeURIComponent(decoded) .replace(/[!'()]/g, escape) .replace(/\*/g, "%2A"); }, decode : function (encoded) { if (!encoded) { return ''; } return decodeURIComponent(encoded); } }; function HmacSha1Signature(signatureBaseString, consumerSecret, tokenSecret) { this._rfc3986 = new Rfc3986(); this._text = signatureBaseString; this._key = this._rfc3986.encode(consumerSecret) + '&' + this._rfc3986.encode(tokenSecret); this._base64EncodedHash = new HmacSha1(this._text, this._key).getBase64EncodedHash(); } HmacSha1Signature.prototype = { generate : function (encode) { return encode === false ? this._base64EncodedHash : this._rfc3986.encode(this._base64EncodedHash); } }; function HmacSha1(text, key) { // load CryptoJs in the browser or in node this._cryptoJS = isNode ? require('crypto-js') : CryptoJS; this._text = text || ''; this._key = key || ''; this._hash = this._cryptoJS.HmacSHA1(this._text, this._key); } HmacSha1.prototype = { getBase64EncodedHash : function () { return this._hash.toString(this._cryptoJS.enc.Base64); } }; var oauthSignature = new OAuthSignature(); oauthSignature.SignatureBaseString = SignatureBaseString; oauthSignature.HttpMethodElement = HttpMethodElement; oauthSignature.UrlElement = UrlElement; oauthSignature.ParametersElement = ParametersElement; oauthSignature.ParametersLoader = ParametersLoader; oauthSignature.Rfc3986 = Rfc3986; oauthSignature.HmacSha1Signature = HmacSha1Signature; oauthSignature.HmacSha1 = HmacSha1; // support for the browser and nodejs if (isNode) { module.exports = oauthSignature; } else { window.oauthSignature = oauthSignature; } })();