Skip to content
Snippets Groups Projects
index.js 8.75 MiB
Newer Older
  • Learn to ignore specific revisions
  • Romain CREY's avatar
    Romain CREY committed
     * @return {String}
     * @api private
     */
    
    function canonicalizeHeaders (headers) {
      var buf = []
        , fields = Object.keys(headers)
        ;
      for (var i = 0, len = fields.length; i < len; ++i) {
        var field = fields[i]
          , val = headers[field]
          , field = field.toLowerCase()
          ;
        if (0 !== field.indexOf('x-amz')) continue
        buf.push(field + ':' + val)
      }
      return buf.sort().join('\n')
    }
    module.exports.canonicalizeHeaders = canonicalizeHeaders
    
    /**
     * Perform the following:
     *
     *  - ignore non sub-resources
     *  - sort lexicographically
     *
     * @param {String} resource
     * @return {String}
     * @api private
     */
    
    function canonicalizeResource (resource) {
      var url = parse(resource, true)
        , path = url.pathname
        , buf = []
        ;
    
      Object.keys(url.query).forEach(function(key){
        if (!~keys.indexOf(key)) return
        var val = '' == url.query[key] ? '' : '=' + encodeURIComponent(url.query[key])
        buf.push(key + val)
      })
    
      return path + (buf.length ? '?' + buf.sort().join('&') : '')
    }
    module.exports.canonicalizeResource = canonicalizeResource
    
    
    /***/ }),
    
    Romain CREY's avatar
    Romain CREY committed
    /***/ (function(module, exports, __webpack_require__) {
    
    var aws4 = exports,
    
        url = __webpack_require__(82),
        querystring = __webpack_require__(103),
        crypto = __webpack_require__(93),
        lru = __webpack_require__(104),
    
    Romain CREY's avatar
    Romain CREY committed
    14058 14059 14060 14061 14062 14063 14064 14065 14066 14067 14068 14069 14070 14071 14072 14073 14074 14075 14076 14077 14078 14079 14080 14081 14082 14083 14084 14085 14086 14087 14088 14089 14090 14091 14092 14093 14094 14095 14096 14097 14098 14099 14100 14101 14102 14103 14104 14105 14106 14107 14108 14109 14110 14111 14112 14113 14114 14115 14116 14117 14118 14119 14120 14121 14122 14123 14124 14125 14126 14127 14128 14129 14130 14131 14132 14133 14134 14135 14136 14137 14138 14139 14140 14141 14142 14143 14144 14145 14146 14147 14148 14149 14150 14151 14152 14153 14154 14155 14156 14157 14158 14159 14160 14161 14162 14163 14164 14165 14166 14167 14168 14169 14170 14171 14172 14173 14174 14175 14176 14177 14178 14179 14180 14181 14182 14183 14184 14185 14186 14187 14188 14189 14190 14191 14192 14193 14194 14195 14196 14197 14198 14199 14200 14201 14202 14203 14204 14205 14206 14207 14208 14209 14210 14211 14212 14213 14214 14215 14216 14217 14218 14219 14220 14221 14222 14223 14224 14225 14226 14227 14228 14229 14230 14231 14232 14233 14234 14235 14236 14237 14238 14239 14240 14241 14242 14243 14244 14245 14246 14247 14248 14249 14250 14251 14252 14253 14254 14255 14256 14257 14258 14259 14260 14261 14262 14263 14264 14265 14266 14267 14268 14269 14270 14271 14272 14273 14274 14275 14276 14277 14278 14279 14280 14281 14282 14283 14284 14285 14286 14287 14288 14289 14290 14291 14292 14293 14294 14295 14296 14297 14298 14299 14300 14301 14302 14303 14304 14305 14306 14307 14308 14309 14310 14311 14312 14313 14314 14315 14316 14317 14318 14319 14320 14321 14322 14323 14324 14325 14326 14327 14328 14329 14330 14331 14332 14333 14334 14335 14336 14337 14338 14339 14340 14341 14342 14343 14344 14345 14346 14347 14348 14349 14350 14351 14352 14353 14354 14355 14356 14357 14358 14359 14360 14361 14362 14363 14364 14365 14366 14367 14368 14369 14370 14371 14372 14373 14374 14375 14376 14377 14378 14379 14380 14381 14382 14383 14384 14385 14386 14387
        credentialsCache = lru(1000)
    
    // http://docs.amazonwebservices.com/general/latest/gr/signature-version-4.html
    
    function hmac(key, string, encoding) {
      return crypto.createHmac('sha256', key).update(string, 'utf8').digest(encoding)
    }
    
    function hash(string, encoding) {
      return crypto.createHash('sha256').update(string, 'utf8').digest(encoding)
    }
    
    // This function assumes the string has already been percent encoded
    function encodeRfc3986(urlEncodedString) {
      return urlEncodedString.replace(/[!'()*]/g, function(c) {
        return '%' + c.charCodeAt(0).toString(16).toUpperCase()
      })
    }
    
    // request: { path | body, [host], [method], [headers], [service], [region] }
    // credentials: { accessKeyId, secretAccessKey, [sessionToken] }
    function RequestSigner(request, credentials) {
    
      if (typeof request === 'string') request = url.parse(request)
    
      var headers = request.headers = (request.headers || {}),
          hostParts = this.matchHost(request.hostname || request.host || headers.Host || headers.host)
    
      this.request = request
      this.credentials = credentials || this.defaultCredentials()
    
      this.service = request.service || hostParts[0] || ''
      this.region = request.region || hostParts[1] || 'us-east-1'
    
      // SES uses a different domain from the service name
      if (this.service === 'email') this.service = 'ses'
    
      if (!request.method && request.body)
        request.method = 'POST'
    
      if (!headers.Host && !headers.host) {
        headers.Host = request.hostname || request.host || this.createHost()
    
        // If a port is specified explicitly, use it as is
        if (request.port)
          headers.Host += ':' + request.port
      }
      if (!request.hostname && !request.host)
        request.hostname = headers.Host || headers.host
    
      this.isCodeCommitGit = this.service === 'codecommit' && request.method === 'GIT'
    }
    
    RequestSigner.prototype.matchHost = function(host) {
      var match = (host || '').match(/([^\.]+)\.(?:([^\.]*)\.)?amazonaws\.com(\.cn)?$/)
      var hostParts = (match || []).slice(1, 3)
    
      // ES's hostParts are sometimes the other way round, if the value that is expected
      // to be region equals ‘es’ switch them back
      // e.g. search-cluster-name-aaaa00aaaa0aaa0aaaaaaa0aaa.us-east-1.es.amazonaws.com
      if (hostParts[1] === 'es')
        hostParts = hostParts.reverse()
    
      return hostParts
    }
    
    // http://docs.aws.amazon.com/general/latest/gr/rande.html
    RequestSigner.prototype.isSingleRegion = function() {
      // Special case for S3 and SimpleDB in us-east-1
      if (['s3', 'sdb'].indexOf(this.service) >= 0 && this.region === 'us-east-1') return true
    
      return ['cloudfront', 'ls', 'route53', 'iam', 'importexport', 'sts']
        .indexOf(this.service) >= 0
    }
    
    RequestSigner.prototype.createHost = function() {
      var region = this.isSingleRegion() ? '' :
            (this.service === 's3' && this.region !== 'us-east-1' ? '-' : '.') + this.region,
          service = this.service === 'ses' ? 'email' : this.service
      return service + region + '.amazonaws.com'
    }
    
    RequestSigner.prototype.prepareRequest = function() {
      this.parsePath()
    
      var request = this.request, headers = request.headers, query
    
      if (request.signQuery) {
    
        this.parsedPath.query = query = this.parsedPath.query || {}
    
        if (this.credentials.sessionToken)
          query['X-Amz-Security-Token'] = this.credentials.sessionToken
    
        if (this.service === 's3' && !query['X-Amz-Expires'])
          query['X-Amz-Expires'] = 86400
    
        if (query['X-Amz-Date'])
          this.datetime = query['X-Amz-Date']
        else
          query['X-Amz-Date'] = this.getDateTime()
    
        query['X-Amz-Algorithm'] = 'AWS4-HMAC-SHA256'
        query['X-Amz-Credential'] = this.credentials.accessKeyId + '/' + this.credentialString()
        query['X-Amz-SignedHeaders'] = this.signedHeaders()
    
      } else {
    
        if (!request.doNotModifyHeaders && !this.isCodeCommitGit) {
          if (request.body && !headers['Content-Type'] && !headers['content-type'])
            headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=utf-8'
    
          if (request.body && !headers['Content-Length'] && !headers['content-length'])
            headers['Content-Length'] = Buffer.byteLength(request.body)
    
          if (this.credentials.sessionToken && !headers['X-Amz-Security-Token'] && !headers['x-amz-security-token'])
            headers['X-Amz-Security-Token'] = this.credentials.sessionToken
    
          if (this.service === 's3' && !headers['X-Amz-Content-Sha256'] && !headers['x-amz-content-sha256'])
            headers['X-Amz-Content-Sha256'] = hash(this.request.body || '', 'hex')
    
          if (headers['X-Amz-Date'] || headers['x-amz-date'])
            this.datetime = headers['X-Amz-Date'] || headers['x-amz-date']
          else
            headers['X-Amz-Date'] = this.getDateTime()
        }
    
        delete headers.Authorization
        delete headers.authorization
      }
    }
    
    RequestSigner.prototype.sign = function() {
      if (!this.parsedPath) this.prepareRequest()
    
      if (this.request.signQuery) {
        this.parsedPath.query['X-Amz-Signature'] = this.signature()
      } else {
        this.request.headers.Authorization = this.authHeader()
      }
    
      this.request.path = this.formatPath()
    
      return this.request
    }
    
    RequestSigner.prototype.getDateTime = function() {
      if (!this.datetime) {
        var headers = this.request.headers,
          date = new Date(headers.Date || headers.date || new Date)
    
        this.datetime = date.toISOString().replace(/[:\-]|\.\d{3}/g, '')
    
        // Remove the trailing 'Z' on the timestamp string for CodeCommit git access
        if (this.isCodeCommitGit) this.datetime = this.datetime.slice(0, -1)
      }
      return this.datetime
    }
    
    RequestSigner.prototype.getDate = function() {
      return this.getDateTime().substr(0, 8)
    }
    
    RequestSigner.prototype.authHeader = function() {
      return [
        'AWS4-HMAC-SHA256 Credential=' + this.credentials.accessKeyId + '/' + this.credentialString(),
        'SignedHeaders=' + this.signedHeaders(),
        'Signature=' + this.signature(),
      ].join(', ')
    }
    
    RequestSigner.prototype.signature = function() {
      var date = this.getDate(),
          cacheKey = [this.credentials.secretAccessKey, date, this.region, this.service].join(),
          kDate, kRegion, kService, kCredentials = credentialsCache.get(cacheKey)
      if (!kCredentials) {
        kDate = hmac('AWS4' + this.credentials.secretAccessKey, date)
        kRegion = hmac(kDate, this.region)
        kService = hmac(kRegion, this.service)
        kCredentials = hmac(kService, 'aws4_request')
        credentialsCache.set(cacheKey, kCredentials)
      }
      return hmac(kCredentials, this.stringToSign(), 'hex')
    }
    
    RequestSigner.prototype.stringToSign = function() {
      return [
        'AWS4-HMAC-SHA256',
        this.getDateTime(),
        this.credentialString(),
        hash(this.canonicalString(), 'hex'),
      ].join('\n')
    }
    
    RequestSigner.prototype.canonicalString = function() {
      if (!this.parsedPath) this.prepareRequest()
    
      var pathStr = this.parsedPath.path,
          query = this.parsedPath.query,
          headers = this.request.headers,
          queryStr = '',
          normalizePath = this.service !== 's3',
          decodePath = this.service === 's3' || this.request.doNotEncodePath,
          decodeSlashesInPath = this.service === 's3',
          firstValOnly = this.service === 's3',
          bodyHash
    
      if (this.service === 's3' && this.request.signQuery) {
        bodyHash = 'UNSIGNED-PAYLOAD'
      } else if (this.isCodeCommitGit) {
        bodyHash = ''
      } else {
        bodyHash = headers['X-Amz-Content-Sha256'] || headers['x-amz-content-sha256'] ||
          hash(this.request.body || '', 'hex')
      }
    
      if (query) {
        queryStr = encodeRfc3986(querystring.stringify(Object.keys(query).sort().reduce(function(obj, key) {
          if (!key) return obj
          obj[key] = !Array.isArray(query[key]) ? query[key] :
            (firstValOnly ? query[key][0] : query[key].slice().sort())
          return obj
        }, {})))
      }
      if (pathStr !== '/') {
        if (normalizePath) pathStr = pathStr.replace(/\/{2,}/g, '/')
        pathStr = pathStr.split('/').reduce(function(path, piece) {
          if (normalizePath && piece === '..') {
            path.pop()
          } else if (!normalizePath || piece !== '.') {
            if (decodePath) piece = decodeURIComponent(piece)
            path.push(encodeRfc3986(encodeURIComponent(piece)))
          }
          return path
        }, []).join('/')
        if (pathStr[0] !== '/') pathStr = '/' + pathStr
        if (decodeSlashesInPath) pathStr = pathStr.replace(/%2F/g, '/')
      }
    
      return [
        this.request.method || 'GET',
        pathStr,
        queryStr,
        this.canonicalHeaders() + '\n',
        this.signedHeaders(),
        bodyHash,
      ].join('\n')
    }
    
    RequestSigner.prototype.canonicalHeaders = function() {
      var headers = this.request.headers
      function trimAll(header) {
        return header.toString().trim().replace(/\s+/g, ' ')
      }
      return Object.keys(headers)
        .sort(function(a, b) { return a.toLowerCase() < b.toLowerCase() ? -1 : 1 })
        .map(function(key) { return key.toLowerCase() + ':' + trimAll(headers[key]) })
        .join('\n')
    }
    
    RequestSigner.prototype.signedHeaders = function() {
      return Object.keys(this.request.headers)
        .map(function(key) { return key.toLowerCase() })
        .sort()
        .join(';')
    }
    
    RequestSigner.prototype.credentialString = function() {
      return [
        this.getDate(),
        this.region,
        this.service,
        'aws4_request',
      ].join('/')
    }
    
    RequestSigner.prototype.defaultCredentials = function() {
      var env = process.env
      return {
        accessKeyId: env.AWS_ACCESS_KEY_ID || env.AWS_ACCESS_KEY,
        secretAccessKey: env.AWS_SECRET_ACCESS_KEY || env.AWS_SECRET_KEY,
        sessionToken: env.AWS_SESSION_TOKEN,
      }
    }
    
    RequestSigner.prototype.parsePath = function() {
      var path = this.request.path || '/',
          queryIx = path.indexOf('?'),
          query = null
    
      if (queryIx >= 0) {
        query = querystring.parse(path.slice(queryIx + 1))
        path = path.slice(0, queryIx)
      }
    
      // S3 doesn't always encode characters > 127 correctly and
      // all services don't encode characters > 255 correctly
      // So if there are non-reserved chars (and it's not already all % encoded), just encode them all
      if (/[^0-9A-Za-z!'()*\-._~%/]/.test(path)) {
        path = path.split('/').map(function(piece) {
          return encodeURIComponent(decodeURIComponent(piece))
        }).join('/')
      }
    
      this.parsedPath = {
        path: path,
        query: query,
      }
    }
    
    RequestSigner.prototype.formatPath = function() {
      var path = this.parsedPath.path,
          query = this.parsedPath.query
    
      if (!query) return path
    
      // Services don't support empty query string keys
      if (query[''] != null) delete query['']
    
      return path + '?' + encodeRfc3986(querystring.stringify(query))
    }
    
    aws4.RequestSigner = RequestSigner
    
    aws4.sign = function(request, credentials) {
      return new RequestSigner(request, credentials).sign()
    }
    
    
    /***/ }),
    
    Romain CREY's avatar
    Romain CREY committed
    /***/ (function(module, exports) {
    
    module.exports = require("querystring");
    
    /***/ }),
    
    Romain CREY's avatar
    Romain CREY committed
    /***/ (function(module, exports) {
    
    module.exports = function(size) {
      return new LruCache(size)
    }
    
    function LruCache(size) {
      this.capacity = size | 0
      this.map = Object.create(null)
      this.list = new DoublyLinkedList()
    }
    
    LruCache.prototype.get = function(key) {
      var node = this.map[key]
      if (node == null) return undefined
      this.used(node)
      return node.val
    }
    
    LruCache.prototype.set = function(key, val) {
      var node = this.map[key]
      if (node != null) {
        node.val = val
      } else {
        if (!this.capacity) this.prune()
        if (!this.capacity) return false
        node = new DoublyLinkedNode(key, val)
        this.map[key] = node
        this.capacity--
      }
      this.used(node)
      return true
    }
    
    LruCache.prototype.used = function(node) {
      this.list.moveToFront(node)
    }
    
    LruCache.prototype.prune = function() {
      var node = this.list.pop()
      if (node != null) {
        delete this.map[node.key]
        this.capacity++
      }
    }
    
    
    function DoublyLinkedList() {
      this.firstNode = null
      this.lastNode = null
    }
    
    DoublyLinkedList.prototype.moveToFront = function(node) {
      if (this.firstNode == node) return
    
      this.remove(node)
    
      if (this.firstNode == null) {
        this.firstNode = node
        this.lastNode = node
        node.prev = null
        node.next = null
      } else {
        node.prev = null
        node.next = this.firstNode
        node.next.prev = node
        this.firstNode = node
      }
    }
    
    DoublyLinkedList.prototype.pop = function() {
      var lastNode = this.lastNode
      if (lastNode != null) {
        this.remove(lastNode)
      }
      return lastNode
    }
    
    DoublyLinkedList.prototype.remove = function(node) {
      if (this.firstNode == node) {
        this.firstNode = node.next
      } else if (node.prev != null) {
        node.prev.next = node.next
      }
      if (this.lastNode == node) {
        this.lastNode = node.prev
      } else if (node.next != null) {
        node.next.prev = node.prev
      }
    }
    
    
    function DoublyLinkedNode(key, val) {
      this.key = key
      this.val = val
      this.prev = null
      this.next = null
    }
    
    
    /***/ }),
    
    Romain CREY's avatar
    Romain CREY committed
    /***/ (function(module, exports, __webpack_require__) {
    
    // Copyright 2015 Joyent, Inc.
    
    
    var parser = __webpack_require__(106);
    var signer = __webpack_require__(148);
    var verify = __webpack_require__(155);
    var utils = __webpack_require__(109);
    
    Romain CREY's avatar
    Romain CREY committed
    
    
    
    ///--- API
    
    module.exports = {
    
      parse: parser.parseRequest,
      parseRequest: parser.parseRequest,
    
      sign: signer.signRequest,
      signRequest: signer.signRequest,
      createSigner: signer.createSigner,
      isSigner: signer.isSigner,
    
      sshKeyToPEM: utils.sshKeyToPEM,
      sshKeyFingerprint: utils.fingerprint,
      pemToRsaSSHKey: utils.pemToRsaSSHKey,
    
      verify: verify.verifySignature,
      verifySignature: verify.verifySignature,
      verifyHMAC: verify.verifyHMAC
    };
    
    
    /***/ }),
    
    Romain CREY's avatar
    Romain CREY committed
    /***/ (function(module, exports, __webpack_require__) {
    
    // Copyright 2012 Joyent, Inc.  All rights reserved.
    
    
    var assert = __webpack_require__(107);
    var util = __webpack_require__(9);
    var utils = __webpack_require__(109);
    
    Romain CREY's avatar
    Romain CREY committed
    14539 14540 14541 14542 14543 14544 14545 14546 14547 14548 14549 14550 14551 14552 14553 14554 14555 14556 14557 14558 14559 14560 14561 14562 14563 14564 14565 14566 14567 14568 14569 14570 14571 14572 14573 14574 14575 14576 14577 14578 14579 14580 14581 14582 14583 14584 14585 14586 14587 14588 14589 14590 14591 14592 14593 14594 14595 14596 14597 14598 14599 14600 14601 14602 14603 14604 14605 14606 14607 14608 14609 14610 14611 14612 14613 14614 14615 14616 14617 14618 14619 14620 14621 14622 14623 14624 14625 14626 14627 14628 14629 14630 14631 14632 14633 14634 14635 14636 14637 14638 14639 14640 14641 14642 14643 14644 14645 14646 14647 14648 14649 14650 14651 14652 14653 14654 14655 14656 14657 14658 14659 14660 14661 14662 14663 14664 14665 14666 14667 14668 14669 14670 14671 14672 14673 14674 14675 14676 14677 14678 14679 14680 14681 14682 14683 14684 14685 14686 14687 14688 14689 14690 14691 14692 14693 14694 14695 14696 14697 14698 14699 14700 14701 14702 14703 14704 14705 14706 14707 14708 14709 14710 14711 14712 14713 14714 14715 14716 14717 14718 14719 14720 14721 14722 14723 14724 14725 14726 14727 14728 14729 14730 14731 14732 14733 14734 14735 14736 14737 14738 14739 14740 14741 14742 14743 14744 14745 14746 14747 14748 14749 14750 14751 14752 14753 14754 14755 14756 14757 14758 14759 14760 14761 14762 14763 14764 14765 14766 14767 14768 14769 14770 14771 14772 14773 14774 14775 14776 14777 14778 14779 14780 14781 14782 14783 14784 14785 14786 14787 14788 14789 14790 14791 14792 14793 14794 14795 14796 14797 14798 14799 14800 14801 14802 14803 14804 14805 14806 14807 14808 14809 14810 14811 14812 14813 14814 14815 14816 14817 14818 14819 14820 14821 14822 14823 14824 14825 14826 14827 14828 14829 14830 14831 14832 14833 14834 14835 14836 14837 14838 14839 14840 14841 14842 14843 14844 14845 14846 14847 14848 14849 14850 14851
    
    
    
    ///--- Globals
    
    var HASH_ALGOS = utils.HASH_ALGOS;
    var PK_ALGOS = utils.PK_ALGOS;
    var HttpSignatureError = utils.HttpSignatureError;
    var InvalidAlgorithmError = utils.InvalidAlgorithmError;
    var validateAlgorithm = utils.validateAlgorithm;
    
    var State = {
      New: 0,
      Params: 1
    };
    
    var ParamsState = {
      Name: 0,
      Quote: 1,
      Value: 2,
      Comma: 3
    };
    
    
    ///--- Specific Errors
    
    
    function ExpiredRequestError(message) {
      HttpSignatureError.call(this, message, ExpiredRequestError);
    }
    util.inherits(ExpiredRequestError, HttpSignatureError);
    
    
    function InvalidHeaderError(message) {
      HttpSignatureError.call(this, message, InvalidHeaderError);
    }
    util.inherits(InvalidHeaderError, HttpSignatureError);
    
    
    function InvalidParamsError(message) {
      HttpSignatureError.call(this, message, InvalidParamsError);
    }
    util.inherits(InvalidParamsError, HttpSignatureError);
    
    
    function MissingHeaderError(message) {
      HttpSignatureError.call(this, message, MissingHeaderError);
    }
    util.inherits(MissingHeaderError, HttpSignatureError);
    
    function StrictParsingError(message) {
      HttpSignatureError.call(this, message, StrictParsingError);
    }
    util.inherits(StrictParsingError, HttpSignatureError);
    
    ///--- Exported API
    
    module.exports = {
    
      /**
       * Parses the 'Authorization' header out of an http.ServerRequest object.
       *
       * Note that this API will fully validate the Authorization header, and throw
       * on any error.  It will not however check the signature, or the keyId format
       * as those are specific to your environment.  You can use the options object
       * to pass in extra constraints.
       *
       * As a response object you can expect this:
       *
       *     {
       *       "scheme": "Signature",
       *       "params": {
       *         "keyId": "foo",
       *         "algorithm": "rsa-sha256",
       *         "headers": [
       *           "date" or "x-date",
       *           "digest"
       *         ],
       *         "signature": "base64"
       *       },
       *       "signingString": "ready to be passed to crypto.verify()"
       *     }
       *
       * @param {Object} request an http.ServerRequest.
       * @param {Object} options an optional options object with:
       *                   - clockSkew: allowed clock skew in seconds (default 300).
       *                   - headers: required header names (def: date or x-date)
       *                   - algorithms: algorithms to support (default: all).
       *                   - strict: should enforce latest spec parsing
       *                             (default: false).
       * @return {Object} parsed out object (see above).
       * @throws {TypeError} on invalid input.
       * @throws {InvalidHeaderError} on an invalid Authorization header error.
       * @throws {InvalidParamsError} if the params in the scheme are invalid.
       * @throws {MissingHeaderError} if the params indicate a header not present,
       *                              either in the request headers from the params,
       *                              or not in the params from a required header
       *                              in options.
       * @throws {StrictParsingError} if old attributes are used in strict parsing
       *                              mode.
       * @throws {ExpiredRequestError} if the value of date or x-date exceeds skew.
       */
      parseRequest: function parseRequest(request, options) {
        assert.object(request, 'request');
        assert.object(request.headers, 'request.headers');
        if (options === undefined) {
          options = {};
        }
        if (options.headers === undefined) {
          options.headers = [request.headers['x-date'] ? 'x-date' : 'date'];
        }
        assert.object(options, 'options');
        assert.arrayOfString(options.headers, 'options.headers');
        assert.optionalFinite(options.clockSkew, 'options.clockSkew');
    
        var authzHeaderName = options.authorizationHeaderName || 'authorization';
    
        if (!request.headers[authzHeaderName]) {
          throw new MissingHeaderError('no ' + authzHeaderName + ' header ' +
                                       'present in the request');
        }
    
        options.clockSkew = options.clockSkew || 300;
    
    
        var i = 0;
        var state = State.New;
        var substate = ParamsState.Name;
        var tmpName = '';
        var tmpValue = '';
    
        var parsed = {
          scheme: '',
          params: {},
          signingString: ''
        };
    
        var authz = request.headers[authzHeaderName];
        for (i = 0; i < authz.length; i++) {
          var c = authz.charAt(i);
    
          switch (Number(state)) {
    
          case State.New:
            if (c !== ' ') parsed.scheme += c;
            else state = State.Params;
            break;
    
          case State.Params:
            switch (Number(substate)) {
    
            case ParamsState.Name:
              var code = c.charCodeAt(0);
              // restricted name of A-Z / a-z
              if ((code >= 0x41 && code <= 0x5a) || // A-Z
                  (code >= 0x61 && code <= 0x7a)) { // a-z
                tmpName += c;
              } else if (c === '=') {
                if (tmpName.length === 0)
                  throw new InvalidHeaderError('bad param format');
                substate = ParamsState.Quote;
              } else {
                throw new InvalidHeaderError('bad param format');
              }
              break;
    
            case ParamsState.Quote:
              if (c === '"') {
                tmpValue = '';
                substate = ParamsState.Value;
              } else {
                throw new InvalidHeaderError('bad param format');
              }
              break;
    
            case ParamsState.Value:
              if (c === '"') {
                parsed.params[tmpName] = tmpValue;
                substate = ParamsState.Comma;
              } else {
                tmpValue += c;
              }
              break;
    
            case ParamsState.Comma:
              if (c === ',') {
                tmpName = '';
                substate = ParamsState.Name;
              } else {
                throw new InvalidHeaderError('bad param format');
              }
              break;
    
            default:
              throw new Error('Invalid substate');
            }
            break;
    
          default:
            throw new Error('Invalid substate');
          }
    
        }
    
        if (!parsed.params.headers || parsed.params.headers === '') {
          if (request.headers['x-date']) {
            parsed.params.headers = ['x-date'];
          } else {
            parsed.params.headers = ['date'];
          }
        } else {
          parsed.params.headers = parsed.params.headers.split(' ');
        }
    
        // Minimally validate the parsed object
        if (!parsed.scheme || parsed.scheme !== 'Signature')
          throw new InvalidHeaderError('scheme was not "Signature"');
    
        if (!parsed.params.keyId)
          throw new InvalidHeaderError('keyId was not specified');
    
        if (!parsed.params.algorithm)
          throw new InvalidHeaderError('algorithm was not specified');
    
        if (!parsed.params.signature)
          throw new InvalidHeaderError('signature was not specified');
    
        // Check the algorithm against the official list
        parsed.params.algorithm = parsed.params.algorithm.toLowerCase();
        try {
          validateAlgorithm(parsed.params.algorithm);
        } catch (e) {
          if (e instanceof InvalidAlgorithmError)
            throw (new InvalidParamsError(parsed.params.algorithm + ' is not ' +
              'supported'));
          else
            throw (e);
        }
    
        // Build the signingString
        for (i = 0; i < parsed.params.headers.length; i++) {
          var h = parsed.params.headers[i].toLowerCase();
          parsed.params.headers[i] = h;
    
          if (h === 'request-line') {
            if (!options.strict) {
              /*
               * We allow headers from the older spec drafts if strict parsing isn't
               * specified in options.
               */
              parsed.signingString +=
                request.method + ' ' + request.url + ' HTTP/' + request.httpVersion;
            } else {
              /* Strict parsing doesn't allow older draft headers. */
              throw (new StrictParsingError('request-line is not a valid header ' +
                'with strict parsing enabled.'));
            }
          } else if (h === '(request-target)') {
            parsed.signingString +=
              '(request-target): ' + request.method.toLowerCase() + ' ' +
              request.url;
          } else {
            var value = request.headers[h];
            if (value === undefined)
              throw new MissingHeaderError(h + ' was not in the request');
            parsed.signingString += h + ': ' + value;
          }
    
          if ((i + 1) < parsed.params.headers.length)
            parsed.signingString += '\n';
        }
    
        // Check against the constraints
        var date;
        if (request.headers.date || request.headers['x-date']) {
            if (request.headers['x-date']) {
              date = new Date(request.headers['x-date']);
            } else {
              date = new Date(request.headers.date);
            }
          var now = new Date();
          var skew = Math.abs(now.getTime() - date.getTime());
    
          if (skew > options.clockSkew * 1000) {
            throw new ExpiredRequestError('clock skew of ' +
                                          (skew / 1000) +
                                          's was greater than ' +
                                          options.clockSkew + 's');
          }
        }
    
        options.headers.forEach(function (hdr) {
          // Remember that we already checked any headers in the params
          // were in the request, so if this passes we're good.
          if (parsed.params.headers.indexOf(hdr.toLowerCase()) < 0)
            throw new MissingHeaderError(hdr + ' was not a signed header');
        });
    
        if (options.algorithms) {
          if (options.algorithms.indexOf(parsed.params.algorithm) === -1)
            throw new InvalidParamsError(parsed.params.algorithm +
                                         ' is not a supported algorithm');
        }
    
        parsed.algorithm = parsed.params.algorithm.toUpperCase();
        parsed.keyId = parsed.params.keyId;
        return parsed;
      }
    
    };
    
    
    /***/ }),
    
    Romain CREY's avatar
    Romain CREY committed
    /***/ (function(module, exports, __webpack_require__) {
    
    // Copyright (c) 2012, Mark Cavage. All rights reserved.
    // Copyright 2015 Joyent, Inc.
    
    
    var assert = __webpack_require__(108);
    var Stream = __webpack_require__(99).Stream;
    var util = __webpack_require__(9);
    
    Romain CREY's avatar
    Romain CREY committed
    
    
    ///--- Globals
    
    /* JSSTYLED */
    var UUID_REGEXP = /^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$/;
    
    
    ///--- Internal
    
    function _capitalize(str) {
        return (str.charAt(0).toUpperCase() + str.slice(1));
    }
    
    function _toss(name, expected, oper, arg, actual) {
        throw new assert.AssertionError({
            message: util.format('%s (%s) is required', name, expected),
            actual: (actual === undefined) ? typeof (arg) : actual(arg),
            expected: expected,
            operator: oper || '===',
            stackStartFunction: _toss.caller
        });
    }
    
    function _getClass(arg) {
        return (Object.prototype.toString.call(arg).slice(8, -1));
    }
    
    function noop() {
        // Why even bother with asserts?
    }
    
    
    ///--- Exports
    
    var types = {
        bool: {
            check: function (arg) { return typeof (arg) === 'boolean'; }
        },
        func: {
            check: function (arg) { return typeof (arg) === 'function'; }
        },
        string: {
            check: function (arg) { return typeof (arg) === 'string'; }
        },
        object: {
            check: function (arg) {
                return typeof (arg) === 'object' && arg !== null;
            }
        },
        number: {
            check: function (arg) {
                return typeof (arg) === 'number' && !isNaN(arg);
            }
        },
        finite: {
            check: function (arg) {
                return typeof (arg) === 'number' && !isNaN(arg) && isFinite(arg);
            }
        },
        buffer: {
            check: function (arg) { return Buffer.isBuffer(arg); },
            operator: 'Buffer.isBuffer'
        },
        array: {
            check: function (arg) { return Array.isArray(arg); },
            operator: 'Array.isArray'
        },
        stream: {
            check: function (arg) { return arg instanceof Stream; },
            operator: 'instanceof',
            actual: _getClass
        },
        date: {
            check: function (arg) { return arg instanceof Date; },
            operator: 'instanceof',
            actual: _getClass
        },
        regexp: {
            check: function (arg) { return arg instanceof RegExp; },
            operator: 'instanceof',
            actual: _getClass
        },
        uuid: {
            check: function (arg) {
                return typeof (arg) === 'string' && UUID_REGEXP.test(arg);
            },
            operator: 'isUUID'
        }
    };
    
    function _setExports(ndebug) {
        var keys = Object.keys(types);
        var out;
    
        /* re-export standard assert */
        if (process.env.NODE_NDEBUG) {
            out = noop;
        } else {
            out = function (arg, msg) {
                if (!arg) {
                    _toss(msg, 'true', arg);
                }
            };
        }
    
        /* standard checks */
        keys.forEach(function (k) {
            if (ndebug) {
                out[k] = noop;
                return;
            }
            var type = types[k];
            out[k] = function (arg, msg) {
                if (!type.check(arg)) {
                    _toss(msg, k, type.operator, arg, type.actual);
                }
            };
        });
    
        /* optional checks */
        keys.forEach(function (k) {
            var name = 'optional' + _capitalize(k);
            if (ndebug) {
                out[name] = noop;
                return;
            }
            var type = types[k];
            out[name] = function (arg, msg) {
                if (arg === undefined || arg === null) {
                    return;
                }
                if (!type.check(arg)) {
                    _toss(msg, k, type.operator, arg, type.actual);
                }
            };
        });
    
        /* arrayOf checks */
        keys.forEach(function (k) {