Comparing version 0.0.9 to 0.0.10
@@ -0,0 +0,0 @@ |
module.exports = require('./lib/knox'); |
@@ -182,5 +182,7 @@ | ||
Object.keys(url.query).forEach(function(key){ | ||
if (!~keys.indexOf(key)) return; | ||
var val = '' == url.query[key] ? '' : '=' + encodeURIComponent(url.query[key]); | ||
buf.push(key + val); | ||
if (key === '') // this is something like ?delete (which is part of the S3 api) so it has no key because there is no '=' | ||
buf.push(val.substring(1)); | ||
else | ||
buf.push(key + val); | ||
}); | ||
@@ -187,0 +189,0 @@ |
@@ -16,6 +16,9 @@ | ||
, url = require('url') | ||
, join = require('path').join | ||
, mime = require('./mime') | ||
, fs = require('fs'); | ||
, fs = require('fs') | ||
, crypto = require('crypto'); | ||
// The max for multi-object delete, bucket listings, etc. | ||
var BUCKET_OPS_MAX = 1000; | ||
/** | ||
@@ -40,2 +43,3 @@ * Initialize a `Client` with the given `options`. | ||
this.endpoint = options.bucket + '.s3.amazonaws.com'; | ||
this.port = 80; | ||
utils.merge(this, options); | ||
@@ -55,3 +59,3 @@ }; | ||
Client.prototype.request = function(method, filename, headers){ | ||
var options = { host: this.endpoint, port: 80 } | ||
var options = { host: this.endpoint, port: this.port } | ||
, date = new Date | ||
@@ -72,4 +76,5 @@ , headers = headers || {}; | ||
, date: date | ||
, resource: auth.canonicalizeResource(join('/', this.bucket, filename)) | ||
, resource: auth.canonicalizeResource('/' + this.bucket + filename) | ||
, contentType: headers['Content-Type'] | ||
, md5: headers['Content-MD5'] || '' | ||
, amazonHeaders: auth.canonicalizeHeaders(headers) | ||
@@ -80,3 +85,3 @@ }); | ||
options.method = method; | ||
options.path = join('/', filename); | ||
options.path = filename; | ||
options.headers = headers; | ||
@@ -130,2 +135,23 @@ var req = http.request(options); | ||
/** | ||
* Copy files from `sourceFilename` to `destFilename` with optional `headers`. | ||
* | ||
* @param {String} sourceFilename | ||
* @param {String} destFilename | ||
* @param {Object} headers | ||
* @return {ClientRequest} | ||
* @api public | ||
*/ | ||
Client.prototype.copy = function(sourceFilename, destFilename, headers){ | ||
headers = utils.merge({ | ||
Expect: '100-continue' | ||
, 'x-amz-acl': 'public-read' | ||
}, headers || {}); | ||
headers['x-amz-copy-source'] = '/' + this.bucket + sourceFilename; | ||
headers['Content-Length'] = 0; // to avoid Node's automatic chunking if omitted | ||
return this.request('PUT', destFilename, headers); | ||
}; | ||
/** | ||
* PUT the file at `src` to `filename`, with callback `fn` | ||
@@ -164,2 +190,3 @@ * receiving a possible exception, and the response object. | ||
, 'Content-Type': mime.lookup(src) | ||
, 'Content-MD5': crypto.createHash('md5').update(buf).digest('base64') | ||
}, headers); | ||
@@ -199,3 +226,3 @@ self.put(filename, headers).on('response', function(res){ | ||
stream | ||
.on('error', function(err){fn(null, err); }) | ||
.on('error', function(err){fn(err); }) | ||
.on('data', function(chunk){ req.write(chunk); }) | ||
@@ -305,3 +332,53 @@ .on('end', function(){ req.end(); }); | ||
function xmlEscape(string) { | ||
return string.replace(/&/g, "&") | ||
.replace(/</g, "<") | ||
.replace(/>/g, ">") | ||
.replace(/"/g, """); | ||
} | ||
function makeDeleteXmlString(keys) { | ||
var tags = keys.map(function (key) { | ||
return "<Object><Key>" + xmlEscape(key) + "</Key></Object>"; | ||
}); | ||
return "<Delete>" + tags.join("") + "</Delete>"; | ||
} | ||
/** | ||
* Delete up to 1000 files at a time, with optional `headers` | ||
* and callback `fn` with a possible exception and the response. | ||
* | ||
* @param {Array[String]} filenames | ||
* @param {Object|Function} headers | ||
* @param {Function} fn | ||
* @api public | ||
*/ | ||
Client.prototype.deleteMultiple = function(filenames, headers, fn){ | ||
if (filenames.length > BUCKET_OPS_MAX) { | ||
throw new Error('Can only delete up to ' + BUCKET_OPS_MAX + ' files ' + | ||
'at a time. You\'ll need to batch them.'); | ||
} | ||
if ('function' == typeof headers) { | ||
fn = headers; | ||
headers = {}; | ||
} | ||
var xml = makeDeleteXmlString(filenames); | ||
headers['Content-Length'] = xml.length; | ||
headers['Content-MD5'] = crypto.createHash('md5').update(xml).digest('base64'); | ||
return this.request('POST', '/?delete', headers) | ||
.on('response', function(res){ | ||
fn(null, res); | ||
}) | ||
.on('error', function(err){ | ||
fn(err); | ||
}) | ||
.end(xml); | ||
}; | ||
/** | ||
* Return a url to the given `filename`. | ||
@@ -316,3 +393,3 @@ * | ||
Client.prototype.http = function(filename){ | ||
return 'http://' + join(this.endpoint, filename); | ||
return 'http://' + this.endpoint + filename; | ||
}; | ||
@@ -329,3 +406,3 @@ | ||
Client.prototype.https = function(filename){ | ||
return 'https://' + join(this.endpoint, filename); | ||
return 'https://' + this.endpoint + filename; | ||
}; | ||
@@ -347,3 +424,3 @@ | ||
date: epoch, | ||
resource: '/' + this.bucket + url.parse(filename).pathname | ||
resource: '/' + url.parse(filename).pathname | ||
}); | ||
@@ -350,0 +427,0 @@ |
@@ -0,0 +0,0 @@ |
@@ -162,2 +162,3 @@ var path = require('path'); | ||
, "ogm" : "application/ogg" | ||
, "ogv" : "video/ogg" | ||
, "p" : "text/x-pascal" | ||
@@ -255,2 +256,3 @@ , "pas" : "text/x-pascal" | ||
, "wax" : "audio/x-ms-wax" | ||
, "webm" : "video/webm" | ||
, "wma" : "audio/x-ms-wma" | ||
@@ -257,0 +259,0 @@ , "wmv" : "video/x-ms-wmv" |
@@ -0,0 +0,0 @@ { |
@@ -0,0 +0,0 @@ A library for doing simple mime-type lookups. |
@@ -0,0 +0,0 @@ |
@@ -5,3 +5,3 @@ { | ||
"keywords": ["aws", "amazon", "s3"], | ||
"version": "0.0.9", | ||
"version": "0.0.10", | ||
"author": "TJ Holowaychuk <tj@learnboost.com>", | ||
@@ -13,3 +13,9 @@ "main": "./index.js", | ||
"url": "git://github.com/LearnBoost/knox.git" | ||
}, | ||
"devDependencies": { | ||
"mocha": "*" | ||
}, | ||
"scripts": { | ||
"test": "mocha" | ||
} | ||
} |
169
Readme.md
@@ -8,4 +8,3 @@ | ||
- Not outdated :), developed for node 0.2.x | ||
- RESTful api (`client.get()`, `client.put()`, etc) | ||
- Familiar API (`client.get()`, `client.put()`, etc) | ||
- Uses node's crypto library (fast!, the others used native js) | ||
@@ -17,17 +16,22 @@ - Very node-like low-level request api via `http.Client` | ||
- TJ Holowaychuk ([visionmedia](http://github.com/visionmedia)) | ||
- TJ Holowaychuk ([visionmedia](https://github.com/visionmedia)) | ||
- Domenic Denicola ([domenic](https://github.com/domenic)) | ||
## Examples | ||
The following examples demonstrate some capabilities of knox and the s3 REST API. First things first, create an s3 client: | ||
The following examples demonstrate some capabilities of knox and the S3 REST | ||
API. First things first, create an S3 client: | ||
var client = knox.createClient({ | ||
key: '<api-key-here>' | ||
, secret: '<secret-here>' | ||
, bucket: 'learnboost' | ||
}); | ||
```js | ||
var client = knox.createClient({ | ||
key: '<api-key-here>' | ||
, secret: '<secret-here>' | ||
, bucket: 'learnboost' | ||
}); | ||
``` | ||
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. | ||
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. | ||
@@ -39,72 +43,100 @@ ### PUT | ||
filename as the first parameter (_/test/Readme.md_), and some headers. Then | ||
we listen for the _response_ event, just as we would for any `http.Client` request, if we have a 200 response, great! output the destination url to stdout. | ||
we listen for the _response_ event, just as we would for any `http.Client` | ||
request, if we have a 200 response, great! output the destination url to | ||
stdout. | ||
fs.readFile('Readme.md', function(err, buf){ | ||
var req = client.put('/test/Readme.md', { | ||
'Content-Length': buf.length | ||
, 'Content-Type': 'text/plain' | ||
}); | ||
req.on('response', function(res){ | ||
if (200 == res.statusCode) { | ||
console.log('saved to %s', req.url); | ||
} | ||
}); | ||
req.end(buf); | ||
}); | ||
```js | ||
fs.readFile('Readme.md', function(err, buf){ | ||
var req = client.put('/test/Readme.md', { | ||
'Content-Length': buf.length | ||
, 'Content-Type': 'text/plain' | ||
}); | ||
req.on('response', function(res){ | ||
if (200 == res.statusCode) { | ||
console.log('saved to %s', req.url); | ||
} | ||
}); | ||
req.end(buf); | ||
}); | ||
``` | ||
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. Note that the field name __MUST__ be lowercase, do not use 'X-Amz-Acl' etc, as this will currently result in duplicate headers (although different case). | ||
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. | ||
Note that the field name __MUST__ be lowercase, do not use 'X-Amz-Acl' etc, as | ||
this will currently result in duplicate headers (although different case). | ||
client.put('/test/Readme.md', { 'x-amz-acl': 'private' }); | ||
```js | ||
client.put('/test/Readme.md', { 'x-amz-acl': 'private' }); | ||
``` | ||
Each HTTP verb has an alternate method with the "File" suffix, for example `put()` also has a higher level method named `putFile()`, accepting a src filename and performs the dirty work shown above for you. Here is an example usage: | ||
Each HTTP verb has an alternate method with the "File" suffix, for example | ||
`put()` also has a higher level method named `putFile()`, accepting a src | ||
filename and performs the dirty work shown above for you. Here is an example | ||
usage: | ||
client.putFile('my.json', '/user.json', function(err, res){ | ||
// Logic | ||
}); | ||
```js | ||
client.putFile('my.json', '/user.json', function(err, res){ | ||
// Logic | ||
}); | ||
``` | ||
Another alternative is to stream via `Client#putStream()`, for example: | ||
var stream = fs.createReadStream('data.json'); | ||
client.putStream(stream, '/some-data.json', function(err, res){ | ||
// Logic | ||
}); | ||
```js | ||
var stream = fs.createReadStream('data.json'); | ||
client.putStream(stream, '/some-data.json', function(err, res){ | ||
// Logic | ||
}); | ||
``` | ||
(Note that this only works with file streams currently.) | ||
An example of moving a file: | ||
client.put('0/0/0.png', { | ||
'Content-Type': 'image/jpg', | ||
'Content-Length': '0', | ||
'x-amz-copy-source': '/test-tiles/0/0/0.png', | ||
'x-amz-metadata-directive': 'REPLACE' | ||
}).on('response', function(res) { | ||
// Logic | ||
}).end(); | ||
```js | ||
client.put('0/0/0.png', { | ||
'Content-Type': 'image/jpg' | ||
, 'Content-Length': '0' | ||
, 'x-amz-copy-source': '/test-tiles/0/0/0.png' | ||
, 'x-amz-metadata-directive': 'REPLACE' | ||
}).on('response', function(res) { | ||
// Logic | ||
}).end(); | ||
``` | ||
### GET | ||
Below is an example __GET__ request on the file we just shoved at s3, and simply outputs the response status code, headers, and body. | ||
Below is an example __GET__ request on the file we just shoved at S3. It simply | ||
outputs the response status code, headers, and body. | ||
client.get('/test/Readme.md').on('response', function(res){ | ||
console.log(res.statusCode); | ||
console.log(res.headers); | ||
res.setEncoding('utf8'); | ||
res.on('data', function(chunk){ | ||
console.log(chunk); | ||
}); | ||
}).end(); | ||
```js | ||
client.get('/test/Readme.md').on('response', function(res){ | ||
console.log(res.statusCode); | ||
console.log(res.headers); | ||
res.setEncoding('utf8'); | ||
res.on('data', function(chunk){ | ||
console.log(chunk); | ||
}); | ||
}).end(); | ||
``` | ||
## DELETE | ||
### DELETE | ||
Delete our file: | ||
client.del('/test/Readme.md').on('response', function(res){ | ||
console.log(res.statusCode); | ||
console.log(res.headers); | ||
}).end(); | ||
```js | ||
client.del('/test/Readme.md').on('response', function(res){ | ||
console.log(res.statusCode); | ||
console.log(res.headers); | ||
}).end(); | ||
``` | ||
Likewise we also have `client.deleteFile()` as a more concise (yet less flexible) solution: | ||
Likewise we also have `client.deleteFile()` as a more concise (yet less | ||
flexible) solution: | ||
client.deleteFile('/test/Readme.md', function(err, res){ | ||
// Logic | ||
}); | ||
```js | ||
client.deleteFile('/test/Readme.md', function(err, res){ | ||
// Logic | ||
}); | ||
``` | ||
@@ -116,9 +148,14 @@ ## Running Tests | ||
{"key":"<api-key-here>", | ||
"secret":"<secret-here>", | ||
"bucket":"<your-bucket-name>"} | ||
```json | ||
{ | ||
"key":"<api-key-here>", | ||
"secret":"<secret-here>", | ||
"bucket":"<your-bucket-name>" | ||
} | ||
``` | ||
Then simply execute: | ||
Then install the dev dependencies and execute the test suite: | ||
$ make test | ||
$ npm install | ||
$ npm test | ||
@@ -125,0 +162,0 @@ ## License |
@@ -6,7 +6,8 @@ | ||
var knox = require('knox') | ||
, auth = knox.auth; | ||
var knox = require('..') | ||
, auth = knox.auth | ||
, assert = require('assert'); | ||
module.exports = { | ||
'test .stringToSign()': function(assert){ | ||
'test .stringToSign()': function(){ | ||
var str = auth.stringToSign({ | ||
@@ -31,3 +32,3 @@ verb: 'PUT' | ||
'test .sign()': function(assert){ | ||
'test .sign()': function(){ | ||
var str = auth.sign({ | ||
@@ -45,3 +46,3 @@ verb: 'PUT' | ||
'test .canonicalizeHeaders()': function(assert){ | ||
'test .canonicalizeHeaders()': function(){ | ||
var str = auth.canonicalizeHeaders({ | ||
@@ -48,0 +49,0 @@ 'X-Amz-Date': 'some date' |
@@ -6,4 +6,6 @@ | ||
var knox = require('knox') | ||
, fs = require('fs'); | ||
var knox = require('..') | ||
, fs = require('fs') | ||
, assert = require('assert') | ||
, crypto = require('crypto'); | ||
@@ -14,3 +16,3 @@ try { | ||
} catch (err) { | ||
console.error('`make test` requires ./auth to contain a JSON string with'); | ||
console.error('The tests require ./auth to contain a JSON string with'); | ||
console.error('`key, secret, and bucket in order to run tests.'); | ||
@@ -23,7 +25,7 @@ process.exit(1); | ||
module.exports = { | ||
'test .version': function(assert){ | ||
assert.match(knox.version, /^\d+\.\d+\.\d+$/); | ||
'test .version': function(){ | ||
assert.ok(/^\d+\.\d+\.\d+$/.test(knox.version)); | ||
}, | ||
'test .createClient() invalid': function(assert){ | ||
'test .createClient() invalid': function(){ | ||
var err; | ||
@@ -36,3 +38,3 @@ try { | ||
assert.equal('aws "key" required', err.message); | ||
var err; | ||
@@ -45,3 +47,3 @@ try { | ||
assert.equal('aws "secret" required', err.message); | ||
var err; | ||
@@ -55,4 +57,4 @@ try { | ||
}, | ||
'test .createClient() valid': function(assert){ | ||
'test .createClient() valid': function(){ | ||
var client = knox.createClient({ | ||
@@ -63,3 +65,3 @@ key: 'foobar' | ||
}); | ||
assert.equal('foobar', client.key); | ||
@@ -70,4 +72,4 @@ assert.equal('baz', client.secret); | ||
}, | ||
'test .createClient() custom endpoint': function(assert){ | ||
'test .createClient() custom endpoint': function(){ | ||
var client = knox.createClient({ | ||
@@ -83,8 +85,8 @@ key: 'foobar' | ||
'test .putFile()': function(assert, done){ | ||
'test .putFile()': function(done){ | ||
var n = 0; | ||
client.putFile(jsonFixture, '/test/user.json', function(err, res){ | ||
client.putFile(jsonFixture, '/test/user2.json', function(err, res){ | ||
assert.ok(!err, 'putFile() got an error!'); | ||
assert.equal(200, res.statusCode); | ||
client.get('/test/user.json').on('response', function(res){ | ||
client.get('/test/user2.json').on('response', function(res){ | ||
assert.equal('application/json', res.headers['content-type']); | ||
@@ -95,4 +97,4 @@ done(); | ||
}, | ||
'test .put()': function(assert, done){ | ||
'test .put()': function(done){ | ||
var n = 0; | ||
@@ -111,6 +113,6 @@ fs.stat(jsonFixture, function(err, stat){ | ||
assert.equal( | ||
'http://'+client.bucket+'.s3.amazonaws.com/test/user.json' | ||
'http://'+client.endpoint+'/test/user.json' | ||
, client.url('/test/user.json')); | ||
assert.equal( | ||
'http://'+client.bucket+'.s3.amazonaws.com/test/user.json' | ||
'http://'+client.endpoint+'/test/user.json' | ||
, req.url); | ||
@@ -123,4 +125,4 @@ done(); | ||
}, | ||
'test .putStream()': function(assert, done){ | ||
'test .putStream()': function(done){ | ||
var stream = fs.createReadStream(jsonFixture); | ||
@@ -133,4 +135,4 @@ client.putStream(stream, '/test/user.json', function(err, res){ | ||
}, | ||
'test .getFile()': function(assert, done){ | ||
'test .getFile()': function(done){ | ||
client.getFile('/test/user.json', function(err, res){ | ||
@@ -144,4 +146,4 @@ assert.ok(!err); | ||
}, | ||
'test .get()': function(assert, done){ | ||
'test .get()': function(done){ | ||
client.get('/test/user.json').on('response', function(res){ | ||
@@ -154,4 +156,4 @@ assert.equal(200, res.statusCode); | ||
}, | ||
'test .head()': function(assert, done){ | ||
'test .head()': function(done){ | ||
client.head('/test/user.json').on('response', function(res){ | ||
@@ -164,4 +166,4 @@ assert.equal(200, res.statusCode); | ||
}, | ||
'test .headFile()': function(assert, done){ | ||
'test .headFile()': function(done){ | ||
client.headFile('/test/user.json', function(err, res){ | ||
@@ -175,4 +177,4 @@ assert.ok(!err); | ||
}, | ||
'test .del()': function(assert, done){ | ||
'test .del()': function(done){ | ||
client.del('/test/user.json').on('response', function(res){ | ||
@@ -183,5 +185,5 @@ assert.equal(204, res.statusCode); | ||
}, | ||
'test .deleteFile()': function(assert, done){ | ||
client.deleteFile('/test/user.json', function(err, res){ | ||
'test .deleteFile()': function(done){ | ||
client.deleteFile('/test/user2.json', function(err, res){ | ||
assert.ok(!err); | ||
@@ -192,4 +194,32 @@ assert.equal(204, res.statusCode); | ||
}, | ||
'test .get() 404': function(assert, done){ | ||
'test .deleteMultiple()': function(done){ | ||
client.deleteMultiple(['test/user.json', '/test/user2.json'], | ||
function (err, res) { | ||
assert.ok(!err); | ||
assert.equal(200, res.statusCode); | ||
done(); | ||
}); | ||
}, | ||
'test /?delete': function (done) { | ||
var xml = ['<?xml version="1.0" encoding="UTF-8"?>\n','<Delete>']; | ||
xml.push('<Object><Key>/test/user3.json</Key></Object>'); | ||
xml.push('</Delete>'); | ||
xml = xml.join(''); | ||
var req = client.request('POST', '/?delete', { | ||
'Content-Length': xml.length, | ||
'Content-MD5': crypto.createHash('md5').update(xml).digest('base64'), | ||
'Accept:': '*/*', | ||
}).on('error', function (err) { | ||
assert.ok(!err); | ||
}).on('response', function (res) { | ||
assert.equal(200, res.statusCode); | ||
done(); | ||
}); | ||
req.write(xml); | ||
req.end(); | ||
}, | ||
'test .get() 404': function(done){ | ||
client.get('/test/user.json').on('response', function(res){ | ||
@@ -200,4 +230,4 @@ assert.equal(404, res.statusCode); | ||
}, | ||
'test .head() 404': function(assert, done){ | ||
'test .head() 404': function(done){ | ||
client.head('/test/user.json').on('response', function(res){ | ||
@@ -204,0 +234,0 @@ assert.equal(404, res.statusCode); |
@@ -6,13 +6,14 @@ | ||
var knox = require('knox') | ||
, utils = knox.utils; | ||
var knox = require('..') | ||
, utils = knox.utils | ||
, assert = require('assert'); | ||
module.exports = { | ||
'test .base64.encode()': function(assert){ | ||
'test .base64.encode()': function(){ | ||
assert.equal('aGV5', utils.base64.encode('hey')); | ||
}, | ||
'test .base64.decode()': function(assert){ | ||
'test .base64.decode()': function(){ | ||
assert.equal('hey', utils.base64.decode('aGV5')); | ||
} | ||
}; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Install scripts
Supply chain riskInstall scripts are run when the package is installed. The majority of malware in npm is hidden in install scripts.
Found 1 instance in 1 package
182
1
1
69005
1
20
1179