Comparing version 1.1.0 to 1.2.0
111
aws4.js
@@ -18,2 +18,8 @@ var aws4 = exports, | ||
function encodeRfc3986(string) { | ||
return querystring.escape(string).replace(/[!'()*]/g, function(c) { | ||
return '%' + c.charCodeAt(0).toString(16).toUpperCase() | ||
}) | ||
} | ||
// request: { path | body, [host], [method], [headers], [service], [region] } | ||
@@ -75,9 +81,11 @@ // credentials: { accessKeyId, secretAccessKey, [sessionToken] } | ||
RequestSigner.prototype.sign = function() { | ||
var request = this.request, headers = request.headers, parsedUrl, query | ||
RequestSigner.prototype.prepareRequest = function() { | ||
this.parsePath() | ||
var request = this.request, headers = request.headers, query | ||
if (request.signQuery) { | ||
parsedUrl = url.parse(request.path || '/', true) | ||
query = parsedUrl.query | ||
this.parsedPath.query = query = this.parsedPath.query || {} | ||
if (this.credentials.sessionToken) | ||
@@ -98,7 +106,2 @@ query['X-Amz-Security-Token'] = this.credentials.sessionToken | ||
delete parsedUrl.search | ||
request.path = url.format(parsedUrl) | ||
request.path += '&X-Amz-Signature=' + this.signature() | ||
} else { | ||
@@ -127,6 +130,17 @@ | ||
delete headers.authorization | ||
headers.Authorization = this.authHeader() | ||
} | ||
} | ||
return request | ||
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 | ||
} | ||
@@ -180,18 +194,40 @@ | ||
RequestSigner.prototype.canonicalString = function() { | ||
var pathStr = this.request.path || '/', | ||
queryIx = pathStr.indexOf('?'), | ||
if (!this.parsedPath) this.prepareRequest() | ||
var pathStr = this.parsedPath.path, | ||
query = this.parsedPath.query, | ||
queryStr = '', | ||
normalizePath = this.service !== 's3', | ||
decodePath = this.service === 's3' || this.request.doNotEncodePath, | ||
decodeSlashesInPath = this.service === 's3', | ||
firstValOnly = this.service === 's3', | ||
bodyHash = this.service === 's3' && this.request.signQuery ? | ||
'UNSIGNED-PAYLOAD' : hash(this.request.body || '', 'hex') | ||
if (queryIx >= 0) { | ||
var query = querystring.parse(pathStr.slice(queryIx + 1)) | ||
pathStr = pathStr.slice(0, queryIx) | ||
if (query) { | ||
queryStr = querystring.stringify(Object.keys(query).sort().reduce(function(obj, key) { | ||
obj[key] = Array.isArray(query[key]) ? query[key].sort() : query[key] | ||
if (!key) return obj | ||
obj[key] = !Array.isArray(query[key]) ? query[key] : | ||
(firstValOnly ? query[key][0] : query[key].slice().sort()) | ||
return obj | ||
}, {})).replace(/[!'()*]/g, function(c) { return '%' + c.charCodeAt(0).toString(16).toUpperCase() }) | ||
}, {}), null, null, {encodeURIComponent: encodeRfc3986}) | ||
} | ||
if (pathStr !== '/') { | ||
if (normalizePath) pathStr = pathStr.replace(/\/{2,}/g, '/') | ||
if (pathStr[0] === '/') pathStr = pathStr.slice(1) | ||
pathStr = '/' + pathStr.split('/').reduce(function(path, piece) { | ||
if (normalizePath && piece === '..') { | ||
path.pop() | ||
} else if (!normalizePath || piece !== '.') { | ||
if (decodePath) piece = querystring.unescape(piece) | ||
path.push(encodeRfc3986(piece)) | ||
} | ||
return path | ||
}, []).join('/') | ||
if (decodeSlashesInPath) pathStr = pathStr.replace(/%2F/g, '/') | ||
} | ||
return [ | ||
this.request.method || 'GET', | ||
url.resolve('/', pathStr.replace(/\/{2,}/g, '/')) || '/', | ||
pathStr, | ||
queryStr, | ||
@@ -240,2 +276,39 @@ this.canonicalHeaders() + '\n', | ||
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 querystring.escape(querystring.unescape(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 + '?' + querystring.stringify(query, null, null, {encodeURIComponent: encodeRfc3986}) | ||
} | ||
aws4.RequestSigner = RequestSigner | ||
@@ -242,0 +315,0 @@ |
{ | ||
"name": "aws4", | ||
"version": "1.1.0", | ||
"version": "1.2.0", | ||
"description": "Signs and prepares requests using AWS Signature Version 4", | ||
@@ -5,0 +5,0 @@ "author": "Michael Hart <michael.hart.au@gmail.com> (http://github.com/mhart)", |
38824
569