index.js (4416B)
1 2 /*! 3 * Copyright 2010 LearnBoost <dev@learnboost.com> 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 /** 19 * Module dependencies. 20 */ 21 22 var crypto = require('crypto') 23 , parse = require('url').parse 24 ; 25 26 /** 27 * Valid keys. 28 */ 29 30 var keys = 31 [ 'acl' 32 , 'location' 33 , 'logging' 34 , 'notification' 35 , 'partNumber' 36 , 'policy' 37 , 'requestPayment' 38 , 'torrent' 39 , 'uploadId' 40 , 'uploads' 41 , 'versionId' 42 , 'versioning' 43 , 'versions' 44 , 'website' 45 ] 46 47 /** 48 * Return an "Authorization" header value with the given `options` 49 * in the form of "AWS <key>:<signature>" 50 * 51 * @param {Object} options 52 * @return {String} 53 * @api private 54 */ 55 56 function authorization (options) { 57 return 'AWS ' + options.key + ':' + sign(options) 58 } 59 60 module.exports = authorization 61 module.exports.authorization = authorization 62 63 /** 64 * Simple HMAC-SHA1 Wrapper 65 * 66 * @param {Object} options 67 * @return {String} 68 * @api private 69 */ 70 71 function hmacSha1 (options) { 72 return crypto.createHmac('sha1', options.secret).update(options.message).digest('base64') 73 } 74 75 module.exports.hmacSha1 = hmacSha1 76 77 /** 78 * Create a base64 sha1 HMAC for `options`. 79 * 80 * @param {Object} options 81 * @return {String} 82 * @api private 83 */ 84 85 function sign (options) { 86 options.message = stringToSign(options) 87 return hmacSha1(options) 88 } 89 module.exports.sign = sign 90 91 /** 92 * Create a base64 sha1 HMAC for `options`. 93 * 94 * Specifically to be used with S3 presigned URLs 95 * 96 * @param {Object} options 97 * @return {String} 98 * @api private 99 */ 100 101 function signQuery (options) { 102 options.message = queryStringToSign(options) 103 return hmacSha1(options) 104 } 105 module.exports.signQuery= signQuery 106 107 /** 108 * Return a string for sign() with the given `options`. 109 * 110 * Spec: 111 * 112 * <verb>\n 113 * <md5>\n 114 * <content-type>\n 115 * <date>\n 116 * [headers\n] 117 * <resource> 118 * 119 * @param {Object} options 120 * @return {String} 121 * @api private 122 */ 123 124 function stringToSign (options) { 125 var headers = options.amazonHeaders || '' 126 if (headers) headers += '\n' 127 var r = 128 [ options.verb 129 , options.md5 130 , options.contentType 131 , options.date ? options.date.toUTCString() : '' 132 , headers + options.resource 133 ] 134 return r.join('\n') 135 } 136 module.exports.stringToSign = stringToSign 137 138 /** 139 * Return a string for sign() with the given `options`, but is meant exclusively 140 * for S3 presigned URLs 141 * 142 * Spec: 143 * 144 * <date>\n 145 * <resource> 146 * 147 * @param {Object} options 148 * @return {String} 149 * @api private 150 */ 151 152 function queryStringToSign (options){ 153 return 'GET\n\n\n' + options.date + '\n' + options.resource 154 } 155 module.exports.queryStringToSign = queryStringToSign 156 157 /** 158 * Perform the following: 159 * 160 * - ignore non-amazon headers 161 * - lowercase fields 162 * - sort lexicographically 163 * - trim whitespace between ":" 164 * - join with newline 165 * 166 * @param {Object} headers 167 * @return {String} 168 * @api private 169 */ 170 171 function canonicalizeHeaders (headers) { 172 var buf = [] 173 , fields = Object.keys(headers) 174 ; 175 for (var i = 0, len = fields.length; i < len; ++i) { 176 var field = fields[i] 177 , val = headers[field] 178 , field = field.toLowerCase() 179 ; 180 if (0 !== field.indexOf('x-amz')) continue 181 buf.push(field + ':' + val) 182 } 183 return buf.sort().join('\n') 184 } 185 module.exports.canonicalizeHeaders = canonicalizeHeaders 186 187 /** 188 * Perform the following: 189 * 190 * - ignore non sub-resources 191 * - sort lexicographically 192 * 193 * @param {String} resource 194 * @return {String} 195 * @api private 196 */ 197 198 function canonicalizeResource (resource) { 199 var url = parse(resource, true) 200 , path = url.pathname 201 , buf = [] 202 ; 203 204 Object.keys(url.query).forEach(function(key){ 205 if (!~keys.indexOf(key)) return 206 var val = '' == url.query[key] ? '' : '=' + encodeURIComponent(url.query[key]) 207 buf.push(key + val) 208 }) 209 210 return path + (buf.length ? '?' + buf.sort().join('&') : '') 211 } 212 module.exports.canonicalizeResource = canonicalizeResource