index.js 4.3KB

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