1/*!2* Copyright 2010 LearnBoost <[email protected]>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 at7*8* http://www.apache.org/licenses/LICENSE-2.09*10* Unless required by applicable law or agreed to in writing, software11* 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 and14* limitations under the License.15*/1617/**18* Module dependencies.19*/2021var crypto = require('crypto')22, parse = require('url').parse23;2425/**26* Valid keys.27*/2829var keys =30[ 'acl'31, 'location'32, 'logging'33, 'notification'34, 'partNumber'35, 'policy'36, 'requestPayment'37, 'torrent'38, 'uploadId'39, 'uploads'40, 'versionId'41, 'versioning'42, 'versions'43, 'website'44]4546/**47* Return an "Authorization" header value with the given `options`48* in the form of "AWS <key>:<signature>"49*50* @param {Object} options51* @return {String}52* @api private53*/5455function authorization (options) {56return 'AWS ' + options.key + ':' + sign(options)57}5859module.exports = authorization60module.exports.authorization = authorization6162/**63* Simple HMAC-SHA1 Wrapper64*65* @param {Object} options66* @return {String}67* @api private68*/6970function hmacSha1 (options) {71return crypto.createHmac('sha1', options.secret).update(options.message).digest('base64')72}7374module.exports.hmacSha1 = hmacSha17576/**77* Create a base64 sha1 HMAC for `options`.78*79* @param {Object} options80* @return {String}81* @api private82*/8384function sign (options) {85options.message = stringToSign(options)86return hmacSha1(options)87}88module.exports.sign = sign8990/**91* Create a base64 sha1 HMAC for `options`.92*93* Specifically to be used with S3 presigned URLs94*95* @param {Object} options96* @return {String}97* @api private98*/99100function signQuery (options) {101options.message = queryStringToSign(options)102return hmacSha1(options)103}104module.exports.signQuery= signQuery105106/**107* Return a string for sign() with the given `options`.108*109* Spec:110*111* <verb>\n112* <md5>\n113* <content-type>\n114* <date>\n115* [headers\n]116* <resource>117*118* @param {Object} options119* @return {String}120* @api private121*/122123function stringToSign (options) {124var headers = options.amazonHeaders || ''125if (headers) headers += '\n'126var r =127[ options.verb128, options.md5129, options.contentType130, options.date ? options.date.toUTCString() : ''131, headers + options.resource132]133return r.join('\n')134}135module.exports.stringToSign = stringToSign136137/**138* Return a string for sign() with the given `options`, but is meant exclusively139* for S3 presigned URLs140*141* Spec:142*143* <date>\n144* <resource>145*146* @param {Object} options147* @return {String}148* @api private149*/150151function queryStringToSign (options){152return 'GET\n\n\n' + options.date + '\n' + options.resource153}154module.exports.queryStringToSign = queryStringToSign155156/**157* Perform the following:158*159* - ignore non-amazon headers160* - lowercase fields161* - sort lexicographically162* - trim whitespace between ":"163* - join with newline164*165* @param {Object} headers166* @return {String}167* @api private168*/169170function canonicalizeHeaders (headers) {171var buf = []172, fields = Object.keys(headers)173;174for (var i = 0, len = fields.length; i < len; ++i) {175var field = fields[i]176, val = headers[field]177, field = field.toLowerCase()178;179if (0 !== field.indexOf('x-amz')) continue180buf.push(field + ':' + val)181}182return buf.sort().join('\n')183}184module.exports.canonicalizeHeaders = canonicalizeHeaders185186/**187* Perform the following:188*189* - ignore non sub-resources190* - sort lexicographically191*192* @param {String} resource193* @return {String}194* @api private195*/196197function canonicalizeResource (resource) {198var url = parse(resource, true)199, path = url.pathname200, buf = []201;202203Object.keys(url.query).forEach(function(key){204if (!~keys.indexOf(key)) return205var val = '' == url.query[key] ? '' : '=' + encodeURIComponent(url.query[key])206buf.push(key + val)207})208209return path + (buf.length ? '?' + buf.sort().join('&') : '')210}211module.exports.canonicalizeResource = canonicalizeResource212213214