react / wstein / node_modules / jest-cli / node_modules / jsdom / node_modules / request / node_modules / http-signature / lib / parser.js
81146 views// Copyright 2012 Joyent, Inc. All rights reserved.12var assert = require('assert-plus');3var util = require('util');4567///--- Globals89var Algorithms = {10'rsa-sha1': true,11'rsa-sha256': true,12'rsa-sha512': true,13'dsa-sha1': true,14'hmac-sha1': true,15'hmac-sha256': true,16'hmac-sha512': true17};1819var State = {20New: 0,21Params: 122};2324var ParamsState = {25Name: 0,26Quote: 1,27Value: 2,28Comma: 329};30313233///--- Specific Errors3435function HttpSignatureError(message, caller) {36if (Error.captureStackTrace)37Error.captureStackTrace(this, caller || HttpSignatureError);3839this.message = message;40this.name = caller.name;41}42util.inherits(HttpSignatureError, Error);4344function ExpiredRequestError(message) {45HttpSignatureError.call(this, message, ExpiredRequestError);46}47util.inherits(ExpiredRequestError, HttpSignatureError);484950function InvalidHeaderError(message) {51HttpSignatureError.call(this, message, InvalidHeaderError);52}53util.inherits(InvalidHeaderError, HttpSignatureError);545556function InvalidParamsError(message) {57HttpSignatureError.call(this, message, InvalidParamsError);58}59util.inherits(InvalidParamsError, HttpSignatureError);606162function MissingHeaderError(message) {63HttpSignatureError.call(this, message, MissingHeaderError);64}65util.inherits(MissingHeaderError, HttpSignatureError);66676869///--- Exported API7071module.exports = {7273/**74* Parses the 'Authorization' header out of an http.ServerRequest object.75*76* Note that this API will fully validate the Authorization header, and throw77* on any error. It will not however check the signature, or the keyId format78* as those are specific to your environment. You can use the options object79* to pass in extra constraints.80*81* As a response object you can expect this:82*83* {84* "scheme": "Signature",85* "params": {86* "keyId": "foo",87* "algorithm": "rsa-sha256",88* "headers": [89* "date" or "x-date",90* "content-md5"91* ],92* "signature": "base64"93* },94* "signingString": "ready to be passed to crypto.verify()"95* }96*97* @param {Object} request an http.ServerRequest.98* @param {Object} options an optional options object with:99* - clockSkew: allowed clock skew in seconds (default 300).100* - headers: required header names (def: date or x-date)101* - algorithms: algorithms to support (default: all).102* @return {Object} parsed out object (see above).103* @throws {TypeError} on invalid input.104* @throws {InvalidHeaderError} on an invalid Authorization header error.105* @throws {InvalidParamsError} if the params in the scheme are invalid.106* @throws {MissingHeaderError} if the params indicate a header not present,107* either in the request headers from the params,108* or not in the params from a required header109* in options.110* @throws {ExpiredRequestError} if the value of date or x-date exceeds skew.111*/112parseRequest: function parseRequest(request, options) {113assert.object(request, 'request');114assert.object(request.headers, 'request.headers');115if (options === undefined) {116options = {};117}118if (options.headers === undefined) {119options.headers = [request.headers['x-date'] ? 'x-date' : 'date'];120}121assert.object(options, 'options');122assert.arrayOfString(options.headers, 'options.headers');123assert.optionalNumber(options.clockSkew, 'options.clockSkew');124125if (!request.headers.authorization)126throw new MissingHeaderError('no authorization header present in ' +127'the request');128129options.clockSkew = options.clockSkew || 300;130131132var i = 0;133var state = State.New;134var substate = ParamsState.Name;135var tmpName = '';136var tmpValue = '';137138var parsed = {139scheme: '',140params: {},141signingString: '',142143get algorithm() {144return this.params.algorithm.toUpperCase();145},146147get keyId() {148return this.params.keyId;149}150151};152153var authz = request.headers.authorization;154for (i = 0; i < authz.length; i++) {155var c = authz.charAt(i);156157switch (Number(state)) {158159case State.New:160if (c !== ' ') parsed.scheme += c;161else state = State.Params;162break;163164case State.Params:165switch (Number(substate)) {166167case ParamsState.Name:168var code = c.charCodeAt(0);169// restricted name of A-Z / a-z170if ((code >= 0x41 && code <= 0x5a) || // A-Z171(code >= 0x61 && code <= 0x7a)) { // a-z172tmpName += c;173} else if (c === '=') {174if (tmpName.length === 0)175throw new InvalidHeaderError('bad param format');176substate = ParamsState.Quote;177} else {178throw new InvalidHeaderError('bad param format');179}180break;181182case ParamsState.Quote:183if (c === '"') {184tmpValue = '';185substate = ParamsState.Value;186} else {187throw new InvalidHeaderError('bad param format');188}189break;190191case ParamsState.Value:192if (c === '"') {193parsed.params[tmpName] = tmpValue;194substate = ParamsState.Comma;195} else {196tmpValue += c;197}198break;199200case ParamsState.Comma:201if (c === ',') {202tmpName = '';203substate = ParamsState.Name;204} else {205throw new InvalidHeaderError('bad param format');206}207break;208209default:210throw new Error('Invalid substate');211}212break;213214default:215throw new Error('Invalid substate');216}217218}219220if (!parsed.params.headers || parsed.params.headers === '') {221if (request.headers['x-date']) {222parsed.params.headers = ['x-date'];223} else {224parsed.params.headers = ['date'];225}226} else {227parsed.params.headers = parsed.params.headers.split(' ');228}229230// Minimally validate the parsed object231if (!parsed.scheme || parsed.scheme !== 'Signature')232throw new InvalidHeaderError('scheme was not "Signature"');233234if (!parsed.params.keyId)235throw new InvalidHeaderError('keyId was not specified');236237if (!parsed.params.algorithm)238throw new InvalidHeaderError('algorithm was not specified');239240if (!parsed.params.signature)241throw new InvalidHeaderError('signature was not specified');242243// Check the algorithm against the official list244parsed.params.algorithm = parsed.params.algorithm.toLowerCase();245if (!Algorithms[parsed.params.algorithm])246throw new InvalidParamsError(parsed.params.algorithm +247' is not supported');248249// Build the signingString250for (i = 0; i < parsed.params.headers.length; i++) {251var h = parsed.params.headers[i].toLowerCase();252parsed.params.headers[i] = h;253254if (h !== 'request-line') {255var value = request.headers[h];256if (!value)257throw new MissingHeaderError(h + ' was not in the request');258parsed.signingString += h + ': ' + value;259} else {260parsed.signingString +=261request.method + ' ' + request.url + ' HTTP/' + request.httpVersion;262}263264if ((i + 1) < parsed.params.headers.length)265parsed.signingString += '\n';266}267268// Check against the constraints269var date;270if (request.headers.date || request.headers['x-date']) {271if (request.headers['x-date']) {272date = new Date(request.headers['x-date']);273} else {274date = new Date(request.headers.date);275}276var now = new Date();277var skew = Math.abs(now.getTime() - date.getTime());278279if (skew > options.clockSkew * 1000) {280throw new ExpiredRequestError('clock skew of ' +281(skew / 1000) +282's was greater than ' +283options.clockSkew + 's');284}285}286287options.headers.forEach(function (hdr) {288// Remember that we already checked any headers in the params289// were in the request, so if this passes we're good.290if (parsed.params.headers.indexOf(hdr) < 0)291throw new MissingHeaderError(hdr + ' was not a signed header');292});293294if (options.algorithms) {295if (options.algorithms.indexOf(parsed.params.algorithm) === -1)296throw new InvalidParamsError(parsed.params.algorithm +297' is not a supported algorithm');298}299300return parsed;301}302303};304305306