+126
-7
@@ -19,3 +19,5 @@ /*! | ||
| , fs = require('fs') | ||
| , crypto = require('crypto'); | ||
| , crypto = require('crypto') | ||
| , xml2js = require('xml2js') | ||
| , qs = require('querystring'); | ||
@@ -44,2 +46,11 @@ // The max for multi-object delete, bucket listings, etc. | ||
| function getContentLength(headers) { | ||
| for (var header in headers) { | ||
| if (header.toLowerCase() === 'content-length') { | ||
| return headers[header]; | ||
| } | ||
| } | ||
| return null; | ||
| } | ||
| /** | ||
@@ -69,3 +80,12 @@ * Initialize a `Client` with the given `options`. | ||
| this.endpoint = options.bucket + '.s3.amazonaws.com'; | ||
| var domain = 's3.amazonaws.com'; | ||
| if (options.region) { | ||
| if (options.region === 'us-standard') { | ||
| // Pesky inconsistency | ||
| domain = 's3.amazonaws.com'; | ||
| } else { | ||
| domain = 's3-' + options.region + '.amazonaws.com'; | ||
| } | ||
| } | ||
| this.endpoint = options.bucket + '.' + domain; | ||
| this.secure = 'undefined' == typeof options.port; | ||
@@ -88,3 +108,3 @@ utils.merge(this, options); | ||
| Client.prototype.request = function(method, filename, headers){ | ||
| var options = { host: this.endpoint } | ||
| var options = { host: this.endpoint, agent: this.agent } | ||
| , date = new Date | ||
@@ -234,3 +254,4 @@ , headers = headers || {}; | ||
| Client.prototype.putStream = function(stream, filename, headers, fn){ | ||
| if (!('Content-Length' in headers)) { | ||
| var contentLength = getContentLength(headers); | ||
| if (contentLength === null) { | ||
| process.nextTick(function () { | ||
@@ -249,9 +270,8 @@ fn(new Error('You must specify a Content-Length header.')); | ||
| var written = 0; | ||
| var total = headers['Content-Length']; | ||
| stream.on('data', function(chunk){ | ||
| written += chunk.length; | ||
| req.emit('progress', { | ||
| percent: written / total * 100 | 0 | ||
| percent: written / contentLength * 100 | 0 | ||
| , written: written | ||
| , total: total | ||
| , total: contentLength | ||
| }); | ||
@@ -487,2 +507,101 @@ }); | ||
| /** | ||
| * Possible params for Client#list. | ||
| * | ||
| * @type {Object} | ||
| */ | ||
| var LIST_PARAMS = { | ||
| delimiter: true | ||
| , marker: true | ||
| ,'max-keys': true | ||
| , prefix: true | ||
| }; | ||
| /** | ||
| * Normalization map for Client#list. | ||
| * | ||
| * @type {Object} | ||
| */ | ||
| var RESPONSE_NORMALIZATION = { | ||
| MaxKeys: Number, | ||
| IsTruncated: Boolean, | ||
| LastModified: Date, | ||
| Size: Number | ||
| }; | ||
| /** | ||
| * Convert data we get from S3 xml in Client#list, since every primitive | ||
| * value there is a string. | ||
| * | ||
| * @type {Object} | ||
| */ | ||
| function normalizeResponse(data) { | ||
| for (var key in data) { | ||
| var Constr = RESPONSE_NORMALIZATION[key]; | ||
| if (Constr) { | ||
| if (Constr === Date) { | ||
| data[key] = new Date(data[key]); | ||
| } else { | ||
| data[key] = Constr(data[key]); | ||
| } | ||
| } else if (Array.isArray(data[key])) { | ||
| data[key].forEach(normalizeResponse); | ||
| } | ||
| } | ||
| } | ||
| /** | ||
| * List up to 1000 objects at a time, with optional `headers`, `params` | ||
| * and callback `fn` with a possible exception and the response. | ||
| * | ||
| * @param {Object|Function} params | ||
| * @param {Object|Function} headers | ||
| * @param {Function} fn | ||
| * @api public | ||
| */ | ||
| Client.prototype.list = function(params, headers, fn){ | ||
| if ('function' == typeof headers) { | ||
| fn = headers; | ||
| headers = {}; | ||
| } | ||
| if ('function' == typeof params) { | ||
| fn = params; | ||
| params = null; | ||
| } | ||
| if (params && !LIST_PARAMS[Object.keys(params)[0]]) { | ||
| headers = params; | ||
| params = null; | ||
| } | ||
| var url = params ? '?' + qs.stringify(params) : ''; | ||
| this.getFile(url, headers, function(err, res){ | ||
| if (err) return fn(err); | ||
| var xmlStr = ''; | ||
| res.on('data', function(chunk){ | ||
| xmlStr += chunk; | ||
| }); | ||
| res.on('end', function(){ | ||
| new xml2js.Parser({explicitArray: false, explicitRoot: false}) | ||
| .parseString(xmlStr, function(err, data){ | ||
| if (err) return fn(err); | ||
| delete data.$; | ||
| normalizeResponse(data); | ||
| fn(null, data); | ||
| }); | ||
| }).on('error', fn); | ||
| }); | ||
| }; | ||
| /** | ||
| * Return a url to the given `filename`. | ||
@@ -489,0 +608,0 @@ * |
+6
-4
@@ -5,7 +5,8 @@ { | ||
| "keywords": ["aws", "amazon", "s3"], | ||
| "version": "0.3.1", | ||
| "version": "0.4.0", | ||
| "author": "TJ Holowaychuk <tj@learnboost.com>", | ||
| "contributors": [ | ||
| "TJ Holowaychuk <tj@learnboost.com>", | ||
| "Domenic Denicola <domenic@domenicdenicola.com>" | ||
| "Domenic Denicola <domenic@domenicdenicola.com>", | ||
| "Oleg Slobodksoi <oleg008@gmail.com>" | ||
| ], | ||
@@ -22,3 +23,4 @@ "license": "MIT", | ||
| "dependencies": { | ||
| "mime": "*" | ||
| "mime": "*", | ||
| "xml2js": "0.2.x" | ||
| }, | ||
@@ -29,3 +31,3 @@ "devDependencies": { | ||
| "scripts": { | ||
| "test": "mocha --slow 500ms --reporter spec" | ||
| "test": "mocha" | ||
| }, | ||
@@ -32,0 +34,0 @@ "directories": { |
+97
-14
@@ -26,6 +26,3 @@ # knox | ||
| By default knox will send all requests to the global endpoint | ||
| (bucket.s3.amazonaws.com). This works regardless of the region where the bucket | ||
| is. But if you want to manually set the endpoint (for performance reasons) you | ||
| can do it with the `endpoint` option. | ||
| More options are documented below for features like other endpoints or regions. | ||
@@ -55,7 +52,6 @@ ### PUT | ||
| By default the _x-amz-acl_ header is _public-read_, meaning anyone can __GET__ | ||
| the file. To alter this simply pass this header to the client request method. | ||
| By default the _x-amz-acl_ header is _private_. To alter this simply pass this header to the client request method. | ||
| ```js | ||
| client.put('/test/obj.json', { 'x-amz-acl': 'private' }); | ||
| client.put('/test/obj.json', { 'x-amz-acl': 'public-read' }); | ||
| ``` | ||
@@ -181,2 +177,29 @@ | ||
| and [listing all the files in your bucket][list]: | ||
| ```js | ||
| client.list({ prefix: 'my-prefix' }, function(err, data){ | ||
| /* `data` will look roughly like: | ||
| { | ||
| Prefix: 'my-prefix', | ||
| IsTruncated: true, | ||
| MaxKeys: 1000, | ||
| Contents: [ | ||
| { | ||
| Key: 'whatever' | ||
| LastModified: new Date(2012, 11, 25, 0, 0, 0), | ||
| ETag: 'whatever', | ||
| Size: 123, | ||
| Owner: 'you', | ||
| StorageClass: 'whatever' | ||
| }, | ||
| ⋮ | ||
| ] | ||
| } | ||
| */ | ||
| }); | ||
| ``` | ||
| And you can always issue ad-hoc requests, e.g. the following to | ||
@@ -192,14 +215,72 @@ [get an object's ACL][acl]: | ||
| [list]: http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTBucketGET.html | ||
| [acl]: http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTObjectGETacl.html | ||
| ## Client Creation Options | ||
| Besides the required `key`, `secret`, and `bucket` options, you can supply any | ||
| of the following: | ||
| ### `endpoint` | ||
| By default knox will send all requests to the global endpoint | ||
| (bucket.s3.amazonaws.com). This works regardless of the region where the bucket | ||
| is. But if you want to manually set the endpoint (for performance reasons) you | ||
| can do it with the `endpoint` option. | ||
| ### `region` | ||
| For your convenience when using buckets not in the US Standard region, you can | ||
| specify the `region` option. When you do so, the `endpoint` hostname is | ||
| automatically assembled. | ||
| As of this writing, valid values for the `region` option are: | ||
| * US Standard (default): `us-standard` | ||
| * US West (Oregon): `us-west-2` | ||
| * US West (Northern California): `us-west-1` | ||
| * EU (Ireland): `eu-west-1` | ||
| * Asia Pacific (Singapore): `ap-southeast-1` | ||
| * Asia Pacific (Tokyo): `ap-northeast-1` | ||
| * South America (Sao Paulo): `sa-east-1` | ||
| If new regions are added later, their subdomain names will also work when passed | ||
| as the `region` option. See the [AWS endpoint documentation][endpoint-docs] for | ||
| the latest list. | ||
| **Convenience APIs such as `putFile` and `putStream` currently do not work as | ||
| expected with buckets in regions other than US Standard without explicitly | ||
| specify the region option.** This will eventually be addressed by resolving | ||
| [issue #66][]; however, for performance reasons, it is always best to specify | ||
| the region option anyway. | ||
| [endpoint-docs]: http://docs.amazonwebservices.com/general/latest/gr/rande.html#s3_region | ||
| [issue #66]: https://github.com/LearnBoost/knox/issues/66 | ||
| ### `secure` and `port` | ||
| By default, knox uses HTTPS to connect to S3 on port 443. You can override | ||
| either of these with the `secure` and `port` options. Note that if you specify a | ||
| custom `port` option, the default for `secure` switches to `false`, although | ||
| you can override it manually if you want to run HTTPS against a specific port. | ||
| ### `agent` | ||
| If you want to use a custom [HTTP agent][], you can specify this with the | ||
| `agent` option. | ||
| [HTTP agent]: http://nodejs.org/docs/latest/api/http.html#http_class_http_agent | ||
| ## Running Tests | ||
| To run the test suite you must first have an S3 account, and create | ||
| a file named _./auth_, which contains your credentials as json, for example: | ||
| To run the test suite you must first have an S3 account. Then create a file named | ||
| _./test/auth.json_, which contains your credentials as JSON, for example: | ||
| ```json | ||
| { | ||
| "key":"<api-key-here>", | ||
| "secret":"<secret-here>", | ||
| "bucket":"<your-bucket-name>" | ||
| "key": "<api-key-here>", | ||
| "secret": "<secret-here>", | ||
| "bucket": "<your-bucket-name>", | ||
| "bucketUsWest2": "<bucket-in-us-west-2-region-here>" | ||
| } | ||
@@ -210,3 +291,5 @@ ``` | ||
| $ npm install | ||
| $ npm test | ||
| ``` | ||
| $ npm install | ||
| $ npm test | ||
| ``` |
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Non-existent author
Supply chain riskThe package was published by an npm account that no longer exists.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
30541
16.19%802
14.41%291
39.9%0
-100%2
100%3
50%+ Added
+ Added
+ Added