Esse commit está contido em:
Tim Fogarty
2013-10-04 18:52:48 +01:00
commit 51aa47d113
144 arquivos alterados com 9922 adições e 3320 exclusões
+2 -2
Ver Arquivo
@@ -1,7 +1,7 @@
#!/usr/bin/nodemon #!/usr/bin/nodemon
var express = require('express'), var express = require('express'),
app = express(), app = module.exports = express.createServer(),
routes = require('./routes'), routes = require('./routes'),
arDrone = require('ar-drone'), arDrone = require('ar-drone'),
client = arDrone.createClient(); client = arDrone.createClient();
@@ -9,7 +9,7 @@ client = arDrone.createClient();
app.set('views', __dirname + '/views'); app.set('views', __dirname + '/views');
app.set('view engine', 'ejs'); app.set('view engine', 'ejs');
app.use(app.router); app.use(app.router);
app.use(express.static(__dirname + 'public')); app.use(express.static(__dirname + '/public'));
app.get('/', routes.index); app.get('/', routes.index);
+6
Ver Arquivo
@@ -1,4 +1,10 @@
3.4.0 / 2013-09-07
==================
* add res.vary(). Closes #1682
* update connect
3.3.8 / 2013-09-02 3.3.8 / 2013-09-02
================== ==================
+40 -2
Ver Arquivo
@@ -237,7 +237,7 @@ res.jsonp = function(obj){
// jsonp // jsonp
if (callback) { if (callback) {
if (callback instanceof Array) callback = callback[0]; if (Array.isArray(callback)) callback = callback[0];
this.set('Content-Type', 'text/javascript'); this.set('Content-Type', 'text/javascript');
var cb = callback.replace(/[^\[\]\w$.]/g, ''); var cb = callback.replace(/[^\[\]\w$.]/g, '');
body = cb + ' && ' + cb + '(' + body + ');'; body = cb + ' && ' + cb + '(' + body + ');';
@@ -463,7 +463,7 @@ res.format = function(obj){
var key = req.accepts(keys); var key = req.accepts(keys);
this.set('Vary', 'Accept'); this.vary("Accept");
if (key) { if (key) {
this.set('Content-Type', normalizeType(key).value); this.set('Content-Type', normalizeType(key).value);
@@ -720,6 +720,44 @@ res.redirect = function(url){
this.end(head ? null : body); this.end(head ? null : body);
}; };
/**
* Add `field` to Vary. If already present in the Vary set, then
* this call is simply ignored.
*
* @param {Array|String} field
* @param {ServerResponse} for chaining
* @api public
*/
res.vary = function(field){
var self = this;
// nothing
if (!field) return this;
// array
if (Array.isArray(field)) {
field.forEach(function(field){
self.vary(field);
});
return;
}
var vary = this.get('Vary');
// append
if (vary) {
vary = vary.split(/ *, */);
if (!~vary.indexOf(field)) vary.push(field);
this.set('Vary', vary.join(', '));
return this;
}
// set
this.set('Vary', field);
return this;
};
/** /**
* Render `view` with the given `options` and optional callback `fn`. * Render `view` with the given `options` and optional callback `fn`.
* When a callback function is given a response will _not_ be made * When a callback function is given a response will _not_ be made
+1
Ver Arquivo
@@ -54,6 +54,7 @@ exports.locals = function(obj){
exports.isAbsolute = function(path){ exports.isAbsolute = function(path){
if ('/' == path[0]) return true; if ('/' == path[0]) return true;
if (':' == path[1] && '\\' == path[2]) return true; if (':' == path[1] && '\\' == path[2]) return true;
if ('\\\\' == path.substring(0, 2)) return true; // Microsoft Azure absolute path
}; };
/** /**
+1 -2
Ver Arquivo
@@ -1,4 +1,3 @@
/*! /*!
* Connect - basicAuth * Connect - basicAuth
* Copyright(c) 2010 Sencha Inc. * Copyright(c) 2010 Sencha Inc.
@@ -30,7 +29,7 @@ var utils = require('../utils')
* *
* connect() * connect()
* .use(connect.basicAuth(function(user, pass){ * .use(connect.basicAuth(function(user, pass){
* return 'tj' == user & 'wahoo' == pass; * return 'tj' == user && 'wahoo' == pass;
* })) * }))
* *
* Async callback verification, accepting `fn(err, user)`. * Async callback verification, accepting `fn(err, user)`.
+1 -1
Ver Arquivo
@@ -67,7 +67,7 @@ module.exports = function csrf(options) {
Object.defineProperty(req.session, '_csrf', { Object.defineProperty(req.session, '_csrf', {
configurable: true, configurable: true,
get: function() { get: function() {
console.warn('req.session._csrf is deprecated, use req.csrfToken([callback]) instead'); console.warn('req.session._csrf is deprecated, use req.csrfToken() instead');
return req.csrfToken(); return req.csrfToken();
} }
}); });
+2 -12
Ver Arquivo
@@ -13,14 +13,6 @@
var utils = require('../utils') var utils = require('../utils')
, _limit = require('./limit'); , _limit = require('./limit');
/**
* noop middleware.
*/
function noop(req, res, next) {
next();
}
/** /**
* JSON: * JSON:
* *
@@ -31,7 +23,7 @@ function noop(req, res, next) {
* *
* - `strict` when `false` anything `JSON.parse()` accepts will be parsed * - `strict` when `false` anything `JSON.parse()` accepts will be parsed
* - `reviver` used as the second "reviver" argument for JSON.parse * - `reviver` used as the second "reviver" argument for JSON.parse
* - `limit` byte limit disabled by default * - `limit` byte limit [1mb]
* *
* @param {Object} options * @param {Object} options
* @return {Function} * @return {Function}
@@ -42,9 +34,7 @@ exports = module.exports = function(options){
var options = options || {} var options = options || {}
, strict = options.strict !== false; , strict = options.strict !== false;
var limit = options.limit var limit = _limit(options.limit || '1mb');
? _limit(options.limit)
: noop;
return function json(req, res, next) { return function json(req, res, next) {
if (req._body) return next(); if (req._body) return next();
+46 -22
Ver Arquivo
@@ -9,19 +9,11 @@
* Module dependencies. * Module dependencies.
*/ */
var formidable = require('formidable') var multiparty = require('multiparty')
, _limit = require('./limit') , _limit = require('./limit')
, utils = require('../utils') , utils = require('../utils')
, qs = require('qs'); , qs = require('qs');
/**
* noop middleware.
*/
function noop(req, res, next) {
next();
}
/** /**
* Multipart: * Multipart:
* *
@@ -31,18 +23,49 @@ function noop(req, res, next) {
* *
* Configuration: * Configuration:
* *
* The options passed are merged with [formidable](https://github.com/felixge/node-formidable)'s * The options passed are merged with [multiparty](https://github.com/superjoe30/node-multiparty)'s
* `IncomingForm` object, allowing you to configure the upload directory, * `Form` object, allowing you to configure the upload directory,
* size limits, etc. For example if you wish to change the upload dir do the following. * size limits, etc. For example if you wish to change the upload dir do the following.
* *
* app.use(connect.multipart({ uploadDir: path })); * app.use(connect.multipart({ uploadDir: path }));
* *
* Options: * Options:
* *
* - `limit` byte limit defaulting to none * - `limit` byte limit defaulting to [100mb]
* - `defer` defers processing and exposes the Formidable form object as `req.form`. * - `defer` defers processing and exposes the multiparty form object as `req.form`.
* `next()` is called without waiting for the form's "end" event. * `next()` is called without waiting for the form's "end" event.
* This option is useful if you need to bind to the "progress" event, for example. * This option is useful if you need to bind to the "progress" or "part" events, for example.
*
* Temporary Files:
*
* By default temporary files are used, stored in `os.tmpDir()`. These
* are not automatically garbage collected, you are in charge of moving them
* or deleting them. When `defer` is not used and these files are created you
* may refernce them via the `req.files` object.
*
* req.files.images.forEach(function(file){
* console.log(' uploaded : %s %skb : %s', file.originalFilename, file.size / 1024 | 0, file.path);
* });
*
* It is highly recommended to monitor and clean up tempfiles in any production
* environment, you may use tools like [reap](https://github.com/visionmedia/reap)
* to do so.
*
* Streaming:
*
* When `defer` is used files are _not_ streamed to tmpfiles, you may
* access them via the "part" events and stream them accordingly:
*
* req.form.on('part', function(part){
* // transfer to s3 etc
* console.log('upload %s %s', part.name, part.filename);
* var out = fs.createWriteStream('/tmp/' + part.filename);
* part.pipe(out);
* });
*
* req.form.on('close', function(){
* res.end('uploaded!');
* });
* *
* @param {Object} options * @param {Object} options
* @return {Function} * @return {Function}
@@ -52,9 +75,7 @@ function noop(req, res, next) {
exports = module.exports = function(options){ exports = module.exports = function(options){
options = options || {}; options = options || {};
var limit = options.limit var limit = _limit(options.limit || '100mb');
? _limit(options.limit)
: noop;
return function multipart(req, res, next) { return function multipart(req, res, next) {
if (req._body) return next(); if (req._body) return next();
@@ -76,7 +97,7 @@ exports = module.exports = function(options){
limit(req, res, function(err){ limit(req, res, function(err){
if (err) return next(err); if (err) return next(err);
var form = new formidable.IncomingForm var form = new multiparty.Form
, data = {} , data = {}
, files = {} , files = {}
, done; , done;
@@ -99,9 +120,12 @@ exports = module.exports = function(options){
ondata(name, val, data); ondata(name, val, data);
}); });
form.on('file', function(name, val){ if (!options.defer) {
ondata(name, val, files); form.on('file', function(name, val){
}); val.name = val.originalFilename;
ondata(name, val, files);
});
}
form.on('error', function(err){ form.on('error', function(err){
if (!options.defer) { if (!options.defer) {
@@ -111,7 +135,7 @@ exports = module.exports = function(options){
done = true; done = true;
}); });
form.on('end', function(){ form.on('close', function(){
if (done) return; if (done) return;
try { try {
req.body = qs.parse(data); req.body = qs.parse(data);
@@ -30,7 +30,7 @@ function noop(req, res, next) {
* *
* Options: * Options:
* *
* - `limit` byte limit disabled by default * - `limit` byte limit [1mb]
* *
* @param {Object} options * @param {Object} options
* @return {Function} * @return {Function}
@@ -40,9 +40,7 @@ function noop(req, res, next) {
exports = module.exports = function(options){ exports = module.exports = function(options){
options = options || {}; options = options || {};
var limit = options.limit var limit = _limit(options.limit || '1mb');
? _limit(options.limit)
: noop;
return function urlencoded(req, res, next) { return function urlencoded(req, res, next) {
if (req._body) return next(); if (req._body) return next();
@@ -1,4 +0,0 @@
/test/tmp/
*.upload
*.un~
*.http
@@ -1,419 +0,0 @@
# Formidable
[![Build Status](https://secure.travis-ci.org/felixge/node-formidable.png?branch=master)](http://travis-ci.org/felixge/node-formidable)
## Purpose
A node.js module for parsing form data, especially file uploads.
## Current status
This module was developed for [Transloadit](http://transloadit.com/), a service focused on uploading
and encoding images and videos. It has been battle-tested against hundreds of GB of file uploads from
a large variety of clients and is considered production-ready.
## Features
* Fast (~500mb/sec), non-buffering multipart parser
* Automatically writing file uploads to disk
* Low memory footprint
* Graceful error handling
* Very high test coverage
## Installation
Via [npm](http://github.com/isaacs/npm):
```
npm install formidable@latest
```
Manually:
```
git clone git://github.com/felixge/node-formidable.git formidable
vim my.js
# var formidable = require('./formidable');
```
Note: Formidable requires [gently](http://github.com/felixge/node-gently) to run the unit tests, but you won't need it for just using the library.
## Example
Parse an incoming file upload.
```javascript
var formidable = require('formidable'),
http = require('http'),
util = require('util');
http.createServer(function(req, res) {
if (req.url == '/upload' && req.method.toLowerCase() == 'post') {
// parse a file upload
var form = new formidable.IncomingForm();
form.parse(req, function(err, fields, files) {
res.writeHead(200, {'content-type': 'text/plain'});
res.write('received upload:\n\n');
res.end(util.inspect({fields: fields, files: files}));
});
return;
}
// show a file upload form
res.writeHead(200, {'content-type': 'text/html'});
res.end(
'<form action="/upload" enctype="multipart/form-data" method="post">'+
'<input type="text" name="title"><br>'+
'<input type="file" name="upload" multiple="multiple"><br>'+
'<input type="submit" value="Upload">'+
'</form>'
);
}).listen(8080);
```
## API
### Formidable.IncomingForm
```javascript
var form = new formidable.IncomingForm()
```
Creates a new incoming form.
```javascript
form.encoding = 'utf-8';
```
Sets encoding for incoming form fields.
```javascript
form.uploadDir = process.env.TMP || process.env.TMPDIR || process.env.TEMP || '/tmp' || process.cwd();
```
The directory for placing file uploads in. You can move them later on using
`fs.rename()`. The default directory is picked at module load time depending on
the first existing directory from those listed above.
```javascript
form.keepExtensions = false;
```
If you want the files written to `form.uploadDir` to include the extensions of the original files, set this property to `true`.
```javascript
form.type
```
Either 'multipart' or 'urlencoded' depending on the incoming request.
```javascript
form.maxFieldsSize = 2 * 1024 * 1024;
```
Limits the amount of memory a field (not file) can allocate in bytes.
If this value is exceeded, an `'error'` event is emitted. The default
size is 2MB.
```javascript
form.maxFields = 0;
```
Limits the number of fields that the querystring parser will decode. Defaults
to 0 (unlimited).
```javascript
form.hash = false;
```
If you want checksums calculated for incoming files, set this to either `'sha1'` or `'md5'`.
```javascript
form.bytesReceived
```
The amount of bytes received for this form so far.
```javascript
form.bytesExpected
```
The expected number of bytes in this form.
```javascript
form.parse(request, [cb]);
```
Parses an incoming node.js `request` containing form data. If `cb` is provided, all fields an files are collected and passed to the callback:
```javascript
form.parse(req, function(err, fields, files) {
// ...
});
form.onPart(part);
```
You may overwrite this method if you are interested in directly accessing the multipart stream. Doing so will disable any `'field'` / `'file'` events processing which would occur otherwise, making you fully responsible for handling the processing.
```javascript
form.onPart = function(part) {
part.addListener('data', function() {
// ...
});
}
```
If you want to use formidable to only handle certain parts for you, you can do so:
```javascript
form.onPart = function(part) {
if (!part.filename) {
// let formidable handle all non-file parts
form.handlePart(part);
}
}
```
Check the code in this method for further inspiration.
### Formidable.File
```javascript
file.size = 0
```
The size of the uploaded file in bytes. If the file is still being uploaded (see `'fileBegin'` event), this property says how many bytes of the file have been written to disk yet.
```javascript
file.path = null
```
The path this file is being written to. You can modify this in the `'fileBegin'` event in
case you are unhappy with the way formidable generates a temporary path for your files.
```javascript
file.name = null
```
The name this file had according to the uploading client.
```javascript
file.type = null
```
The mime type of this file, according to the uploading client.
```javascript
file.lastModifiedDate = null
```
A date object (or `null`) containing the time this file was last written to. Mostly
here for compatibility with the [W3C File API Draft](http://dev.w3.org/2006/webapi/FileAPI/).
```javascript
file.hash = null
```
If hash calculation was set, you can read the hex digest out of this var.
#### Formidable.File#toJSON()
This method returns a JSON-representation of the file, allowing you to
`JSON.stringify()` the file which is useful for logging and responding
to requests.
### Events
#### 'progress'
```javascript
form.on('progress', function(bytesReceived, bytesExpected) {
});
```
Emitted after each incoming chunk of data that has been parsed. Can be used to roll your own progress bar.
#### 'field'
```javascript
form.on('field', function(name, value) {
});
```
#### 'fileBegin'
Emitted whenever a field / value pair has been received.
```javascript
form.on('fileBegin', function(name, file) {
});
```
#### 'file'
Emitted whenever a new file is detected in the upload stream. Use this even if
you want to stream the file to somewhere else while buffering the upload on
the file system.
Emitted whenever a field / file pair has been received. `file` is an instance of `File`.
```javascript
form.on('file', function(name, file) {
});
```
#### 'error'
Emitted when there is an error processing the incoming form. A request that experiences an error is automatically paused, you will have to manually call `request.resume()` if you want the request to continue firing `'data'` events.
```javascript
form.on('error', function(err) {
});
```
#### 'aborted'
Emitted when the request was aborted by the user. Right now this can be due to a 'timeout' or 'close' event on the socket. In the future there will be a separate 'timeout' event (needs a change in the node core).
```javascript
form.on('aborted', function() {
});
```
##### 'end'
```javascript
form.on('end', function() {
});
```
Emitted when the entire request has been received, and all contained files have finished flushing to disk. This is a great place for you to send your response.
## Changelog
### v1.0.14
* Add failing hash tests. (Ben Trask)
* Enable hash calculation again (Eugene Girshov)
* Test for immediate data events (Tim Smart)
* Re-arrange IncomingForm#parse (Tim Smart)
### v1.0.13
* Only update hash if update method exists (Sven Lito)
* According to travis v0.10 needs to go quoted (Sven Lito)
* Bumping build node versions (Sven Lito)
* Additional fix for empty requests (Eugene Girshov)
* Change the default to 1000, to match the new Node behaviour. (OrangeDog)
* Add ability to control maxKeys in the querystring parser. (OrangeDog)
* Adjust test case to work with node 0.9.x (Eugene Girshov)
* Update package.json (Sven Lito)
* Path adjustment according to eb4468b (Markus Ast)
### v1.0.12
* Emit error on aborted connections (Eugene Girshov)
* Add support for empty requests (Eugene Girshov)
* Fix name/filename handling in Content-Disposition (jesperp)
* Tolerate malformed closing boundary in multipart (Eugene Girshov)
* Ignore preamble in multipart messages (Eugene Girshov)
* Add support for application/json (Mike Frey, Carlos Rodriguez)
* Add support for Base64 encoding (Elmer Bulthuis)
* Add File#toJSON (TJ Holowaychuk)
* Remove support for Node.js 0.4 & 0.6 (Andrew Kelley)
* Documentation improvements (Sven Lito, Andre Azevedo)
* Add support for application/octet-stream (Ion Lupascu, Chris Scribner)
* Use os.tmpDir() to get tmp directory (Andrew Kelley)
* Improve package.json (Andrew Kelley, Sven Lito)
* Fix benchmark script (Andrew Kelley)
* Fix scope issue in incoming_forms (Sven Lito)
* Fix file handle leak on error (OrangeDog)
### v1.0.11
* Calculate checksums for incoming files (sreuter)
* Add definition parameters to "IncomingForm" as an argument (Math-)
### v1.0.10
* Make parts to be proper Streams (Matt Robenolt)
### v1.0.9
* Emit progress when content length header parsed (Tim Koschützki)
* Fix Readme syntax due to GitHub changes (goob)
* Replace references to old 'sys' module in Readme with 'util' (Peter Sugihara)
### v1.0.8
* Strip potentially unsafe characters when using `keepExtensions: true`.
* Switch to utest / urun for testing
* Add travis build
### v1.0.7
* Remove file from package that was causing problems when installing on windows. (#102)
* Fix typos in Readme (Jason Davies).
### v1.0.6
* Do not default to the default to the field name for file uploads where
filename="".
### v1.0.5
* Support filename="" in multipart parts
* Explain unexpected end() errors in parser better
**Note:** Starting with this version, formidable emits 'file' events for empty
file input fields. Previously those were incorrectly emitted as regular file
input fields with value = "".
### v1.0.4
* Detect a good default tmp directory regardless of platform. (#88)
### v1.0.3
* Fix problems with utf8 characters (#84) / semicolons in filenames (#58)
* Small performance improvements
* New test suite and fixture system
### v1.0.2
* Exclude node\_modules folder from git
* Implement new `'aborted'` event
* Fix files in example folder to work with recent node versions
* Make gently a devDependency
[See Commits](https://github.com/felixge/node-formidable/compare/v1.0.1...v1.0.2)
### v1.0.1
* Fix package.json to refer to proper main directory. (#68, Dean Landolt)
[See Commits](https://github.com/felixge/node-formidable/compare/v1.0.0...v1.0.1)
### v1.0.0
* Add support for multipart boundaries that are quoted strings. (Jeff Craig)
This marks the beginning of development on version 2.0 which will include
several architectural improvements.
[See Commits](https://github.com/felixge/node-formidable/compare/v0.9.11...v1.0.0)
### v0.9.11
* Emit `'progress'` event when receiving data, regardless of parsing it. (Tim Koschützki)
* Use [W3C FileAPI Draft](http://dev.w3.org/2006/webapi/FileAPI/) properties for File class
**Important:** The old property names of the File class will be removed in a
future release.
[See Commits](https://github.com/felixge/node-formidable/compare/v0.9.10...v0.9.11)
### Older releases
These releases were done before starting to maintain the above Changelog:
* [v0.9.10](https://github.com/felixge/node-formidable/compare/v0.9.9...v0.9.10)
* [v0.9.9](https://github.com/felixge/node-formidable/compare/v0.9.8...v0.9.9)
* [v0.9.8](https://github.com/felixge/node-formidable/compare/v0.9.7...v0.9.8)
* [v0.9.7](https://github.com/felixge/node-formidable/compare/v0.9.6...v0.9.7)
* [v0.9.6](https://github.com/felixge/node-formidable/compare/v0.9.5...v0.9.6)
* [v0.9.5](https://github.com/felixge/node-formidable/compare/v0.9.4...v0.9.5)
* [v0.9.4](https://github.com/felixge/node-formidable/compare/v0.9.3...v0.9.4)
* [v0.9.3](https://github.com/felixge/node-formidable/compare/v0.9.2...v0.9.3)
* [v0.9.2](https://github.com/felixge/node-formidable/compare/v0.9.1...v0.9.2)
* [v0.9.1](https://github.com/felixge/node-formidable/compare/v0.9.0...v0.9.1)
* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0)
* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0)
* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0)
* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0)
* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0)
* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0)
* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0)
* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0)
* [v0.1.0](https://github.com/felixge/node-formidable/commits/v0.1.0)
## License
Formidable is licensed under the MIT license.
## Ports
* [multipart-parser](http://github.com/FooBarWidget/multipart-parser): a C++ parser based on formidable
## Credits
* [Ryan Dahl](http://twitter.com/ryah) for his work on [http-parser](http://github.com/ry/http-parser) which heavily inspired multipart_parser.js
@@ -1,71 +0,0 @@
var assert = require('assert');
require('../test/common');
var multipartParser = require('../lib/multipart_parser'),
MultipartParser = multipartParser.MultipartParser,
parser = new MultipartParser(),
Buffer = require('buffer').Buffer,
boundary = '-----------------------------168072824752491622650073',
mb = 100,
buffer = createMultipartBuffer(boundary, mb * 1024 * 1024),
callbacks =
{ partBegin: -1,
partEnd: -1,
headerField: -1,
headerValue: -1,
partData: -1,
end: -1,
};
parser.initWithBoundary(boundary);
parser.onHeaderField = function() {
callbacks.headerField++;
};
parser.onHeaderValue = function() {
callbacks.headerValue++;
};
parser.onPartBegin = function() {
callbacks.partBegin++;
};
parser.onPartData = function() {
callbacks.partData++;
};
parser.onPartEnd = function() {
callbacks.partEnd++;
};
parser.onEnd = function() {
callbacks.end++;
};
var start = +new Date(),
nparsed = parser.write(buffer),
duration = +new Date - start,
mbPerSec = (mb / (duration / 1000)).toFixed(2);
console.log(mbPerSec+' mb/sec');
assert.equal(nparsed, buffer.length);
function createMultipartBuffer(boundary, size) {
var head =
'--'+boundary+'\r\n'
+ 'content-disposition: form-data; name="field1"\r\n'
+ '\r\n'
, tail = '\r\n--'+boundary+'--\r\n'
, buffer = new Buffer(size);
buffer.write(head, 'ascii', 0);
buffer.write(tail, 'ascii', buffer.length - tail.length);
return buffer;
}
process.on('exit', function() {
for (var k in callbacks) {
assert.equal(0, callbacks[k], k+' count off by '+callbacks[k]);
}
});
@@ -1,67 +0,0 @@
var common = require('../test/common'),
http = require('http'),
util = require('util'),
formidable = common.formidable,
Buffer = require('buffer').Buffer,
port = common.port,
server;
server = http.createServer(function(req, res) {
if (req.method !== 'POST') {
res.writeHead(200, {'content-type': 'text/plain'})
res.end('Please POST a JSON payload to http://localhost:'+port+'/')
return;
}
var form = new formidable.IncomingForm(),
fields = {};
form
.on('error', function(err) {
res.writeHead(500, {'content-type': 'text/plain'});
res.end('error:\n\n'+util.inspect(err));
console.error(err);
})
.on('field', function(field, value) {
console.log(field, value);
fields[field] = value;
})
.on('end', function() {
console.log('-> post done');
res.writeHead(200, {'content-type': 'text/plain'});
res.end('received fields:\n\n '+util.inspect(fields));
});
form.parse(req);
});
server.listen(port);
console.log('listening on http://localhost:'+port+'/');
var request = http.request({
host: 'localhost',
path: '/',
port: port,
method: 'POST',
headers: { 'content-type':'application/json', 'content-length':48 }
}, function(response) {
var data = '';
console.log('\nServer responded with:');
console.log('Status:', response.statusCode);
response.pipe(process.stdout);
response.on('end', function() {
console.log('\n')
process.exit();
});
// response.on('data', function(chunk) {
// data += chunk.toString('utf8');
// });
// response.on('end', function() {
// console.log('Response Data:')
// console.log(data);
// process.exit();
// });
})
request.write('{"numbers":[1,2,3,4,5],"nested":{"key":"value"}}');
request.end();
@@ -1,43 +0,0 @@
require('../test/common');
var http = require('http'),
util = require('util'),
formidable = require('formidable'),
server;
server = http.createServer(function(req, res) {
if (req.url == '/') {
res.writeHead(200, {'content-type': 'text/html'});
res.end(
'<form action="/post" method="post">'+
'<input type="text" name="title"><br>'+
'<input type="text" name="data[foo][]"><br>'+
'<input type="submit" value="Submit">'+
'</form>'
);
} else if (req.url == '/post') {
var form = new formidable.IncomingForm(),
fields = [];
form
.on('error', function(err) {
res.writeHead(200, {'content-type': 'text/plain'});
res.end('error:\n\n'+util.inspect(err));
})
.on('field', function(field, value) {
console.log(field, value);
fields.push([field, value]);
})
.on('end', function() {
console.log('-> post done');
res.writeHead(200, {'content-type': 'text/plain'});
res.end('received fields:\n\n '+util.inspect(fields));
});
form.parse(req);
} else {
res.writeHead(404, {'content-type': 'text/plain'});
res.end('404');
}
});
server.listen(TEST_PORT);
console.log('listening on http://localhost:'+TEST_PORT+'/');
@@ -1,48 +0,0 @@
require('../test/common');
var http = require('http'),
util = require('util'),
formidable = require('formidable'),
server;
server = http.createServer(function(req, res) {
if (req.url == '/') {
res.writeHead(200, {'content-type': 'text/html'});
res.end(
'<form action="/upload" enctype="multipart/form-data" method="post">'+
'<input type="text" name="title"><br>'+
'<input type="file" name="upload" multiple="multiple"><br>'+
'<input type="submit" value="Upload">'+
'</form>'
);
} else if (req.url == '/upload') {
var form = new formidable.IncomingForm(),
files = [],
fields = [];
form.uploadDir = TEST_TMP;
form
.on('field', function(field, value) {
console.log(field, value);
fields.push([field, value]);
})
.on('file', function(field, file) {
console.log(field, file);
files.push([field, file]);
})
.on('end', function() {
console.log('-> upload done');
res.writeHead(200, {'content-type': 'text/plain'});
res.write('received fields:\n\n '+util.inspect(fields));
res.write('\n\n');
res.end('received files:\n\n '+util.inspect(files));
});
form.parse(req);
} else {
res.writeHead(404, {'content-type': 'text/plain'});
res.end('404');
}
});
server.listen(TEST_PORT);
console.log('listening on http://localhost:'+TEST_PORT+'/');
@@ -1 +0,0 @@
module.exports = require('./lib');
@@ -1,72 +0,0 @@
if (global.GENTLY) require = GENTLY.hijack(require);
var util = require('util'),
WriteStream = require('fs').WriteStream,
EventEmitter = require('events').EventEmitter,
crypto = require('crypto');
function File(properties) {
EventEmitter.call(this);
this.size = 0;
this.path = null;
this.name = null;
this.type = null;
this.hash = null;
this.lastModifiedDate = null;
this._writeStream = null;
for (var key in properties) {
this[key] = properties[key];
}
if(typeof this.hash === 'string') {
this.hash = crypto.createHash(properties.hash);
} else {
this.hash = null;
}
}
module.exports = File;
util.inherits(File, EventEmitter);
File.prototype.open = function() {
this._writeStream = new WriteStream(this.path);
};
File.prototype.toJSON = function() {
return {
size: this.size,
path: this.path,
name: this.name,
type: this.type,
mtime: this.lastModifiedDate,
length: this.length,
filename: this.filename,
mime: this.mime
};
};
File.prototype.write = function(buffer, cb) {
var self = this;
if (self.hash) {
self.hash.update(buffer);
}
this._writeStream.write(buffer, function() {
self.lastModifiedDate = new Date();
self.size += buffer.length;
self.emit('progress', self.size);
cb();
});
};
File.prototype.end = function(cb) {
var self = this;
if (self.hash) {
self.hash = self.hash.digest('hex');
}
this._writeStream.end(function() {
self.emit('end');
cb();
});
};
@@ -1,535 +0,0 @@
if (global.GENTLY) require = GENTLY.hijack(require);
var fs = require('fs');
var util = require('util'),
path = require('path'),
File = require('./file'),
MultipartParser = require('./multipart_parser').MultipartParser,
QuerystringParser = require('./querystring_parser').QuerystringParser,
OctetParser = require('./octet_parser').OctetParser,
JSONParser = require('./json_parser').JSONParser,
StringDecoder = require('string_decoder').StringDecoder,
EventEmitter = require('events').EventEmitter,
Stream = require('stream').Stream,
os = require('os');
function IncomingForm(opts) {
if (!(this instanceof IncomingForm)) return new IncomingForm(opts);
EventEmitter.call(this);
opts=opts||{};
this.error = null;
this.ended = false;
this.maxFields = opts.maxFields || 1000;
this.maxFieldsSize = opts.maxFieldsSize || 2 * 1024 * 1024;
this.keepExtensions = opts.keepExtensions || false;
this.uploadDir = opts.uploadDir || os.tmpDir();
this.encoding = opts.encoding || 'utf-8';
this.headers = null;
this.type = null;
this.hash = false;
this.bytesReceived = null;
this.bytesExpected = null;
this._parser = null;
this._flushing = 0;
this._fieldsSize = 0;
this.openedFiles = [];
return this;
};
util.inherits(IncomingForm, EventEmitter);
exports.IncomingForm = IncomingForm;
IncomingForm.prototype.parse = function(req, cb) {
this.pause = function() {
try {
req.pause();
} catch (err) {
// the stream was destroyed
if (!this.ended) {
// before it was completed, crash & burn
this._error(err);
}
return false;
}
return true;
};
this.resume = function() {
try {
req.resume();
} catch (err) {
// the stream was destroyed
if (!this.ended) {
// before it was completed, crash & burn
this._error(err);
}
return false;
}
return true;
};
// Setup callback first, so we don't miss anything from data events emitted
// immediately.
if (cb) {
var fields = {}, files = {};
this
.on('field', function(name, value) {
fields[name] = value;
})
.on('file', function(name, file) {
files[name] = file;
})
.on('error', function(err) {
cb(err, fields, files);
})
.on('end', function() {
cb(null, fields, files);
});
}
// Parse headers and setup the parser, ready to start listening for data.
this.writeHeaders(req.headers);
// Start listening for data.
var self = this;
req
.on('error', function(err) {
self._error(err);
})
.on('aborted', function() {
self.emit('aborted');
self._error(new Error('Request aborted'));
})
.on('data', function(buffer) {
self.write(buffer);
})
.on('end', function() {
if (self.error) {
return;
}
var err = self._parser.end();
if (err) {
self._error(err);
}
});
return this;
};
IncomingForm.prototype.writeHeaders = function(headers) {
this.headers = headers;
this._parseContentLength();
this._parseContentType();
};
IncomingForm.prototype.write = function(buffer) {
if (!this._parser) {
this._error(new Error('unintialized parser'));
return;
}
this.bytesReceived += buffer.length;
this.emit('progress', this.bytesReceived, this.bytesExpected);
var bytesParsed = this._parser.write(buffer);
if (bytesParsed !== buffer.length) {
this._error(new Error('parser error, '+bytesParsed+' of '+buffer.length+' bytes parsed'));
}
return bytesParsed;
};
IncomingForm.prototype.pause = function() {
// this does nothing, unless overwritten in IncomingForm.parse
return false;
};
IncomingForm.prototype.resume = function() {
// this does nothing, unless overwritten in IncomingForm.parse
return false;
};
IncomingForm.prototype.onPart = function(part) {
// this method can be overwritten by the user
this.handlePart(part);
};
IncomingForm.prototype.handlePart = function(part) {
var self = this;
if (part.filename === undefined) {
var value = ''
, decoder = new StringDecoder(this.encoding);
part.on('data', function(buffer) {
self._fieldsSize += buffer.length;
if (self._fieldsSize > self.maxFieldsSize) {
self._error(new Error('maxFieldsSize exceeded, received '+self._fieldsSize+' bytes of field data'));
return;
}
value += decoder.write(buffer);
});
part.on('end', function() {
self.emit('field', part.name, value);
});
return;
}
this._flushing++;
var file = new File({
path: this._uploadPath(part.filename),
name: part.filename,
type: part.mime,
hash: self.hash
});
this.emit('fileBegin', part.name, file);
file.open();
this.openedFiles.push(file);
part.on('data', function(buffer) {
self.pause();
file.write(buffer, function() {
self.resume();
});
});
part.on('end', function() {
file.end(function() {
self._flushing--;
self.emit('file', part.name, file);
self._maybeEnd();
});
});
};
function dummyParser(self) {
return {
end: function () {
self.ended = true;
self._maybeEnd();
return null;
}
};
}
IncomingForm.prototype._parseContentType = function() {
if (this.bytesExpected === 0) {
this._parser = dummyParser(this);
return;
}
if (!this.headers['content-type']) {
this._error(new Error('bad content-type header, no content-type'));
return;
}
if (this.headers['content-type'].match(/octet-stream/i)) {
this._initOctetStream();
return;
}
if (this.headers['content-type'].match(/urlencoded/i)) {
this._initUrlencoded();
return;
}
if (this.headers['content-type'].match(/multipart/i)) {
var m;
if (m = this.headers['content-type'].match(/boundary=(?:"([^"]+)"|([^;]+))/i)) {
this._initMultipart(m[1] || m[2]);
} else {
this._error(new Error('bad content-type header, no multipart boundary'));
}
return;
}
if (this.headers['content-type'].match(/json/i)) {
this._initJSONencoded();
return;
}
this._error(new Error('bad content-type header, unknown content-type: '+this.headers['content-type']));
};
IncomingForm.prototype._error = function(err) {
if (this.error || this.ended) {
return;
}
this.error = err;
this.pause();
this.emit('error', err);
if (Array.isArray(this.openedFiles)) {
this.openedFiles.forEach(function(file) {
file._writeStream.destroy();
setTimeout(fs.unlink, 0, file.path);
});
}
};
IncomingForm.prototype._parseContentLength = function() {
this.bytesReceived = 0;
if (this.headers['content-length']) {
this.bytesExpected = parseInt(this.headers['content-length'], 10);
} else if (this.headers['transfer-encoding'] === undefined) {
this.bytesExpected = 0;
}
if (this.bytesExpected !== null) {
this.emit('progress', this.bytesReceived, this.bytesExpected);
}
};
IncomingForm.prototype._newParser = function() {
return new MultipartParser();
};
IncomingForm.prototype._initMultipart = function(boundary) {
this.type = 'multipart';
var parser = new MultipartParser(),
self = this,
headerField,
headerValue,
part;
parser.initWithBoundary(boundary);
parser.onPartBegin = function() {
part = new Stream();
part.readable = true;
part.headers = {};
part.name = null;
part.filename = null;
part.mime = null;
part.transferEncoding = 'binary';
part.transferBuffer = '';
headerField = '';
headerValue = '';
};
parser.onHeaderField = function(b, start, end) {
headerField += b.toString(self.encoding, start, end);
};
parser.onHeaderValue = function(b, start, end) {
headerValue += b.toString(self.encoding, start, end);
};
parser.onHeaderEnd = function() {
headerField = headerField.toLowerCase();
part.headers[headerField] = headerValue;
var m;
if (headerField == 'content-disposition') {
if (m = headerValue.match(/\bname="([^"]+)"/i)) {
part.name = m[1];
}
part.filename = self._fileName(headerValue);
} else if (headerField == 'content-type') {
part.mime = headerValue;
} else if (headerField == 'content-transfer-encoding') {
part.transferEncoding = headerValue.toLowerCase();
}
headerField = '';
headerValue = '';
};
parser.onHeadersEnd = function() {
switch(part.transferEncoding){
case 'binary':
case '7bit':
case '8bit':
parser.onPartData = function(b, start, end) {
part.emit('data', b.slice(start, end));
};
parser.onPartEnd = function() {
part.emit('end');
};
break;
case 'base64':
parser.onPartData = function(b, start, end) {
part.transferBuffer += b.slice(start, end).toString('ascii');
/*
four bytes (chars) in base64 converts to three bytes in binary
encoding. So we should always work with a number of bytes that
can be divided by 4, it will result in a number of buytes that
can be divided vy 3.
*/
var offset = parseInt(part.transferBuffer.length / 4) * 4;
part.emit('data', new Buffer(part.transferBuffer.substring(0, offset), 'base64'))
part.transferBuffer = part.transferBuffer.substring(offset);
};
parser.onPartEnd = function() {
part.emit('data', new Buffer(part.transferBuffer, 'base64'))
part.emit('end');
};
break;
default:
return self._error(new Error('unknown transfer-encoding'));
}
self.onPart(part);
};
parser.onEnd = function() {
self.ended = true;
self._maybeEnd();
};
this._parser = parser;
};
IncomingForm.prototype._fileName = function(headerValue) {
var m = headerValue.match(/\bfilename="(.*?)"($|; )/i);
if (!m) return;
var filename = m[1].substr(m[1].lastIndexOf('\\') + 1);
filename = filename.replace(/%22/g, '"');
filename = filename.replace(/&#([\d]{4});/g, function(m, code) {
return String.fromCharCode(code);
});
return filename;
};
IncomingForm.prototype._initUrlencoded = function() {
this.type = 'urlencoded';
var parser = new QuerystringParser(this.maxFields)
, self = this;
parser.onField = function(key, val) {
self.emit('field', key, val);
};
parser.onEnd = function() {
self.ended = true;
self._maybeEnd();
};
this._parser = parser;
};
IncomingForm.prototype._initOctetStream = function() {
this.type = 'octet-stream';
var filename = this.headers['x-file-name'];
var mime = this.headers['content-type'];
var file = new File({
path: this._uploadPath(filename),
name: filename,
type: mime
});
file.open();
this.emit('fileBegin', filename, file);
this._flushing++;
var self = this;
self._parser = new OctetParser();
//Keep track of writes that haven't finished so we don't emit the file before it's done being written
var outstandingWrites = 0;
self._parser.on('data', function(buffer){
self.pause();
outstandingWrites++;
file.write(buffer, function() {
outstandingWrites--;
self.resume();
if(self.ended){
self._parser.emit('doneWritingFile');
}
});
});
self._parser.on('end', function(){
self._flushing--;
self.ended = true;
var done = function(){
self.emit('file', 'file', file);
self._maybeEnd();
};
if(outstandingWrites === 0){
done();
} else {
self._parser.once('doneWritingFile', done);
}
});
};
IncomingForm.prototype._initJSONencoded = function() {
this.type = 'json';
var parser = new JSONParser()
, self = this;
if (this.bytesExpected) {
parser.initWithLength(this.bytesExpected);
}
parser.onField = function(key, val) {
self.emit('field', key, val);
}
parser.onEnd = function() {
self.ended = true;
self._maybeEnd();
};
this._parser = parser;
};
IncomingForm.prototype._uploadPath = function(filename) {
var name = '';
for (var i = 0; i < 32; i++) {
name += Math.floor(Math.random() * 16).toString(16);
}
if (this.keepExtensions) {
var ext = path.extname(filename);
ext = ext.replace(/(\.[a-z0-9]+).*/, '$1');
name += ext;
}
return path.join(this.uploadDir, name);
};
IncomingForm.prototype._maybeEnd = function() {
if (!this.ended || this._flushing || this.error) {
return;
}
this.emit('end');
};
@@ -1,3 +0,0 @@
var IncomingForm = require('./incoming_form').IncomingForm;
IncomingForm.IncomingForm = IncomingForm;
module.exports = IncomingForm;
@@ -1,35 +0,0 @@
if (global.GENTLY) require = GENTLY.hijack(require);
var Buffer = require('buffer').Buffer
function JSONParser() {
this.data = new Buffer('');
this.bytesWritten = 0;
};
exports.JSONParser = JSONParser;
JSONParser.prototype.initWithLength = function(length) {
this.data = new Buffer(length);
}
JSONParser.prototype.write = function(buffer) {
if (this.data.length >= this.bytesWritten + buffer.length) {
buffer.copy(this.data, this.bytesWritten);
} else {
this.data = Buffer.concat([this.data, buffer]);
}
this.bytesWritten += buffer.length;
return buffer.length;
}
JSONParser.prototype.end = function() {
try {
var fields = JSON.parse(this.data.toString('utf8'))
for (var field in fields) {
this.onField(field, fields[field]);
}
} catch (e) {}
this.data = null;
this.onEnd();
}
@@ -1,324 +0,0 @@
var Buffer = require('buffer').Buffer,
s = 0,
S =
{ PARSER_UNINITIALIZED: s++,
START: s++,
START_BOUNDARY: s++,
HEADER_FIELD_START: s++,
HEADER_FIELD: s++,
HEADER_VALUE_START: s++,
HEADER_VALUE: s++,
HEADER_VALUE_ALMOST_DONE: s++,
HEADERS_ALMOST_DONE: s++,
PART_DATA_START: s++,
PART_DATA: s++,
PART_END: s++,
END: s++
},
f = 1,
F =
{ PART_BOUNDARY: f,
LAST_BOUNDARY: f *= 2
},
LF = 10,
CR = 13,
SPACE = 32,
HYPHEN = 45,
COLON = 58,
A = 97,
Z = 122,
lower = function(c) {
return c | 0x20;
};
for (s in S) {
exports[s] = S[s];
}
function MultipartParser() {
this.boundary = null;
this.boundaryChars = null;
this.lookbehind = null;
this.state = S.PARSER_UNINITIALIZED;
this.index = null;
this.flags = 0;
};
exports.MultipartParser = MultipartParser;
MultipartParser.stateToString = function(stateNumber) {
for (var state in S) {
var number = S[state];
if (number === stateNumber) return state;
}
};
MultipartParser.prototype.initWithBoundary = function(str) {
this.boundary = new Buffer(str.length+4);
this.boundary.write('\r\n--', 'ascii', 0);
this.boundary.write(str, 'ascii', 4);
this.lookbehind = new Buffer(this.boundary.length+8);
this.state = S.START;
this.boundaryChars = {};
for (var i = 0; i < this.boundary.length; i++) {
this.boundaryChars[this.boundary[i]] = true;
}
};
MultipartParser.prototype.write = function(buffer) {
var self = this,
i = 0,
len = buffer.length,
prevIndex = this.index,
index = this.index,
state = this.state,
flags = this.flags,
lookbehind = this.lookbehind,
boundary = this.boundary,
boundaryChars = this.boundaryChars,
boundaryLength = this.boundary.length,
boundaryEnd = boundaryLength - 1,
bufferLength = buffer.length,
c,
cl,
mark = function(name) {
self[name+'Mark'] = i;
},
clear = function(name) {
delete self[name+'Mark'];
},
callback = function(name, buffer, start, end) {
if (start !== undefined && start === end) {
return;
}
var callbackSymbol = 'on'+name.substr(0, 1).toUpperCase()+name.substr(1);
if (callbackSymbol in self) {
self[callbackSymbol](buffer, start, end);
}
},
dataCallback = function(name, clear) {
var markSymbol = name+'Mark';
if (!(markSymbol in self)) {
return;
}
if (!clear) {
callback(name, buffer, self[markSymbol], buffer.length);
self[markSymbol] = 0;
} else {
callback(name, buffer, self[markSymbol], i);
delete self[markSymbol];
}
};
for (i = 0; i < len; i++) {
c = buffer[i];
switch (state) {
case S.PARSER_UNINITIALIZED:
return i;
case S.START:
index = 0;
state = S.START_BOUNDARY;
case S.START_BOUNDARY:
if (index == boundary.length - 2) {
if (c != CR) {
return i;
}
index++;
break;
} else if (index - 1 == boundary.length - 2) {
if (c != LF) {
return i;
}
index = 0;
callback('partBegin');
state = S.HEADER_FIELD_START;
break;
}
if (c != boundary[index+2]) {
index = -2;
}
if (c == boundary[index+2]) {
index++;
}
break;
case S.HEADER_FIELD_START:
state = S.HEADER_FIELD;
mark('headerField');
index = 0;
case S.HEADER_FIELD:
if (c == CR) {
clear('headerField');
state = S.HEADERS_ALMOST_DONE;
break;
}
index++;
if (c == HYPHEN) {
break;
}
if (c == COLON) {
if (index == 1) {
// empty header field
return i;
}
dataCallback('headerField', true);
state = S.HEADER_VALUE_START;
break;
}
cl = lower(c);
if (cl < A || cl > Z) {
return i;
}
break;
case S.HEADER_VALUE_START:
if (c == SPACE) {
break;
}
mark('headerValue');
state = S.HEADER_VALUE;
case S.HEADER_VALUE:
if (c == CR) {
dataCallback('headerValue', true);
callback('headerEnd');
state = S.HEADER_VALUE_ALMOST_DONE;
}
break;
case S.HEADER_VALUE_ALMOST_DONE:
if (c != LF) {
return i;
}
state = S.HEADER_FIELD_START;
break;
case S.HEADERS_ALMOST_DONE:
if (c != LF) {
return i;
}
callback('headersEnd');
state = S.PART_DATA_START;
break;
case S.PART_DATA_START:
state = S.PART_DATA;
mark('partData');
case S.PART_DATA:
prevIndex = index;
if (index == 0) {
// boyer-moore derrived algorithm to safely skip non-boundary data
i += boundaryEnd;
while (i < bufferLength && !(buffer[i] in boundaryChars)) {
i += boundaryLength;
}
i -= boundaryEnd;
c = buffer[i];
}
if (index < boundary.length) {
if (boundary[index] == c) {
if (index == 0) {
dataCallback('partData', true);
}
index++;
} else {
index = 0;
}
} else if (index == boundary.length) {
index++;
if (c == CR) {
// CR = part boundary
flags |= F.PART_BOUNDARY;
} else if (c == HYPHEN) {
// HYPHEN = end boundary
flags |= F.LAST_BOUNDARY;
} else {
index = 0;
}
} else if (index - 1 == boundary.length) {
if (flags & F.PART_BOUNDARY) {
index = 0;
if (c == LF) {
// unset the PART_BOUNDARY flag
flags &= ~F.PART_BOUNDARY;
callback('partEnd');
callback('partBegin');
state = S.HEADER_FIELD_START;
break;
}
} else if (flags & F.LAST_BOUNDARY) {
if (c == HYPHEN) {
callback('partEnd');
callback('end');
state = S.END;
} else {
index = 0;
}
} else {
index = 0;
}
}
if (index > 0) {
// when matching a possible boundary, keep a lookbehind reference
// in case it turns out to be a false lead
lookbehind[index-1] = c;
} else if (prevIndex > 0) {
// if our boundary turned out to be rubbish, the captured lookbehind
// belongs to partData
callback('partData', lookbehind, 0, prevIndex);
prevIndex = 0;
mark('partData');
// reconsider the current character even so it interrupted the sequence
// it could be the beginning of a new sequence
i--;
}
break;
case S.END:
break;
default:
return i;
}
}
dataCallback('headerField');
dataCallback('headerValue');
dataCallback('partData');
this.index = index;
this.state = state;
this.flags = flags;
return len;
};
MultipartParser.prototype.end = function() {
var callback = function(self, name) {
var callbackSymbol = 'on'+name.substr(0, 1).toUpperCase()+name.substr(1);
if (callbackSymbol in self) {
self[callbackSymbol]();
}
};
if ((this.state == S.HEADER_FIELD_START && this.index == 0) ||
(this.state == S.PART_DATA && this.index == this.boundary.length)) {
callback(this, 'partEnd');
callback(this, 'end');
} else if (this.state != S.END) {
return new Error('MultipartParser.end(): stream ended unexpectedly: ' + this.explain());
}
};
MultipartParser.prototype.explain = function() {
return 'state = ' + MultipartParser.stateToString(this.state);
};
@@ -1,20 +0,0 @@
var EventEmitter = require('events').EventEmitter
, util = require('util');
function OctetParser(options){
if(!(this instanceof OctetParser)) return new OctetParser(options);
EventEmitter.call(this);
}
util.inherits(OctetParser, EventEmitter);
exports.OctetParser = OctetParser;
OctetParser.prototype.write = function(buffer) {
this.emit('data', buffer);
return buffer.length;
};
OctetParser.prototype.end = function() {
this.emit('end');
};
@@ -1,27 +0,0 @@
if (global.GENTLY) require = GENTLY.hijack(require);
// This is a buffering parser, not quite as nice as the multipart one.
// If I find time I'll rewrite this to be fully streaming as well
var querystring = require('querystring');
function QuerystringParser(maxKeys) {
this.maxKeys = maxKeys;
this.buffer = '';
};
exports.QuerystringParser = QuerystringParser;
QuerystringParser.prototype.write = function(buffer) {
this.buffer += buffer.toString('ascii');
return buffer.length;
};
QuerystringParser.prototype.end = function() {
var fields = querystring.parse(this.buffer, '&', '=', { maxKeys: this.maxKeys });
for (var field in fields) {
this.onField(field, fields[field]);
}
this.buffer = '';
this.onEnd();
};
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
@@ -1,18 +0,0 @@
var path = require('path');
var root = path.join(__dirname, '../');
exports.dir = {
root : root,
lib : root + '/lib',
fixture : root + '/test/fixture',
tmp : root + '/test/tmp',
};
exports.port = 13532;
exports.formidable = require('..');
exports.assert = require('assert');
exports.require = function(lib) {
return require(exports.dir.lib + '/' + lib);
};
@@ -1,24 +0,0 @@
module.exports['menu_seperator.png.http'] = [
{type: 'file', name: 'image', filename: 'menu_separator.png', fixture: 'menu_separator.png',
sha1: 'c845ca3ea794be298f2a1b79769b71939eaf4e54'}
];
module.exports['beta-sticker-1.png.http'] = [
{type: 'file', name: 'sticker', filename: 'beta-sticker-1.png', fixture: 'beta-sticker-1.png',
sha1: '6abbcffd12b4ada5a6a084fe9e4584f846331bc4'}
];
module.exports['blank.gif.http'] = [
{type: 'file', name: 'file', filename: 'blank.gif', fixture: 'blank.gif',
sha1: 'a1fdee122b95748d81cee426d717c05b5174fe96'}
];
module.exports['binaryfile.tar.gz.http'] = [
{type: 'file', name: 'file', filename: 'binaryfile.tar.gz', fixture: 'binaryfile.tar.gz',
sha1: 'cfabe13b348e5e69287d677860880c52a69d2155'}
];
module.exports['plain.txt.http'] = [
{type: 'file', name: 'file', filename: 'plain.txt', fixture: 'plain.txt',
sha1: 'b31d07bac24ac32734de88b3687dddb10e976872'}
];
@@ -1,6 +0,0 @@
module.exports = {
'empty.http': [],
'empty-urlencoded.http': [],
'empty-multipart.http': [],
'minimal.http': [],
};
@@ -1,96 +0,0 @@
var hashish = require('hashish');
var fs = require('fs');
var findit = require('findit');
var path = require('path');
var http = require('http');
var net = require('net');
var assert = require('assert');
var common = require('../common');
var formidable = common.formidable;
var server = http.createServer();
server.listen(common.port, findFixtures);
function findFixtures() {
var fixtures = [];
findit
.sync(common.dir.fixture + '/js')
.forEach(function(jsPath) {
if (!/\.js$/.test(jsPath)) return;
var group = path.basename(jsPath, '.js');
hashish.forEach(require(jsPath), function(fixture, name) {
fixtures.push({
name : group + '/' + name,
fixture : fixture,
});
});
});
testNext(fixtures);
}
function testNext(fixtures) {
var fixture = fixtures.shift();
if (!fixture) return server.close();
var name = fixture.name;
var fixture = fixture.fixture;
uploadFixture(name, function(err, parts) {
if (err) throw err;
fixture.forEach(function(expectedPart, i) {
var parsedPart = parts[i];
assert.equal(parsedPart.type, expectedPart.type);
assert.equal(parsedPart.name, expectedPart.name);
if (parsedPart.type === 'file') {
var file = parsedPart.value;
assert.equal(file.name, expectedPart.filename);
if(expectedPart.sha1) assert.equal(file.hash, expectedPart.sha1);
}
});
testNext(fixtures);
});
};
function uploadFixture(name, cb) {
server.once('request', function(req, res) {
var form = new formidable.IncomingForm();
form.uploadDir = common.dir.tmp;
form.hash = "sha1";
form.parse(req);
function callback() {
var realCallback = cb;
cb = function() {};
realCallback.apply(null, arguments);
}
var parts = [];
form
.on('error', callback)
.on('fileBegin', function(name, value) {
parts.push({type: 'file', name: name, value: value});
})
.on('field', function(name, value) {
parts.push({type: 'field', name: name, value: value});
})
.on('end', function() {
res.end('OK');
callback(null, parts);
});
});
var socket = net.createConnection(common.port);
var file = fs.createReadStream(common.dir.fixture + '/http/' + name);
file.pipe(socket, {end: false});
socket.on('data', function () {
socket.end();
});
}
@@ -1,38 +0,0 @@
var common = require('../common');
var formidable = common.formidable;
var http = require('http');
var assert = require('assert');
var testData = {
numbers: [1, 2, 3, 4, 5],
nested: { key: 'value' }
};
var server = http.createServer(function(req, res) {
var form = new formidable.IncomingForm();
form.parse(req, function(err, fields, files) {
assert.deepEqual(fields, testData);
res.end();
server.close();
});
});
var port = common.port;
server.listen(port, function(err){
assert.equal(err, null);
var request = http.request({
port: port,
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
});
request.write(JSON.stringify(testData));
request.end();
});
@@ -1,45 +0,0 @@
var common = require('../common');
var formidable = common.formidable;
var http = require('http');
var fs = require('fs');
var path = require('path');
var hashish = require('hashish');
var assert = require('assert');
var testFilePath = path.join(__dirname, '../fixture/file/binaryfile.tar.gz');
var server = http.createServer(function(req, res) {
var form = new formidable.IncomingForm();
form.parse(req, function(err, fields, files) {
assert.equal(hashish(files).length, 1);
var file = files.file;
assert.equal(file.size, 301);
var uploaded = fs.readFileSync(file.path);
var original = fs.readFileSync(testFilePath);
assert.deepEqual(uploaded, original);
res.end();
server.close();
});
});
var port = common.port;
server.listen(port, function(err){
assert.equal(err, null);
var request = http.request({
port: port,
method: 'POST',
headers: {
'Content-Type': 'application/octet-stream'
}
});
fs.createReadStream(testFilePath).pipe(request);
});
@@ -1,24 +0,0 @@
var path = require('path'),
fs = require('fs');
try {
global.Gently = require('gently');
} catch (e) {
throw new Error('this test suite requires node-gently');
}
exports.lib = path.join(__dirname, '../../lib');
global.GENTLY = new Gently();
global.assert = require('assert');
global.TEST_PORT = 13532;
global.TEST_FIXTURES = path.join(__dirname, '../fixture');
global.TEST_TMP = path.join(__dirname, '../tmp');
// Stupid new feature in node that complains about gently attaching too many
// listeners to process 'exit'. This is a workaround until I can think of a
// better way to deal with this.
if (process.setMaxListeners) {
process.setMaxListeners(10000);
}
@@ -1,80 +0,0 @@
var common = require('../common');
var CHUNK_LENGTH = 10,
multipartParser = require(common.lib + '/multipart_parser'),
MultipartParser = multipartParser.MultipartParser,
parser = new MultipartParser(),
fixtures = require(TEST_FIXTURES + '/multipart'),
Buffer = require('buffer').Buffer;
Object.keys(fixtures).forEach(function(name) {
var fixture = fixtures[name],
buffer = new Buffer(Buffer.byteLength(fixture.raw, 'binary')),
offset = 0,
chunk,
nparsed,
parts = [],
part = null,
headerField,
headerValue,
endCalled = '';
parser.initWithBoundary(fixture.boundary);
parser.onPartBegin = function() {
part = {headers: {}, data: ''};
parts.push(part);
headerField = '';
headerValue = '';
};
parser.onHeaderField = function(b, start, end) {
headerField += b.toString('ascii', start, end);
};
parser.onHeaderValue = function(b, start, end) {
headerValue += b.toString('ascii', start, end);
}
parser.onHeaderEnd = function() {
part.headers[headerField] = headerValue;
headerField = '';
headerValue = '';
};
parser.onPartData = function(b, start, end) {
var str = b.toString('ascii', start, end);
part.data += b.slice(start, end);
}
parser.onEnd = function() {
endCalled = true;
}
buffer.write(fixture.raw, 'binary', 0);
while (offset < buffer.length) {
if (offset + CHUNK_LENGTH < buffer.length) {
chunk = buffer.slice(offset, offset+CHUNK_LENGTH);
} else {
chunk = buffer.slice(offset, buffer.length);
}
offset = offset + CHUNK_LENGTH;
nparsed = parser.write(chunk);
if (nparsed != chunk.length) {
if (fixture.expectError) {
return;
}
puts('-- ERROR --');
p(chunk.toString('ascii'));
throw new Error(chunk.length+' bytes written, but only '+nparsed+' bytes parsed!');
}
}
if (fixture.expectError) {
throw new Error('expected parse error did not happen');
}
assert.ok(endCalled);
assert.deepEqual(parts, fixture.parts);
});
@@ -1,104 +0,0 @@
var common = require('../common');
var WriteStreamStub = GENTLY.stub('fs', 'WriteStream');
var File = require(common.lib + '/file'),
EventEmitter = require('events').EventEmitter,
file,
gently;
function test(test) {
gently = new Gently();
file = new File();
test();
gently.verify(test.name);
}
test(function constructor() {
assert.ok(file instanceof EventEmitter);
assert.strictEqual(file.size, 0);
assert.strictEqual(file.path, null);
assert.strictEqual(file.name, null);
assert.strictEqual(file.type, null);
assert.strictEqual(file.lastModifiedDate, null);
assert.strictEqual(file._writeStream, null);
(function testSetProperties() {
var file2 = new File({foo: 'bar'});
assert.equal(file2.foo, 'bar');
})();
});
test(function open() {
var WRITE_STREAM;
file.path = '/foo';
gently.expect(WriteStreamStub, 'new', function (path) {
WRITE_STREAM = this;
assert.strictEqual(path, file.path);
});
file.open();
assert.strictEqual(file._writeStream, WRITE_STREAM);
});
test(function write() {
var BUFFER = {length: 10},
CB_STUB,
CB = function() {
CB_STUB.apply(this, arguments);
};
file._writeStream = {};
gently.expect(file._writeStream, 'write', function (buffer, cb) {
assert.strictEqual(buffer, BUFFER);
gently.expect(file, 'emit', function (event, bytesWritten) {
assert.ok(file.lastModifiedDate instanceof Date);
assert.equal(event, 'progress');
assert.equal(bytesWritten, file.size);
});
CB_STUB = gently.expect(function writeCb() {
assert.equal(file.size, 10);
});
cb();
gently.expect(file, 'emit', function (event, bytesWritten) {
assert.equal(event, 'progress');
assert.equal(bytesWritten, file.size);
});
CB_STUB = gently.expect(function writeCb() {
assert.equal(file.size, 20);
});
cb();
});
file.write(BUFFER, CB);
});
test(function end() {
var CB_STUB,
CB = function() {
CB_STUB.apply(this, arguments);
};
file._writeStream = {};
gently.expect(file._writeStream, 'end', function (cb) {
gently.expect(file, 'emit', function (event) {
assert.equal(event, 'end');
});
CB_STUB = gently.expect(function endCb() {
});
cb();
});
file.end(CB);
});
@@ -1,756 +0,0 @@
var common = require('../common');
var MultipartParserStub = GENTLY.stub('./multipart_parser', 'MultipartParser'),
QuerystringParserStub = GENTLY.stub('./querystring_parser', 'QuerystringParser'),
EventEmitterStub = GENTLY.stub('events', 'EventEmitter'),
StreamStub = GENTLY.stub('stream', 'Stream'),
FileStub = GENTLY.stub('./file');
var formidable = require(common.lib + '/index'),
IncomingForm = formidable.IncomingForm,
events = require('events'),
fs = require('fs'),
path = require('path'),
Buffer = require('buffer').Buffer,
fixtures = require(TEST_FIXTURES + '/multipart'),
form,
gently;
function test(test) {
gently = new Gently();
gently.expect(EventEmitterStub, 'call');
form = new IncomingForm();
test();
gently.verify(test.name);
}
test(function constructor() {
assert.strictEqual(form.error, null);
assert.strictEqual(form.ended, false);
assert.strictEqual(form.type, null);
assert.strictEqual(form.headers, null);
assert.strictEqual(form.keepExtensions, false);
// Can't assume dir === '/tmp' for portability
// assert.strictEqual(form.uploadDir, '/tmp');
// Make sure it is a directory instead
assert.doesNotThrow(function () {
assert(fs.statSync(form.uploadDir).isDirectory());
});
assert.strictEqual(form.encoding, 'utf-8');
assert.strictEqual(form.bytesReceived, null);
assert.strictEqual(form.bytesExpected, null);
assert.strictEqual(form.maxFieldsSize, 2 * 1024 * 1024);
assert.strictEqual(form._parser, null);
assert.strictEqual(form._flushing, 0);
assert.strictEqual(form._fieldsSize, 0);
assert.ok(form instanceof EventEmitterStub);
assert.equal(form.constructor.name, 'IncomingForm');
(function testSimpleConstructor() {
gently.expect(EventEmitterStub, 'call');
var form = IncomingForm();
assert.ok(form instanceof IncomingForm);
})();
(function testSimpleConstructorShortcut() {
gently.expect(EventEmitterStub, 'call');
var form = formidable();
assert.ok(form instanceof IncomingForm);
})();
});
test(function parse() {
var REQ = {headers: {}}
, emit = {};
gently.expect(form, 'writeHeaders', function(headers) {
assert.strictEqual(headers, REQ.headers);
});
var EVENTS = ['error', 'aborted', 'data', 'end'];
gently.expect(REQ, 'on', EVENTS.length, function(event, fn) {
assert.equal(event, EVENTS.shift());
emit[event] = fn;
return this;
});
form.parse(REQ);
(function testPause() {
gently.expect(REQ, 'pause');
assert.strictEqual(form.pause(), true);
})();
(function testPauseCriticalException() {
form.ended = false;
var ERR = new Error('dasdsa');
gently.expect(REQ, 'pause', function() {
throw ERR;
});
gently.expect(form, '_error', function(err) {
assert.strictEqual(err, ERR);
});
assert.strictEqual(form.pause(), false);
})();
(function testPauseHarmlessException() {
form.ended = true;
var ERR = new Error('dasdsa');
gently.expect(REQ, 'pause', function() {
throw ERR;
});
assert.strictEqual(form.pause(), false);
})();
(function testResume() {
gently.expect(REQ, 'resume');
assert.strictEqual(form.resume(), true);
})();
(function testResumeCriticalException() {
form.ended = false;
var ERR = new Error('dasdsa');
gently.expect(REQ, 'resume', function() {
throw ERR;
});
gently.expect(form, '_error', function(err) {
assert.strictEqual(err, ERR);
});
assert.strictEqual(form.resume(), false);
})();
(function testResumeHarmlessException() {
form.ended = true;
var ERR = new Error('dasdsa');
gently.expect(REQ, 'resume', function() {
throw ERR;
});
assert.strictEqual(form.resume(), false);
})();
(function testEmitError() {
var ERR = new Error('something bad happened');
gently.expect(form, '_error',function(err) {
assert.strictEqual(err, ERR);
});
emit.error(ERR);
})();
(function testEmitAborted() {
gently.expect(form, 'emit',function(event) {
assert.equal(event, 'aborted');
});
gently.expect(form, '_error');
emit.aborted();
})();
(function testEmitData() {
var BUFFER = [1, 2, 3];
gently.expect(form, 'write', function(buffer) {
assert.strictEqual(buffer, BUFFER);
});
emit.data(BUFFER);
})();
(function testEmitEnd() {
form._parser = {};
(function testWithError() {
var ERR = new Error('haha');
gently.expect(form._parser, 'end', function() {
return ERR;
});
gently.expect(form, '_error', function(err) {
assert.strictEqual(err, ERR);
});
emit.end();
})();
(function testWithoutError() {
gently.expect(form._parser, 'end');
emit.end();
})();
(function testAfterError() {
form.error = true;
emit.end();
})();
})();
(function testWithCallback() {
gently.expect(EventEmitterStub, 'call');
var form = new IncomingForm(),
REQ = {headers: {}},
parseCalled = 0;
gently.expect(form, 'on', 4, function(event, fn) {
if (event == 'field') {
fn('field1', 'foo');
fn('field1', 'bar');
fn('field2', 'nice');
}
if (event == 'file') {
fn('file1', '1');
fn('file1', '2');
fn('file2', '3');
}
if (event == 'end') {
fn();
}
return this;
});
gently.expect(form, 'writeHeaders');
gently.expect(REQ, 'on', 4, function() {
return this;
});
var parseCbOk = function (err, fields, files) {
assert.deepEqual(fields, {field1: 'bar', field2: 'nice'});
assert.deepEqual(files, {file1: '2', file2: '3'});
};
form.parse(REQ, parseCbOk);
var ERR = new Error('test');
gently.expect(form, 'on', 3, function(event, fn) {
if (event == 'field') {
fn('foo', 'bar');
}
if (event == 'error') {
fn(ERR);
gently.expect(form, 'on');
gently.expect(form, 'writeHeaders');
gently.expect(REQ, 'on', 4, function() {
return this;
});
}
return this;
});
form.parse(REQ, function parseCbErr(err, fields, files) {
assert.strictEqual(err, ERR);
assert.deepEqual(fields, {foo: 'bar'});
});
})();
(function testWriteOrder() {
gently.expect(EventEmitterStub, 'call');
var form = new IncomingForm();
var REQ = new events.EventEmitter();
var BUF = {};
var DATACB = null;
REQ.on('newListener', function(event, fn) {
if ('data' === event) fn(BUF);
});
gently.expect(form, 'writeHeaders');
gently.expect(form, 'write', function(buf) {
assert.strictEqual(buf, BUF);
});
form.parse(REQ);
})();
});
test(function pause() {
assert.strictEqual(form.pause(), false);
});
test(function resume() {
assert.strictEqual(form.resume(), false);
});
test(function writeHeaders() {
var HEADERS = {};
gently.expect(form, '_parseContentLength');
gently.expect(form, '_parseContentType');
form.writeHeaders(HEADERS);
assert.strictEqual(form.headers, HEADERS);
});
test(function write() {
var parser = {},
BUFFER = [1, 2, 3];
form._parser = parser;
form.bytesExpected = 523423;
(function testBasic() {
gently.expect(form, 'emit', function(event, bytesReceived, bytesExpected) {
assert.equal(event, 'progress');
assert.equal(bytesReceived, BUFFER.length);
assert.equal(bytesExpected, form.bytesExpected);
});
gently.expect(parser, 'write', function(buffer) {
assert.strictEqual(buffer, BUFFER);
return buffer.length;
});
assert.equal(form.write(BUFFER), BUFFER.length);
assert.equal(form.bytesReceived, BUFFER.length);
})();
(function testParserError() {
gently.expect(form, 'emit');
gently.expect(parser, 'write', function(buffer) {
assert.strictEqual(buffer, BUFFER);
return buffer.length - 1;
});
gently.expect(form, '_error', function(err) {
assert.ok(err.message.match(/parser error/i));
});
assert.equal(form.write(BUFFER), BUFFER.length - 1);
assert.equal(form.bytesReceived, BUFFER.length + BUFFER.length);
})();
(function testUninitialized() {
delete form._parser;
gently.expect(form, '_error', function(err) {
assert.ok(err.message.match(/unintialized parser/i));
});
form.write(BUFFER);
})();
});
test(function parseContentType() {
var HEADERS = {};
form.headers = {'content-type': 'application/x-www-form-urlencoded'};
gently.expect(form, '_initUrlencoded');
form._parseContentType();
// accept anything that has 'urlencoded' in it
form.headers = {'content-type': 'broken-client/urlencoded-stupid'};
gently.expect(form, '_initUrlencoded');
form._parseContentType();
var BOUNDARY = '---------------------------57814261102167618332366269';
form.headers = {'content-type': 'multipart/form-data; boundary='+BOUNDARY};
gently.expect(form, '_initMultipart', function(boundary) {
assert.equal(boundary, BOUNDARY);
});
form._parseContentType();
(function testQuotedBoundary() {
form.headers = {'content-type': 'multipart/form-data; boundary="' + BOUNDARY + '"'};
gently.expect(form, '_initMultipart', function(boundary) {
assert.equal(boundary, BOUNDARY);
});
form._parseContentType();
})();
(function testNoBoundary() {
form.headers = {'content-type': 'multipart/form-data'};
gently.expect(form, '_error', function(err) {
assert.ok(err.message.match(/no multipart boundary/i));
});
form._parseContentType();
})();
(function testNoContentType() {
form.headers = {};
gently.expect(form, '_error', function(err) {
assert.ok(err.message.match(/no content-type/i));
});
form._parseContentType();
})();
(function testUnknownContentType() {
form.headers = {'content-type': 'invalid'};
gently.expect(form, '_error', function(err) {
assert.ok(err.message.match(/unknown content-type/i));
});
form._parseContentType();
})();
});
test(function parseContentLength() {
var HEADERS = {};
form.headers = {};
gently.expect(form, 'emit', function(event, bytesReceived, bytesExpected) {
assert.equal(event, 'progress');
assert.equal(bytesReceived, 0);
assert.equal(bytesExpected, 0);
});
form._parseContentLength();
form.headers['content-length'] = '8';
gently.expect(form, 'emit', function(event, bytesReceived, bytesExpected) {
assert.equal(event, 'progress');
assert.equal(bytesReceived, 0);
assert.equal(bytesExpected, 8);
});
form._parseContentLength();
assert.strictEqual(form.bytesReceived, 0);
assert.strictEqual(form.bytesExpected, 8);
// JS can be evil, lets make sure we are not
form.headers['content-length'] = '08';
gently.expect(form, 'emit', function(event, bytesReceived, bytesExpected) {
assert.equal(event, 'progress');
assert.equal(bytesReceived, 0);
assert.equal(bytesExpected, 8);
});
form._parseContentLength();
assert.strictEqual(form.bytesExpected, 8);
});
test(function _initMultipart() {
var BOUNDARY = '123',
PARSER;
gently.expect(MultipartParserStub, 'new', function() {
PARSER = this;
});
gently.expect(MultipartParserStub.prototype, 'initWithBoundary', function(boundary) {
assert.equal(boundary, BOUNDARY);
});
form._initMultipart(BOUNDARY);
assert.equal(form.type, 'multipart');
assert.strictEqual(form._parser, PARSER);
(function testRegularField() {
var PART;
gently.expect(StreamStub, 'new', function() {
PART = this;
});
gently.expect(form, 'onPart', function(part) {
assert.strictEqual(part, PART);
assert.deepEqual
( part.headers
, { 'content-disposition': 'form-data; name="field1"'
, 'foo': 'bar'
}
);
assert.equal(part.name, 'field1');
var strings = ['hello', ' world'];
gently.expect(part, 'emit', 2, function(event, b) {
assert.equal(event, 'data');
assert.equal(b.toString(), strings.shift());
});
gently.expect(part, 'emit', function(event, b) {
assert.equal(event, 'end');
});
});
PARSER.onPartBegin();
PARSER.onHeaderField(new Buffer('content-disposition'), 0, 10);
PARSER.onHeaderField(new Buffer('content-disposition'), 10, 19);
PARSER.onHeaderValue(new Buffer('form-data; name="field1"'), 0, 14);
PARSER.onHeaderValue(new Buffer('form-data; name="field1"'), 14, 24);
PARSER.onHeaderEnd();
PARSER.onHeaderField(new Buffer('foo'), 0, 3);
PARSER.onHeaderValue(new Buffer('bar'), 0, 3);
PARSER.onHeaderEnd();
PARSER.onHeadersEnd();
PARSER.onPartData(new Buffer('hello world'), 0, 5);
PARSER.onPartData(new Buffer('hello world'), 5, 11);
PARSER.onPartEnd();
})();
(function testFileField() {
var PART;
gently.expect(StreamStub, 'new', function() {
PART = this;
});
gently.expect(form, 'onPart', function(part) {
assert.deepEqual
( part.headers
, { 'content-disposition': 'form-data; name="field2"; filename="C:\\Documents and Settings\\IE\\Must\\Die\\Sun"et.jpg"'
, 'content-type': 'text/plain'
}
);
assert.equal(part.name, 'field2');
assert.equal(part.filename, 'Sun"et.jpg');
assert.equal(part.mime, 'text/plain');
gently.expect(part, 'emit', function(event, b) {
assert.equal(event, 'data');
assert.equal(b.toString(), '... contents of file1.txt ...');
});
gently.expect(part, 'emit', function(event, b) {
assert.equal(event, 'end');
});
});
PARSER.onPartBegin();
PARSER.onHeaderField(new Buffer('content-disposition'), 0, 19);
PARSER.onHeaderValue(new Buffer('form-data; name="field2"; filename="C:\\Documents and Settings\\IE\\Must\\Die\\Sun"et.jpg"'), 0, 85);
PARSER.onHeaderEnd();
PARSER.onHeaderField(new Buffer('Content-Type'), 0, 12);
PARSER.onHeaderValue(new Buffer('text/plain'), 0, 10);
PARSER.onHeaderEnd();
PARSER.onHeadersEnd();
PARSER.onPartData(new Buffer('... contents of file1.txt ...'), 0, 29);
PARSER.onPartEnd();
})();
(function testEnd() {
gently.expect(form, '_maybeEnd');
PARSER.onEnd();
assert.ok(form.ended);
})();
});
test(function _fileName() {
// TODO
return;
});
test(function _initUrlencoded() {
var PARSER;
gently.expect(QuerystringParserStub, 'new', function() {
PARSER = this;
});
form._initUrlencoded();
assert.equal(form.type, 'urlencoded');
assert.strictEqual(form._parser, PARSER);
(function testOnField() {
var KEY = 'KEY', VAL = 'VAL';
gently.expect(form, 'emit', function(field, key, val) {
assert.equal(field, 'field');
assert.equal(key, KEY);
assert.equal(val, VAL);
});
PARSER.onField(KEY, VAL);
})();
(function testOnEnd() {
gently.expect(form, '_maybeEnd');
PARSER.onEnd();
assert.equal(form.ended, true);
})();
});
test(function _error() {
var ERR = new Error('bla');
gently.expect(form, 'pause');
gently.expect(form, 'emit', function(event, err) {
assert.equal(event, 'error');
assert.strictEqual(err, ERR);
});
form._error(ERR);
assert.strictEqual(form.error, ERR);
// make sure _error only does its thing once
form._error(ERR);
});
test(function onPart() {
var PART = {};
gently.expect(form, 'handlePart', function(part) {
assert.strictEqual(part, PART);
});
form.onPart(PART);
});
test(function handlePart() {
(function testUtf8Field() {
var PART = new events.EventEmitter();
PART.name = 'my_field';
gently.expect(form, 'emit', function(event, field, value) {
assert.equal(event, 'field');
assert.equal(field, 'my_field');
assert.equal(value, 'hello world: €');
});
form.handlePart(PART);
PART.emit('data', new Buffer('hello'));
PART.emit('data', new Buffer(' world: '));
PART.emit('data', new Buffer([0xE2]));
PART.emit('data', new Buffer([0x82, 0xAC]));
PART.emit('end');
})();
(function testBinaryField() {
var PART = new events.EventEmitter();
PART.name = 'my_field2';
gently.expect(form, 'emit', function(event, field, value) {
assert.equal(event, 'field');
assert.equal(field, 'my_field2');
assert.equal(value, 'hello world: '+new Buffer([0xE2, 0x82, 0xAC]).toString('binary'));
});
form.encoding = 'binary';
form.handlePart(PART);
PART.emit('data', new Buffer('hello'));
PART.emit('data', new Buffer(' world: '));
PART.emit('data', new Buffer([0xE2]));
PART.emit('data', new Buffer([0x82, 0xAC]));
PART.emit('end');
})();
(function testFieldSize() {
form.maxFieldsSize = 8;
var PART = new events.EventEmitter();
PART.name = 'my_field';
gently.expect(form, '_error', function(err) {
assert.equal(err.message, 'maxFieldsSize exceeded, received 9 bytes of field data');
});
form.handlePart(PART);
form._fieldsSize = 1;
PART.emit('data', new Buffer(7));
PART.emit('data', new Buffer(1));
})();
(function testFilePart() {
var PART = new events.EventEmitter(),
FILE = new events.EventEmitter(),
PATH = '/foo/bar';
PART.name = 'my_file';
PART.filename = 'sweet.txt';
PART.mime = 'sweet.txt';
gently.expect(form, '_uploadPath', function(filename) {
assert.equal(filename, PART.filename);
return PATH;
});
gently.expect(FileStub, 'new', function(properties) {
assert.equal(properties.path, PATH);
assert.equal(properties.name, PART.filename);
assert.equal(properties.type, PART.mime);
FILE = this;
gently.expect(form, 'emit', function (event, field, file) {
assert.equal(event, 'fileBegin');
assert.strictEqual(field, PART.name);
assert.strictEqual(file, FILE);
});
gently.expect(FILE, 'open');
});
form.handlePart(PART);
assert.equal(form._flushing, 1);
var BUFFER;
gently.expect(form, 'pause');
gently.expect(FILE, 'write', function(buffer, cb) {
assert.strictEqual(buffer, BUFFER);
gently.expect(form, 'resume');
// @todo handle cb(new Err)
cb();
});
PART.emit('data', BUFFER = new Buffer('test'));
gently.expect(FILE, 'end', function(cb) {
gently.expect(form, 'emit', function(event, field, file) {
assert.equal(event, 'file');
assert.strictEqual(file, FILE);
});
gently.expect(form, '_maybeEnd');
cb();
assert.equal(form._flushing, 0);
});
PART.emit('end');
})();
});
test(function _uploadPath() {
(function testUniqueId() {
var UUID_A, UUID_B;
gently.expect(GENTLY.hijacked.path, 'join', function(uploadDir, uuid) {
assert.equal(uploadDir, form.uploadDir);
UUID_A = uuid;
});
form._uploadPath();
gently.expect(GENTLY.hijacked.path, 'join', function(uploadDir, uuid) {
UUID_B = uuid;
});
form._uploadPath();
assert.notEqual(UUID_A, UUID_B);
})();
(function testFileExtension() {
form.keepExtensions = true;
var FILENAME = 'foo.jpg',
EXT = '.bar';
gently.expect(GENTLY.hijacked.path, 'extname', function(filename) {
assert.equal(filename, FILENAME);
gently.restore(path, 'extname');
return EXT;
});
gently.expect(GENTLY.hijacked.path, 'join', function(uploadDir, name) {
assert.equal(path.extname(name), EXT);
});
form._uploadPath(FILENAME);
})();
});
test(function _maybeEnd() {
gently.expect(form, 'emit', 0);
form._maybeEnd();
form.ended = true;
form._flushing = 1;
form._maybeEnd();
gently.expect(form, 'emit', function(event) {
assert.equal(event, 'end');
});
form.ended = true;
form._flushing = 0;
form._maybeEnd();
});
@@ -1,50 +0,0 @@
var common = require('../common');
var multipartParser = require(common.lib + '/multipart_parser'),
MultipartParser = multipartParser.MultipartParser,
events = require('events'),
Buffer = require('buffer').Buffer,
parser;
function test(test) {
parser = new MultipartParser();
test();
}
test(function constructor() {
assert.equal(parser.boundary, null);
assert.equal(parser.state, 0);
assert.equal(parser.flags, 0);
assert.equal(parser.boundaryChars, null);
assert.equal(parser.index, null);
assert.equal(parser.lookbehind, null);
assert.equal(parser.constructor.name, 'MultipartParser');
});
test(function initWithBoundary() {
var boundary = 'abc';
parser.initWithBoundary(boundary);
assert.deepEqual(Array.prototype.slice.call(parser.boundary), [13, 10, 45, 45, 97, 98, 99]);
assert.equal(parser.state, multipartParser.START);
assert.deepEqual(parser.boundaryChars, {10: true, 13: true, 45: true, 97: true, 98: true, 99: true});
});
test(function parserError() {
var boundary = 'abc',
buffer = new Buffer(5);
parser.initWithBoundary(boundary);
buffer.write('--ad', 'ascii', 0);
assert.equal(parser.write(buffer), 5);
});
test(function end() {
(function testError() {
assert.equal(parser.end().message, 'MultipartParser.end(): stream ended unexpectedly: ' + parser.explain());
})();
(function testRegular() {
parser.state = multipartParser.END;
assert.strictEqual(parser.end(), undefined);
})();
});
@@ -1,45 +0,0 @@
var common = require('../common');
var QuerystringParser = require(common.lib + '/querystring_parser').QuerystringParser,
Buffer = require('buffer').Buffer,
gently,
parser;
function test(test) {
gently = new Gently();
parser = new QuerystringParser();
test();
gently.verify(test.name);
}
test(function constructor() {
assert.equal(parser.buffer, '');
assert.equal(parser.constructor.name, 'QuerystringParser');
});
test(function write() {
var a = new Buffer('a=1');
assert.equal(parser.write(a), a.length);
var b = new Buffer('&b=2');
parser.write(b);
assert.equal(parser.buffer, a + b);
});
test(function end() {
var FIELDS = {a: ['b', {c: 'd'}], e: 'f'};
gently.expect(GENTLY.hijacked.querystring, 'parse', function(str) {
assert.equal(str, parser.buffer);
return FIELDS;
});
gently.expect(parser, 'onField', Object.keys(FIELDS).length, function(key, val) {
assert.deepEqual(FIELDS[key], val);
});
gently.expect(parser, 'onEnd');
parser.buffer = 'my buffer';
parser.end();
assert.equal(parser.buffer, '');
});
@@ -1,71 +0,0 @@
var common = require('../common');
var BOUNDARY = '---------------------------10102754414578508781458777923',
FIXTURE = TEST_FIXTURES+'/multi_video.upload',
fs = require('fs'),
http = require('http'),
formidable = require(common.lib + '/index'),
server = http.createServer();
server.on('request', function(req, res) {
var form = new formidable.IncomingForm(),
uploads = {};
form.uploadDir = TEST_TMP;
form.hash = 'sha1';
form.parse(req);
form
.on('fileBegin', function(field, file) {
assert.equal(field, 'upload');
var tracker = {file: file, progress: [], ended: false};
uploads[file.name] = tracker;
file
.on('progress', function(bytesReceived) {
tracker.progress.push(bytesReceived);
assert.equal(bytesReceived, file.size);
})
.on('end', function() {
tracker.ended = true;
});
})
.on('field', function(field, value) {
assert.equal(field, 'title');
assert.equal(value, '');
})
.on('file', function(field, file) {
assert.equal(field, 'upload');
assert.strictEqual(uploads[file.name].file, file);
})
.on('end', function() {
assert.ok(uploads['shortest_video.flv']);
assert.ok(uploads['shortest_video.flv'].ended);
assert.ok(uploads['shortest_video.flv'].progress.length > 3);
assert.equal(uploads['shortest_video.flv'].file.hash, 'd6a17616c7143d1b1438ceeef6836d1a09186b3a');
assert.equal(uploads['shortest_video.flv'].progress.slice(-1), uploads['shortest_video.flv'].file.size);
assert.ok(uploads['shortest_video.mp4']);
assert.ok(uploads['shortest_video.mp4'].ended);
assert.ok(uploads['shortest_video.mp4'].progress.length > 3);
assert.equal(uploads['shortest_video.mp4'].file.hash, '937dfd4db263f4887ceae19341dcc8d63bcd557f');
server.close();
res.writeHead(200);
res.end('good');
});
});
server.listen(TEST_PORT, function() {
var stat, headers, request, fixture;
stat = fs.statSync(FIXTURE);
request = http.request({
port: TEST_PORT,
path: '/',
method: 'POST',
headers: {
'content-type': 'multipart/form-data; boundary='+BOUNDARY,
'content-length': stat.size,
},
});
fs.createReadStream(FIXTURE).pipe(request);
});
@@ -1 +0,0 @@
require('urun')(__dirname)
@@ -1,67 +0,0 @@
<html>
<head>
<title>Convert a file to a base64 request</title>
<script type="text/javascript">
function form_submit(e){
console.log(e)
var resultOutput = document.getElementById('resultOutput');
var fileInput = document.getElementById('fileInput');
var fieldInput = document.getElementById('fieldInput');
makeRequestBase64(fileInput.files[0], fieldInput.value, function(err, result){
resultOutput.value = result;
});
return false;
}
function makeRequestBase64(file, fieldName, cb){
var boundary = '\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/';
var crlf = "\r\n";
var reader = new FileReader();
reader.onload = function(e){
var body = '';
body += '--' + boundary + crlf;
body += 'Content-Disposition: form-data; name="' + fieldName + '"; filename="' + escape(file.name)+ '"' + crlf;
body += 'Content-Type: ' + file.type + '' + crlf;
body += 'Content-Transfer-Encoding: base64' + crlf
body += crlf;
body += e.target.result.substring(e.target.result.indexOf(',') + 1) + crlf;
body += '--' + boundary + '--';
var head = '';
head += 'POST /upload HTTP/1.1' + crlf;
head += 'Host: localhost:8080' + crlf;
head += 'Content-Type: multipart/form-data; boundary=' + boundary + '' + crlf;
head += 'Content-Length: ' + body.length + '' + crlf;
cb(null, head + crlf + body);
};
reader.readAsDataURL(file);
}
</script>
</head>
<body>
<form action="" onsubmit="return form_submit();">
<label>File: <input id="fileInput" type="file" /></label><br />
<label>Field: <input id="fieldInput" type="text" value="file" /></label><br />
<button type="submit">Ok!</button><br />
<label>Request: <textarea id="resultOutput" readonly="readonly" rows="20" cols="80"></textarea></label><br />
</form>
<p>
Don't forget to save the output with windows (CRLF) line endings!
</p>
</body>
</html>
@@ -1,33 +0,0 @@
var common = require('../common');
var test = require('utest');
var assert = common.assert;
var File = common.require('file');
var file;
var now = new Date;
test('IncomingForm', {
before: function() {
file = new File({
size: 1024,
path: '/tmp/cat.png',
name: 'cat.png',
type: 'image/png',
lastModifiedDate: now,
filename: 'cat.png',
mime: 'image/png'
})
},
'#toJSON()': function() {
var obj = file.toJSON();
var len = Object.keys(obj).length;
assert.equal(1024, obj.size);
assert.equal('/tmp/cat.png', obj.path);
assert.equal('cat.png', obj.name);
assert.equal('image/png', obj.type);
assert.equal('image/png', obj.mime);
assert.equal('cat.png', obj.filename);
assert.equal(now, obj.mtime);
assert.equal(len, 8);
}
});
@@ -1,63 +0,0 @@
var common = require('../common');
var test = require('utest');
var assert = common.assert;
var IncomingForm = common.require('incoming_form').IncomingForm;
var path = require('path');
var form;
test('IncomingForm', {
before: function() {
form = new IncomingForm();
},
'#_fileName with regular characters': function() {
var filename = 'foo.txt';
assert.equal(form._fileName(makeHeader(filename)), 'foo.txt');
},
'#_fileName with unescaped quote': function() {
var filename = 'my".txt';
assert.equal(form._fileName(makeHeader(filename)), 'my".txt');
},
'#_fileName with escaped quote': function() {
var filename = 'my%22.txt';
assert.equal(form._fileName(makeHeader(filename)), 'my".txt');
},
'#_fileName with bad quote and additional sub-header': function() {
var filename = 'my".txt';
var header = makeHeader(filename) + '; foo="bar"';
assert.equal(form._fileName(header), filename);
},
'#_fileName with semicolon': function() {
var filename = 'my;.txt';
assert.equal(form._fileName(makeHeader(filename)), 'my;.txt');
},
'#_fileName with utf8 character': function() {
var filename = 'my&#9731;.txt';
assert.equal(form._fileName(makeHeader(filename)), 'my☃.txt');
},
'#_uploadPath strips harmful characters from extension when keepExtensions': function() {
form.keepExtensions = true;
var ext = path.extname(form._uploadPath('fine.jpg?foo=bar'));
assert.equal(ext, '.jpg');
var ext = path.extname(form._uploadPath('fine?foo=bar'));
assert.equal(ext, '');
var ext = path.extname(form._uploadPath('super.cr2+dsad'));
assert.equal(ext, '.cr2');
var ext = path.extname(form._uploadPath('super.bar'));
assert.equal(ext, '.bar');
},
});
function makeHeader(filename) {
return 'Content-Disposition: form-data; name="upload"; filename="' + filename + '"';
}
@@ -0,0 +1,70 @@
{
// Settings
"passfail" : false, // Stop on first error.
"maxerr" : 100, // Maximum errors before stopping.
// Predefined globals whom JSHint will ignore.
"browser" : false, // Standard browser globals e.g. `window`, `document`.
"node" : true,
"rhino" : false,
"couch" : false,
"wsh" : false, // Windows Scripting Host.
"jquery" : false,
"prototypejs" : false,
"mootools" : false,
"dojo" : false,
"predef" : [
"describe", "it", "before", "after"
],
// Development.
"debug" : true, // Allow debugger statements e.g. browser breakpoints.
"devel" : true, // Allow development statements e.g. `console.log();`.
// EcmaScript 5.
"es5" : true, // Allow EcmaScript 5 syntax.
"strict" : false, // Require `use strict` pragma in every file.
"globalstrict" : true, // Allow global "use strict" (also enables 'strict').
// The Good Parts.
"asi" : true, // Tolerate Automatic Semicolon Insertion (no semicolons).
"laxbreak" : false, // Tolerate unsafe line breaks e.g. `return [\n] x` without semicolons.
"laxcomma" : true,
"bitwise" : false, // Prohibit bitwise operators (&, |, ^, etc.).
"boss" : true, // Tolerate assignments inside if, for & while. Usually conditions & loops are for comparison, not assignments.
"curly" : false, // Require {} for every new block or scope.
"eqeqeq" : true, // Require triple equals i.e. `===`.
"eqnull" : true, // Tolerate use of `== null`.
"evil" : false, // Tolerate use of `eval`.
"expr" : false, // Tolerate `ExpressionStatement` as Programs.
"forin" : false, // Prohibt `for in` loops without `hasOwnProperty`.
"immed" : true, // Require immediate invocations to be wrapped in parens e.g. `( function(){}() );`
"latedef" : false, // Prohibit variable use before definition.
"loopfunc" : false, // Allow functions to be defined within loops.
"noarg" : true, // Prohibit use of `arguments.caller` and `arguments.callee`.
"regexp" : false, // Prohibit `.` and `[^...]` in regular expressions.
"regexdash" : false, // Tolerate unescaped last dash i.e. `[-...]`.
"scripturl" : false, // Tolerate script-targeted URLs.
"shadow" : false, // Allows re-define variables later in code e.g. `var x=1; x=2;`.
"supernew" : false, // Tolerate `new function () { ... };` and `new Object;`.
"undef" : true, // Require all non-global variables be declared before they are used.
// Persone styling prefrences.
"newcap" : true, // Require capitalization of all constructor functions e.g. `new F()`.
"noempty" : true, // Prohibit use of empty blocks.
"nonew" : true, // Prohibit use of constructors for side-effects.
"nomen" : false, // Prohibit use of initial or trailing underbars in names.
"onevar" : false, // Allow only one `var` statement per function.
"plusplus" : false, // Prohibit use of `++` & `--`.
"sub" : false, // Tolerate all forms of subscript notation besides dot notation e.g. `dict['key']` instead of `dict.key`.
"trailing" : true, // Prohibit trailing whitespaces.
"white" : false // Check against strict whitespace and indentation rules.
}
@@ -0,0 +1 @@
/node_modules
@@ -1,5 +1,5 @@
language: node_js language: node_js
node_js: node_js:
- 0.8 - "0.8"
- 0.9 - "0.9"
- "0.10" - "0.10"
@@ -0,0 +1,169 @@
### 2.1.6
* expose `hash` as an option to `Form`. (thanks wookiehangover)
### 2.1.5
* fix possible 'close' event before all temp files are done
### 2.1.4
* fix crash for invalid requests
### 2.1.3
* add `file.size`
### 2.1.2
* proper backpressure support
* update s3 example
### 2.1.1
* fix uploads larger than 2KB
* fix both s3 and upload example
* add part.byteCount and part.byteOffset
### 2.1.0 (recalled)
* Complete rewrite. See README for changes and new API.
### v1.0.13
* Only update hash if update method exists (Sven Lito)
* According to travis v0.10 needs to go quoted (Sven Lito)
* Bumping build node versions (Sven Lito)
* Additional fix for empty requests (Eugene Girshov)
* Change the default to 1000, to match the new Node behaviour. (OrangeDog)
* Add ability to control maxKeys in the querystring parser. (OrangeDog)
* Adjust test case to work with node 0.9.x (Eugene Girshov)
* Update package.json (Sven Lito)
* Path adjustment according to eb4468b (Markus Ast)
### v1.0.12
* Emit error on aborted connections (Eugene Girshov)
* Add support for empty requests (Eugene Girshov)
* Fix name/filename handling in Content-Disposition (jesperp)
* Tolerate malformed closing boundary in multipart (Eugene Girshov)
* Ignore preamble in multipart messages (Eugene Girshov)
* Add support for application/json (Mike Frey, Carlos Rodriguez)
* Add support for Base64 encoding (Elmer Bulthuis)
* Add File#toJSON (TJ Holowaychuk)
* Remove support for Node.js 0.4 & 0.6 (Andrew Kelley)
* Documentation improvements (Sven Lito, Andre Azevedo)
* Add support for application/octet-stream (Ion Lupascu, Chris Scribner)
* Use os.tmpDir() to get tmp directory (Andrew Kelley)
* Improve package.json (Andrew Kelley, Sven Lito)
* Fix benchmark script (Andrew Kelley)
* Fix scope issue in incoming_forms (Sven Lito)
* Fix file handle leak on error (OrangeDog)
### v1.0.11
* Calculate checksums for incoming files (sreuter)
* Add definition parameters to "IncomingForm" as an argument (Math-)
### v1.0.10
* Make parts to be proper Streams (Matt Robenolt)
### v1.0.9
* Emit progress when content length header parsed (Tim Koschützki)
* Fix Readme syntax due to GitHub changes (goob)
* Replace references to old 'sys' module in Readme with 'util' (Peter Sugihara)
### v1.0.8
* Strip potentially unsafe characters when using `keepExtensions: true`.
* Switch to utest / urun for testing
* Add travis build
### v1.0.7
* Remove file from package that was causing problems when installing on windows. (#102)
* Fix typos in Readme (Jason Davies).
### v1.0.6
* Do not default to the default to the field name for file uploads where
filename="".
### v1.0.5
* Support filename="" in multipart parts
* Explain unexpected end() errors in parser better
**Note:** Starting with this version, formidable emits 'file' events for empty
file input fields. Previously those were incorrectly emitted as regular file
input fields with value = "".
### v1.0.4
* Detect a good default tmp directory regardless of platform. (#88)
### v1.0.3
* Fix problems with utf8 characters (#84) / semicolons in filenames (#58)
* Small performance improvements
* New test suite and fixture system
### v1.0.2
* Exclude node\_modules folder from git
* Implement new `'aborted'` event
* Fix files in example folder to work with recent node versions
* Make gently a devDependency
[See Commits](https://github.com/felixge/node-formidable/compare/v1.0.1...v1.0.2)
### v1.0.1
* Fix package.json to refer to proper main directory. (#68, Dean Landolt)
[See Commits](https://github.com/felixge/node-formidable/compare/v1.0.0...v1.0.1)
### v1.0.0
* Add support for multipart boundaries that are quoted strings. (Jeff Craig)
This marks the beginning of development on version 2.0 which will include
several architectural improvements.
[See Commits](https://github.com/felixge/node-formidable/compare/v0.9.11...v1.0.0)
### v0.9.11
* Emit `'progress'` event when receiving data, regardless of parsing it. (Tim Koschützki)
* Use [W3C FileAPI Draft](http://dev.w3.org/2006/webapi/FileAPI/) properties for File class
**Important:** The old property names of the File class will be removed in a
future release.
[See Commits](https://github.com/felixge/node-formidable/compare/v0.9.10...v0.9.11)
### Older releases
These releases were done before starting to maintain the above Changelog:
* [v0.9.10](https://github.com/felixge/node-formidable/compare/v0.9.9...v0.9.10)
* [v0.9.9](https://github.com/felixge/node-formidable/compare/v0.9.8...v0.9.9)
* [v0.9.8](https://github.com/felixge/node-formidable/compare/v0.9.7...v0.9.8)
* [v0.9.7](https://github.com/felixge/node-formidable/compare/v0.9.6...v0.9.7)
* [v0.9.6](https://github.com/felixge/node-formidable/compare/v0.9.5...v0.9.6)
* [v0.9.5](https://github.com/felixge/node-formidable/compare/v0.9.4...v0.9.5)
* [v0.9.4](https://github.com/felixge/node-formidable/compare/v0.9.3...v0.9.4)
* [v0.9.3](https://github.com/felixge/node-formidable/compare/v0.9.2...v0.9.3)
* [v0.9.2](https://github.com/felixge/node-formidable/compare/v0.9.1...v0.9.2)
* [v0.9.1](https://github.com/felixge/node-formidable/compare/v0.9.0...v0.9.1)
* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0)
* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0)
* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0)
* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0)
* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0)
* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0)
* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0)
* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0)
* [v0.1.0](https://github.com/felixge/node-formidable/commits/v0.1.0)
@@ -0,0 +1,163 @@
[![Build Status](https://travis-ci.org/superjoe30/node-multiparty.png?branch=master)](https://travis-ci.org/superjoe30/node-multiparty)
# multiparty
Parse http requests with content-type `multipart/form-data`, also known as file uploads.
### Why the fork?
* This module uses the Node.js v0.10 streams properly, *even in Node.js v0.8*
* It will not create a temp file for you unless you want it to.
* Counts bytes and does math to help you figure out the `Content-Length` of
each part.
* You can easily stream uploads to s3 with
[knox](https://github.com/LearnBoost/knox), for [example](examples/s3.js).
* Less bugs. This code is simpler, has all deprecated functionality removed,
has cleaner tests, and does not try to do anything beyond multipart stream
parsing.
## Installation
```
npm install multiparty
```
## Usage
* See [examples](examples).
* Using express or connect? See [connect-multiparty](https://github.com/superjoe30/connect-multiparty)
Parse an incoming `multipart/form-data` request.
```js
var multiparty = require('multiparty')
, http = require('http')
, util = require('util')
http.createServer(function(req, res) {
if (req.url === '/upload' && req.method === 'POST') {
// parse a file upload
var form = new multiparty.Form();
form.parse(req, function(err, fields, files) {
res.writeHead(200, {'content-type': 'text/plain'});
res.write('received upload:\n\n');
res.end(util.inspect({fields: fields, files: files}));
});
return;
}
// show a file upload form
res.writeHead(200, {'content-type': 'text/html'});
res.end(
'<form action="/upload" enctype="multipart/form-data" method="post">'+
'<input type="text" name="title"><br>'+
'<input type="file" name="upload" multiple="multiple"><br>'+
'<input type="submit" value="Upload">'+
'</form>'
);
}).listen(8080);
```
## API
### multiparty.Form
```js
var form = new multiparty.Form(options)
```
Creates a new form. Options:
* `encoding` - sets encoding for the incoming form fields. Defaults to `utf8`.
* `maxFieldSize` - Limits the amount of memory a field (not a file) can
allocate in bytes. If this value is exceeded, an `error` event is emitted.
The default size is 2MB.
* `maxFields` - Limits the number of fields that will be parsed before
emitting an `error` event. A file counts as a field in this case.
Defaults to 1000.
* `autoFields` - Enables `field` events. This is automatically set to `true`
if you add a `field` listener.
* `autoFiles` - Enables `file` events. This is automatically set to `true`
if you add a `file` listener.
* `uploadDir` - Only relevant when `autoFiles` is `true`. The directory for
placing file uploads in. You can move them later using `fs.rename()`.
Defaults to `os.tmpDir()`.
* `hash` - Only relevant when `autoFiles` is `true`. If you want checksums
calculated for incoming files, set this to either `sha1` or `md5`.
Defaults to off.
#### form.parse(request, [cb])
Parses an incoming node.js `request` containing form data. If `cb` is
provided, `autoFields` and `autoFiles` are set to `true` and all fields and
files are collected and passed to the callback:
```js
form.parse(req, function(err, fields, files) {
// ...
});
```
#### form.bytesReceived
The amount of bytes received for this form so far.
#### form.bytesExpected
The expected number of bytes in this form.
### Events
#### 'error' (err)
You definitely want to handle this event. If not your server *will* crash when
users submit bogus multipart requests!
#### 'part' (part)
Emitted when a part is encountered in the request. `part` is a
`ReadableStream`. It also has the following properties:
* `headers` - the headers for this part. For example, you may be interested
in `content-type`.
* `name` - the field name for this part
* `filename` - only if the part is an incoming file
* `byteOffset` - the byte offset of this part in the request body
* `byteCount` - assuming that this is the last part in the request,
this is the size of this part in bytes. You could use this, for
example, to set the `Content-Length` header if uploading to S3.
If the part had a `Content-Length` header then that value is used
here instead.
#### 'aborted'
Emitted when the request is aborted. This event will be followed shortly
by an `error` event. In practice you do not need to handle this event.
#### 'progress' (bytesReceived, bytesExpected)
#### 'close'
Emitted after all parts have been parsed and emitted. Not emitted if an `error`
event is emitted. This is typically when you would send your response.
#### 'file' (name, file)
**By default multiparty will not touch your hard drive.** But if you add this
listener, multiparty automatically sets `form.autoFiles` to `true` and will
stream uploads to disk for you.
* `name` - the field name for this file
* `file` - an object with these properties:
- `originalFilename` - the filename that the user reports for the file
- `path` - the absolute path of the uploaded file on disk
- `headers` - the HTTP headers that were sent along with this file
- `size` - size of the file in bytes
If you set the `form.hash` option, then `file` will also contain a `hash`
property which is the checksum of the file.
#### 'field' (name, value)
* `name` - field name
* `value` - string field value
@@ -0,0 +1,41 @@
var http = require('http')
, util = require('util')
, multiparty = require('../')
, azure = require('azure')
, PORT = process.env.PORT || 27372
var server = http.createServer(function(req, res) {
if (req.url === '/') {
res.writeHead(200, {'content-type': 'text/html'});
res.end(
'<form action="/upload" enctype="multipart/form-data" method="post">'+
'<input type="text" name="title"><br>'+
'<input type="file" name="upload"><br>'+
'<input type="submit" value="Upload">'+
'</form>'
);
} else if (req.url === '/upload') {
var blobService = azure.createBlobService();
var form = new multiparty.Form();
form.on('part', function(part) {
if (!part.filename) return;
var size = part.byteCount - part.byteOffset;
var name = part.filename;
var container = 'blobContainerName';
blobService.createBlockBlobFromStream(container, name, part, size, function(error) {
if (error) {
// error handling
}
});
});
form.parse(req);
res.send('File uploaded successfully');
});
server.listen(PORT, function() {
console.info('listening on http://0.0.0.0:'+PORT+'/');
});
@@ -0,0 +1,74 @@
var http = require('http')
, util = require('util')
, multiparty = require('../')
, knox = require('knox')
, Batch = require('batch')
, PORT = process.env.PORT || 27372
var s3Client = knox.createClient({
secure: false,
key: process.env.S3_KEY,
secret: process.env.S3_SECRET,
bucket: process.env.S3_BUCKET,
});
var server = http.createServer(function(req, res) {
if (req.url === '/') {
res.writeHead(200, {'content-type': 'text/html'});
res.end(
'<form action="/upload" enctype="multipart/form-data" method="post">'+
'<input type="text" name="path"><br>'+
'<input type="file" name="upload"><br>'+
'<input type="submit" value="Upload">'+
'</form>'
);
} else if (req.url === '/upload') {
var headers = {
'x-amz-acl': 'public-read',
};
var form = new multiparty.Form();
var batch = new Batch();
batch.push(function(cb) {
form.on('field', function(name, value) {
if (name === 'path') {
var destPath = value;
if (destPath[0] !== '/') destPath = '/' + destPath;
cb(null, destPath);
}
});
});
batch.push(function(cb) {
form.on('part', function(part) {
if (! part.filename) return;
cb(null, part);
});
});
batch.end(function(err, results) {
if (err) throw err;
form.removeListener('close', onEnd);
var destPath = results[0]
, part = results[1];
headers['Content-Length'] = part.byteCount;
s3Client.putStream(part, destPath, headers, function(err, s3Response) {
if (err) throw err;
res.statusCode = s3Response.statusCode;
s3Response.pipe(res);
console.log("https://s3.amazonaws.com/" + process.env.S3_BUCKET + destPath);
});
});
form.on('close', onEnd);
form.parse(req);
} else {
res.writeHead(404, {'content-type': 'text/plain'});
res.end('404');
}
function onEnd() {
throw new Error("no uploaded file");
}
});
server.listen(PORT, function() {
console.info('listening on http://0.0.0.0:'+PORT+'/');
});
@@ -0,0 +1,37 @@
var http = require('http')
, util = require('util')
, multiparty = require('../')
, PORT = process.env.PORT || 27372
var server = http.createServer(function(req, res) {
if (req.url === '/') {
res.writeHead(200, {'content-type': 'text/html'});
res.end(
'<form action="/upload" enctype="multipart/form-data" method="post">'+
'<input type="text" name="title"><br>'+
'<input type="file" name="upload" multiple="multiple"><br>'+
'<input type="submit" value="Upload">'+
'</form>'
);
} else if (req.url === '/upload') {
var form = new multiparty.Form();
form.parse(req, function(err, fields, files) {
if (err) {
res.writeHead(400, {'content-type': 'text/plain'});
res.end("invalid request: " + err.message);
return;
}
res.writeHead(200, {'content-type': 'text/plain'});
res.write('received fields:\n\n '+util.inspect(fields));
res.write('\n\n');
res.end('received files:\n\n '+util.inspect(files));
});
} else {
res.writeHead(404, {'content-type': 'text/plain'});
res.end('404');
}
});
server.listen(PORT, function() {
console.info('listening on http://0.0.0.0:'+PORT+'/');
});
+596
Ver Arquivo
@@ -0,0 +1,596 @@
exports.Form = Form;
var stream = require('readable-stream')
, util = require('util')
, fs = require('fs')
, crypto = require('crypto')
, path = require('path')
, os = require('os')
, assert = require('assert')
, StringDecoder = require('string_decoder').StringDecoder
, StreamCounter = require('stream-counter')
var START = 0
, START_BOUNDARY = 1
, HEADER_FIELD_START = 2
, HEADER_FIELD = 3
, HEADER_VALUE_START = 4
, HEADER_VALUE = 5
, HEADER_VALUE_ALMOST_DONE = 6
, HEADERS_ALMOST_DONE = 7
, PART_DATA_START = 8
, PART_DATA = 9
, PART_END = 10
, END = 11
, LF = 10
, CR = 13
, SPACE = 32
, HYPHEN = 45
, COLON = 58
, A = 97
, Z = 122
var CONTENT_TYPE_RE = /^multipart\/(form-data|related);\s+boundary=(?:"([^"]+)"|([^;]+))$/i;
var FILE_EXT_RE = /(\.[_\-a-zA-Z0-9]{0,16}).*/;
var LAST_BOUNDARY_SUFFIX_LEN = 4; // --\r\n
util.inherits(Form, stream.Writable);
function Form(options) {
var self = this;
stream.Writable.call(self);
options = options || {};
self.error = null;
self.finished = false;
self.autoFields = !!options.autoFields;
self.autoFiles = !!options.autoFields;
self.maxFields = options.maxFields || 1000;
self.maxFieldsSize = options.maxFieldsSize || 2 * 1024 * 1024;
self.uploadDir = options.uploadDir || os.tmpDir();
self.encoding = options.encoding || 'utf8';
self.hash = options.hash || false;
self.bytesReceived = 0;
self.bytesExpected = null;
self.openedFiles = [];
self.totalFieldSize = 0;
self.totalFieldCount = 0;
self.flushing = 0;
self.backpressure = false;
self.writeCbs = [];
if (options.boundary) setUpParser(self, options.boundary);
self.on('newListener', function(eventName) {
if (eventName === 'file') {
self.autoFiles = true;
} else if (eventName === 'field') {
self.autoFields = true;
}
});
}
Form.prototype.parse = function(req, cb) {
var self = this;
// if the user supplies a callback, this implies autoFields and autoFiles
if (cb) {
self.autoFields = true;
self.autoFiles = true;
}
req.on('error', function(err) {
error(self, err);
});
req.on('aborted', function() {
self.emit('aborted');
error(self, new Error("Request aborted"));
});
self.bytesExpected = getBytesExpected(req.headers);
var contentType = req.headers['content-type'];
if (!contentType) {
error(self, new Error('missing content-type header'));
return;
}
var m = contentType.match(CONTENT_TYPE_RE);
if (!m) {
error(self, new Error('unrecognized content-type: ' + contentType));
return;
}
var boundary = m[2] || m[3];
setUpParser(self, boundary);
req.pipe(self);
if (cb) {
var fields = {}
, files = {};
self.on('error', function(err) {
cb(err);
});
self.on('field', function(name, value) {
fields[name] = value;
});
self.on('file', function(name, file) {
files[name] = file;
});
self.on('close', function() {
cb(null, fields, files);
});
}
};
Form.prototype._write = function(buffer, encoding, cb) {
var self = this
, i = 0
, len = buffer.length
, prevIndex = self.index
, index = self.index
, state = self.state
, lookbehind = self.lookbehind
, boundary = self.boundary
, boundaryChars = self.boundaryChars
, boundaryLength = self.boundary.length
, boundaryEnd = boundaryLength - 1
, bufferLength = buffer.length
, c
, cl
for (i = 0; i < len; i++) {
c = buffer[i];
switch (state) {
case START:
index = 0;
state = START_BOUNDARY;
/* falls through */
case START_BOUNDARY:
if (index === boundaryLength - 2) {
if (c !== CR) return error(self, new Error("Expected CR Received " + c));
index++;
break;
} else if (index === boundaryLength - 1) {
if (c !== LF) return error(self, new Error("Expected LF Received " + c));
index = 0;
self.onParsePartBegin();
state = HEADER_FIELD_START;
break;
}
if (c !== boundary[index+2]) index = -2;
if (c === boundary[index+2]) index++;
break;
case HEADER_FIELD_START:
state = HEADER_FIELD;
self.headerFieldMark = i;
index = 0;
/* falls through */
case HEADER_FIELD:
if (c === CR) {
self.headerFieldMark = null;
state = HEADERS_ALMOST_DONE;
break;
}
index++;
if (c === HYPHEN) break;
if (c === COLON) {
if (index === 1) {
// empty header field
error(self, new Error("Empty header field"));
return;
}
self.onParseHeaderField(buffer.slice(self.headerFieldMark, i));
self.headerFieldMark = null;
state = HEADER_VALUE_START;
break;
}
cl = lower(c);
if (cl < A || cl > Z) {
error(self, new Error("Expected alphabetic character, received " + c));
return;
}
break;
case HEADER_VALUE_START:
if (c === SPACE) break;
self.headerValueMark = i;
state = HEADER_VALUE;
/* falls through */
case HEADER_VALUE:
if (c === CR) {
self.onParseHeaderValue(buffer.slice(self.headerValueMark, i));
self.headerValueMark = null;
self.onParseHeaderEnd();
state = HEADER_VALUE_ALMOST_DONE;
}
break;
case HEADER_VALUE_ALMOST_DONE:
if (c !== LF) return error(self, new Error("Expected LF Received " + c));
state = HEADER_FIELD_START;
break;
case HEADERS_ALMOST_DONE:
if (c !== LF) return error(self, new Error("Expected LF Received " + c));
var err = self.onParseHeadersEnd(i + 1);
if (err) return error(self, err);
state = PART_DATA_START;
break;
case PART_DATA_START:
state = PART_DATA;
self.partDataMark = i;
/* falls through */
case PART_DATA:
prevIndex = index;
if (index === 0) {
// boyer-moore derrived algorithm to safely skip non-boundary data
i += boundaryEnd;
while (i < bufferLength && !(buffer[i] in boundaryChars)) {
i += boundaryLength;
}
i -= boundaryEnd;
c = buffer[i];
}
if (index < boundaryLength) {
if (boundary[index] === c) {
if (index === 0) {
self.onParsePartData(buffer.slice(self.partDataMark, i));
self.partDataMark = null;
}
index++;
} else {
index = 0;
}
} else if (index === boundaryLength) {
index++;
if (c === CR) {
// CR = part boundary
self.partBoundaryFlag = true;
} else if (c === HYPHEN) {
// HYPHEN = end boundary
self.lastBoundaryFlag = true;
} else {
index = 0;
}
} else if (index - 1 === boundaryLength) {
if (self.partBoundaryFlag) {
index = 0;
if (c === LF) {
self.partBoundaryFlag = false;
self.onParsePartEnd();
self.onParsePartBegin();
state = HEADER_FIELD_START;
break;
}
} else if (self.lastBoundaryFlag) {
if (c === HYPHEN) {
self.onParsePartEnd();
self.end();
state = END;
} else {
index = 0;
}
} else {
index = 0;
}
}
if (index > 0) {
// when matching a possible boundary, keep a lookbehind reference
// in case it turns out to be a false lead
lookbehind[index-1] = c;
} else if (prevIndex > 0) {
// if our boundary turned out to be rubbish, the captured lookbehind
// belongs to partData
self.onParsePartData(lookbehind.slice(0, prevIndex));
prevIndex = 0;
self.partDataMark = i;
// reconsider the current character even so it interrupted the sequence
// it could be the beginning of a new sequence
i--;
}
break;
case END:
break;
default:
error(self, new Error("Parser has invalid state."));
return;
}
}
if (self.headerFieldMark != null) {
self.onParseHeaderField(buffer.slice(self.headerFieldMark));
self.headerFieldMark = 0;
}
if (self.headerValueMark != null) {
self.onParseHeaderValue(buffer.slice(self.headerValueMark));
self.headerValueMark = 0;
}
if (self.partDataMark != null) {
self.onParsePartData(buffer.slice(self.partDataMark));
self.partDataMark = 0;
}
self.index = index;
self.state = state;
self.bytesReceived += buffer.length;
self.emit('progress', self.bytesReceived, self.bytesExpected);
if (self.backpressure) {
self.writeCbs.push(cb);
} else {
cb();
}
};
Form.prototype.onParsePartBegin = function() {
clearPartVars(this);
}
Form.prototype.onParseHeaderField = function(b) {
this.headerField += this.headerFieldDecoder.write(b);
}
Form.prototype.onParseHeaderValue = function(b) {
this.headerValue += this.headerValueDecoder.write(b);
}
Form.prototype.onParseHeaderEnd = function() {
this.headerField = this.headerField.toLowerCase();
this.partHeaders[this.headerField] = this.headerValue;
var m;
if (this.headerField === 'content-disposition') {
if (m = this.headerValue.match(/\bname="([^"]+)"/i)) {
this.partName = m[1];
}
this.partFilename = parseFilename(this.headerValue);
} else if (this.headerField === 'content-transfer-encoding') {
this.partTransferEncoding = this.headerValue.toLowerCase();
}
this.headerFieldDecoder = new StringDecoder(this.encoding);
this.headerField = '';
this.headerValueDecoder = new StringDecoder(this.encoding);
this.headerValue = '';
}
Form.prototype.onParsePartData = function(b) {
if (this.partTransferEncoding === 'base64') {
this.backpressure = ! this.destStream.write(b.toString('ascii'), 'base64');
} else {
this.backpressure = ! this.destStream.write(b);
}
}
Form.prototype.onParsePartEnd = function() {
if (this.destStream) {
flushWriteCbs(this);
var s = this.destStream;
process.nextTick(function() {
s.end();
});
}
clearPartVars(this);
}
Form.prototype.onParseHeadersEnd = function(offset) {
var self = this;
switch(self.partTransferEncoding){
case 'binary':
case '7bit':
case '8bit':
self.partTransferEncoding = 'binary';
break;
case 'base64': break;
default:
return new Error("unknown transfer-encoding: " + self.partTransferEncoding);
}
self.destStream = new stream.PassThrough();
self.destStream.on('drain', function() {
flushWriteCbs(self);
});
self.destStream.headers = self.partHeaders;
self.destStream.name = self.partName;
self.destStream.filename = self.partFilename;
self.destStream.byteOffset = self.bytesReceived + offset;
var partContentLength = self.destStream.headers['content-length'];
self.destStream.byteCount = partContentLength ?
parseInt(partContentLength, 10) :
(self.bytesExpected - self.destStream.byteOffset -
self.boundary.length - LAST_BOUNDARY_SUFFIX_LEN);
self.totalFieldCount += 1;
if (self.totalFieldCount >= self.maxFields) {
error(self, new Error("maxFields " + self.maxFields + " exceeded."));
return;
}
self.emit('part', self.destStream);
if (self.destStream.filename == null && self.autoFields) {
handleField(self, self.destStream);
} else if (self.destStream.filename != null && self.autoFiles) {
handleFile(self, self.destStream);
}
}
function flushWriteCbs(self) {
self.writeCbs.forEach(function(cb) {
process.nextTick(cb);
});
self.writeCbs = [];
self.backpressure = false;
}
function getBytesExpected(headers) {
var contentLength = headers['content-length'];
if (contentLength) {
return parseInt(contentLength, 10);
} else if (headers['transfer-encoding'] == null) {
return 0;
} else {
return null;
}
}
function error(self, err) {
assert.ok(!self.error, err.stack);
self.error = err;
self.emit('error', err);
self.openedFiles.forEach(function(file) {
file.ws.destroy();
fs.unlink(file.path, function(err) {
// this is already an error condition, ignore 2nd error
});
});
}
function beginFlush(self) {
self.flushing += 1;
}
function endFlush(self) {
self.flushing -= 1;
maybeClose(self);
}
function maybeClose(self) {
if (!self.flushing && self.finished && !self.error) {
self.emit('close');
}
}
function handleFile(self, fileStream) {
beginFlush(self);
var file = {
originalFilename: fileStream.filename,
path: uploadPath(self.uploadDir, fileStream.filename),
headers: fileStream.headers,
};
file.ws = fs.createWriteStream(file.path);
self.openedFiles.push(file);
fileStream.pipe(file.ws);
var counter = new StreamCounter();
fileStream.pipe(counter);
var hashWorkaroundStream
, hash = null;
if (self.hash) {
// workaround stream because https://github.com/joyent/node/issues/5216
hashWorkaroundStream = stream.Writable();
hash = crypto.createHash(self.hash);
hashWorkaroundStream._write = function(buffer, encoding, callback) {
hash.update(buffer);
callback();
};
fileStream.pipe(hashWorkaroundStream);
}
file.ws.on('error', function(err) {
error(self, err);
});
file.ws.on('close', function() {
if (hash) file.hash = hash.digest('hex');
file.size = counter.bytes;
self.emit('file', fileStream.name, file);
endFlush(self);
});
}
function handleField(self, fieldStream) {
var value = ''
, decoder = new StringDecoder(self.encoding);
beginFlush(self);
fieldStream.on('readable', function() {
var buffer = fieldStream.read();
self.totalFieldSize += buffer.length;
if (self.totalFieldSize > self.maxFieldsSize) {
error(self, new Error("maxFieldsSize " + self.maxFieldsSize + " exceeded"));
return;
}
value += decoder.write(buffer);
});
fieldStream.on('end', function() {
self.emit('field', fieldStream.name, value);
endFlush(self);
});
}
function clearPartVars(self) {
self.partHeaders = {};
self.partName = null;
self.partFilename = null;
self.partTransferEncoding = 'binary';
self.destStream = null;
self.headerFieldDecoder = new StringDecoder(self.encoding);
self.headerField = "";
self.headerValueDecoder = new StringDecoder(self.encoding);
self.headerValue = "";
}
function setUpParser(self, boundary) {
self.boundary = new Buffer(boundary.length + 4);
self.boundary.write('\r\n--', 0, boundary.length + 4, 'ascii');
self.boundary.write(boundary, 4, boundary.length, 'ascii');
self.lookbehind = new Buffer(self.boundary.length + 8);
self.state = START;
self.boundaryChars = {};
for (var i = 0; i < self.boundary.length; i++) {
self.boundaryChars[self.boundary[i]] = true;
}
self.index = null;
self.partBoundaryFlag = false;
self.lastBoundaryFlag = false;
self.on('finish', function() {
if ((self.state === HEADER_FIELD_START && self.index === 0) ||
(self.state === PART_DATA && self.index === self.boundary.length))
{
self.onParsePartEnd();
} else if (self.state !== END) {
error(self, new Error('stream ended unexpectedly'));
}
self.finished = true;
maybeClose(self);
});
}
function uploadPath(baseDir, filename) {
var ext = path.extname(filename).replace(FILE_EXT_RE, '$1');
var name = process.pid + '-' +
(Math.random() * 0x100000000 + 1).toString(36) + ext;
return path.join(baseDir, name);
}
function parseFilename(headerValue) {
var m = headerValue.match(/\bfilename="(.*?)"($|; )/i);
if (!m) return;
var filename = m[1].substr(m[1].lastIndexOf('\\') + 1);
filename = filename.replace(/%22/g, '"');
filename = filename.replace(/&#([\d]{4});/g, function(m, code) {
return String.fromCharCode(code);
});
return filename;
}
function lower(c) {
return c | 0x20;
}
@@ -0,0 +1,27 @@
Copyright (c) Isaac Z. Schlueter ("Author")
All rights reserved.
The BSD License
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,768 @@
# readable-stream
A new class of streams for Node.js
This module provides the new Stream base classes introduced in Node
v0.10, for use in Node v0.8. You can use it to have programs that
have to work with node v0.8, while being forward-compatible for v0.10
and beyond. When you drop support for v0.8, you can remove this
module, and only use the native streams.
This is almost exactly the same codebase as appears in Node v0.10.
However:
1. The exported object is actually the Readable class. Decorating the
native `stream` module would be global pollution.
2. In v0.10, you can safely use `base64` as an argument to
`setEncoding` in Readable streams. However, in v0.8, the
StringDecoder class has no `end()` method, which is problematic for
Base64. So, don't use that, because it'll break and be weird.
Other than that, the API is the same as `require('stream')` in v0.10,
so the API docs are reproduced below.
----------
Stability: 2 - Unstable
A stream is an abstract interface implemented by various objects in
Node. For example a request to an HTTP server is a stream, as is
stdout. Streams are readable, writable, or both. All streams are
instances of [EventEmitter][]
You can load the Stream base classes by doing `require('stream')`.
There are base classes provided for Readable streams, Writable
streams, Duplex streams, and Transform streams.
## Compatibility
In earlier versions of Node, the Readable stream interface was
simpler, but also less powerful and less useful.
* Rather than waiting for you to call the `read()` method, `'data'`
events would start emitting immediately. If you needed to do some
I/O to decide how to handle data, then you had to store the chunks
in some kind of buffer so that they would not be lost.
* The `pause()` method was advisory, rather than guaranteed. This
meant that you still had to be prepared to receive `'data'` events
even when the stream was in a paused state.
In Node v0.10, the Readable class described below was added. For
backwards compatibility with older Node programs, Readable streams
switch into "old mode" when a `'data'` event handler is added, or when
the `pause()` or `resume()` methods are called. The effect is that,
even if you are not using the new `read()` method and `'readable'`
event, you no longer have to worry about losing `'data'` chunks.
Most programs will continue to function normally. However, this
introduces an edge case in the following conditions:
* No `'data'` event handler is added.
* The `pause()` and `resume()` methods are never called.
For example, consider the following code:
```javascript
// WARNING! BROKEN!
net.createServer(function(socket) {
// we add an 'end' method, but never consume the data
socket.on('end', function() {
// It will never get here.
socket.end('I got your message (but didnt read it)\n');
});
}).listen(1337);
```
In versions of node prior to v0.10, the incoming message data would be
simply discarded. However, in Node v0.10 and beyond, the socket will
remain paused forever.
The workaround in this situation is to call the `resume()` method to
trigger "old mode" behavior:
```javascript
// Workaround
net.createServer(function(socket) {
socket.on('end', function() {
socket.end('I got your message (but didnt read it)\n');
});
// start the flow of data, discarding it.
socket.resume();
}).listen(1337);
```
In addition to new Readable streams switching into old-mode, pre-v0.10
style streams can be wrapped in a Readable class using the `wrap()`
method.
## Class: stream.Readable
<!--type=class-->
A `Readable Stream` has the following methods, members, and events.
Note that `stream.Readable` is an abstract class designed to be
extended with an underlying implementation of the `_read(size)`
method. (See below.)
### new stream.Readable([options])
* `options` {Object}
* `highWaterMark` {Number} The maximum number of bytes to store in
the internal buffer before ceasing to read from the underlying
resource. Default=16kb
* `encoding` {String} If specified, then buffers will be decoded to
strings using the specified encoding. Default=null
* `objectMode` {Boolean} Whether this stream should behave
as a stream of objects. Meaning that stream.read(n) returns
a single value instead of a Buffer of size n
In classes that extend the Readable class, make sure to call the
constructor so that the buffering settings can be properly
initialized.
### readable.\_read(size)
* `size` {Number} Number of bytes to read asynchronously
Note: **This function should NOT be called directly.** It should be
implemented by child classes, and called by the internal Readable
class methods only.
All Readable stream implementations must provide a `_read` method
to fetch data from the underlying resource.
This method is prefixed with an underscore because it is internal to
the class that defines it, and should not be called directly by user
programs. However, you **are** expected to override this method in
your own extension classes.
When data is available, put it into the read queue by calling
`readable.push(chunk)`. If `push` returns false, then you should stop
reading. When `_read` is called again, you should start pushing more
data.
The `size` argument is advisory. Implementations where a "read" is a
single call that returns data can use this to know how much data to
fetch. Implementations where that is not relevant, such as TCP or
TLS, may ignore this argument, and simply provide data whenever it
becomes available. There is no need, for example to "wait" until
`size` bytes are available before calling `stream.push(chunk)`.
### readable.push(chunk)
* `chunk` {Buffer | null | String} Chunk of data to push into the read queue
* return {Boolean} Whether or not more pushes should be performed
Note: **This function should be called by Readable implementors, NOT
by consumers of Readable subclasses.** The `_read()` function will not
be called again until at least one `push(chunk)` call is made. If no
data is available, then you MAY call `push('')` (an empty string) to
allow a future `_read` call, without adding any data to the queue.
The `Readable` class works by putting data into a read queue to be
pulled out later by calling the `read()` method when the `'readable'`
event fires.
The `push()` method will explicitly insert some data into the read
queue. If it is called with `null` then it will signal the end of the
data.
In some cases, you may be wrapping a lower-level source which has some
sort of pause/resume mechanism, and a data callback. In those cases,
you could wrap the low-level source object by doing something like
this:
```javascript
// source is an object with readStop() and readStart() methods,
// and an `ondata` member that gets called when it has data, and
// an `onend` member that gets called when the data is over.
var stream = new Readable();
source.ondata = function(chunk) {
// if push() returns false, then we need to stop reading from source
if (!stream.push(chunk))
source.readStop();
};
source.onend = function() {
stream.push(null);
};
// _read will be called when the stream wants to pull more data in
// the advisory size argument is ignored in this case.
stream._read = function(n) {
source.readStart();
};
```
### readable.unshift(chunk)
* `chunk` {Buffer | null | String} Chunk of data to unshift onto the read queue
* return {Boolean} Whether or not more pushes should be performed
This is the corollary of `readable.push(chunk)`. Rather than putting
the data at the *end* of the read queue, it puts it at the *front* of
the read queue.
This is useful in certain use-cases where a stream is being consumed
by a parser, which needs to "un-consume" some data that it has
optimistically pulled out of the source.
```javascript
// A parser for a simple data protocol.
// The "header" is a JSON object, followed by 2 \n characters, and
// then a message body.
//
// Note: This can be done more simply as a Transform stream. See below.
function SimpleProtocol(source, options) {
if (!(this instanceof SimpleProtocol))
return new SimpleProtocol(options);
Readable.call(this, options);
this._inBody = false;
this._sawFirstCr = false;
// source is a readable stream, such as a socket or file
this._source = source;
var self = this;
source.on('end', function() {
self.push(null);
});
// give it a kick whenever the source is readable
// read(0) will not consume any bytes
source.on('readable', function() {
self.read(0);
});
this._rawHeader = [];
this.header = null;
}
SimpleProtocol.prototype = Object.create(
Readable.prototype, { constructor: { value: SimpleProtocol }});
SimpleProtocol.prototype._read = function(n) {
if (!this._inBody) {
var chunk = this._source.read();
// if the source doesn't have data, we don't have data yet.
if (chunk === null)
return this.push('');
// check if the chunk has a \n\n
var split = -1;
for (var i = 0; i < chunk.length; i++) {
if (chunk[i] === 10) { // '\n'
if (this._sawFirstCr) {
split = i;
break;
} else {
this._sawFirstCr = true;
}
} else {
this._sawFirstCr = false;
}
}
if (split === -1) {
// still waiting for the \n\n
// stash the chunk, and try again.
this._rawHeader.push(chunk);
this.push('');
} else {
this._inBody = true;
var h = chunk.slice(0, split);
this._rawHeader.push(h);
var header = Buffer.concat(this._rawHeader).toString();
try {
this.header = JSON.parse(header);
} catch (er) {
this.emit('error', new Error('invalid simple protocol data'));
return;
}
// now, because we got some extra data, unshift the rest
// back into the read queue so that our consumer will see it.
var b = chunk.slice(split);
this.unshift(b);
// and let them know that we are done parsing the header.
this.emit('header', this.header);
}
} else {
// from there on, just provide the data to our consumer.
// careful not to push(null), since that would indicate EOF.
var chunk = this._source.read();
if (chunk) this.push(chunk);
}
};
// Usage:
var parser = new SimpleProtocol(source);
// Now parser is a readable stream that will emit 'header'
// with the parsed header data.
```
### readable.wrap(stream)
* `stream` {Stream} An "old style" readable stream
If you are using an older Node library that emits `'data'` events and
has a `pause()` method that is advisory only, then you can use the
`wrap()` method to create a Readable stream that uses the old stream
as its data source.
For example:
```javascript
var OldReader = require('./old-api-module.js').OldReader;
var oreader = new OldReader;
var Readable = require('stream').Readable;
var myReader = new Readable().wrap(oreader);
myReader.on('readable', function() {
myReader.read(); // etc.
});
```
### Event: 'readable'
When there is data ready to be consumed, this event will fire.
When this event emits, call the `read()` method to consume the data.
### Event: 'end'
Emitted when the stream has received an EOF (FIN in TCP terminology).
Indicates that no more `'data'` events will happen. If the stream is
also writable, it may be possible to continue writing.
### Event: 'data'
The `'data'` event emits either a `Buffer` (by default) or a string if
`setEncoding()` was used.
Note that adding a `'data'` event listener will switch the Readable
stream into "old mode", where data is emitted as soon as it is
available, rather than waiting for you to call `read()` to consume it.
### Event: 'error'
Emitted if there was an error receiving data.
### Event: 'close'
Emitted when the underlying resource (for example, the backing file
descriptor) has been closed. Not all streams will emit this.
### readable.setEncoding(encoding)
Makes the `'data'` event emit a string instead of a `Buffer`. `encoding`
can be `'utf8'`, `'utf16le'` (`'ucs2'`), `'ascii'`, or `'hex'`.
The encoding can also be set by specifying an `encoding` field to the
constructor.
### readable.read([size])
* `size` {Number | null} Optional number of bytes to read.
* Return: {Buffer | String | null}
Note: **This function SHOULD be called by Readable stream users.**
Call this method to consume data once the `'readable'` event is
emitted.
The `size` argument will set a minimum number of bytes that you are
interested in. If not set, then the entire content of the internal
buffer is returned.
If there is no data to consume, or if there are fewer bytes in the
internal buffer than the `size` argument, then `null` is returned, and
a future `'readable'` event will be emitted when more is available.
Calling `stream.read(0)` will always return `null`, and will trigger a
refresh of the internal buffer, but otherwise be a no-op.
### readable.pipe(destination, [options])
* `destination` {Writable Stream}
* `options` {Object} Optional
* `end` {Boolean} Default=true
Connects this readable stream to `destination` WriteStream. Incoming
data on this stream gets written to `destination`. Properly manages
back-pressure so that a slow destination will not be overwhelmed by a
fast readable stream.
This function returns the `destination` stream.
For example, emulating the Unix `cat` command:
process.stdin.pipe(process.stdout);
By default `end()` is called on the destination when the source stream
emits `end`, so that `destination` is no longer writable. Pass `{ end:
false }` as `options` to keep the destination stream open.
This keeps `writer` open so that "Goodbye" can be written at the
end.
reader.pipe(writer, { end: false });
reader.on("end", function() {
writer.end("Goodbye\n");
});
Note that `process.stderr` and `process.stdout` are never closed until
the process exits, regardless of the specified options.
### readable.unpipe([destination])
* `destination` {Writable Stream} Optional
Undo a previously established `pipe()`. If no destination is
provided, then all previously established pipes are removed.
### readable.pause()
Switches the readable stream into "old mode", where data is emitted
using a `'data'` event rather than being buffered for consumption via
the `read()` method.
Ceases the flow of data. No `'data'` events are emitted while the
stream is in a paused state.
### readable.resume()
Switches the readable stream into "old mode", where data is emitted
using a `'data'` event rather than being buffered for consumption via
the `read()` method.
Resumes the incoming `'data'` events after a `pause()`.
## Class: stream.Writable
<!--type=class-->
A `Writable` Stream has the following methods, members, and events.
Note that `stream.Writable` is an abstract class designed to be
extended with an underlying implementation of the
`_write(chunk, encoding, cb)` method. (See below.)
### new stream.Writable([options])
* `options` {Object}
* `highWaterMark` {Number} Buffer level when `write()` starts
returning false. Default=16kb
* `decodeStrings` {Boolean} Whether or not to decode strings into
Buffers before passing them to `_write()`. Default=true
In classes that extend the Writable class, make sure to call the
constructor so that the buffering settings can be properly
initialized.
### writable.\_write(chunk, encoding, callback)
* `chunk` {Buffer | String} The chunk to be written. Will always
be a buffer unless the `decodeStrings` option was set to `false`.
* `encoding` {String} If the chunk is a string, then this is the
encoding type. Ignore chunk is a buffer. Note that chunk will
**always** be a buffer unless the `decodeStrings` option is
explicitly set to `false`.
* `callback` {Function} Call this function (optionally with an error
argument) when you are done processing the supplied chunk.
All Writable stream implementations must provide a `_write` method to
send data to the underlying resource.
Note: **This function MUST NOT be called directly.** It should be
implemented by child classes, and called by the internal Writable
class methods only.
Call the callback using the standard `callback(error)` pattern to
signal that the write completed successfully or with an error.
If the `decodeStrings` flag is set in the constructor options, then
`chunk` may be a string rather than a Buffer, and `encoding` will
indicate the sort of string that it is. This is to support
implementations that have an optimized handling for certain string
data encodings. If you do not explicitly set the `decodeStrings`
option to `false`, then you can safely ignore the `encoding` argument,
and assume that `chunk` will always be a Buffer.
This method is prefixed with an underscore because it is internal to
the class that defines it, and should not be called directly by user
programs. However, you **are** expected to override this method in
your own extension classes.
### writable.write(chunk, [encoding], [callback])
* `chunk` {Buffer | String} Data to be written
* `encoding` {String} Optional. If `chunk` is a string, then encoding
defaults to `'utf8'`
* `callback` {Function} Optional. Called when this chunk is
successfully written.
* Returns {Boolean}
Writes `chunk` to the stream. Returns `true` if the data has been
flushed to the underlying resource. Returns `false` to indicate that
the buffer is full, and the data will be sent out in the future. The
`'drain'` event will indicate when the buffer is empty again.
The specifics of when `write()` will return false, is determined by
the `highWaterMark` option provided to the constructor.
### writable.end([chunk], [encoding], [callback])
* `chunk` {Buffer | String} Optional final data to be written
* `encoding` {String} Optional. If `chunk` is a string, then encoding
defaults to `'utf8'`
* `callback` {Function} Optional. Called when the final chunk is
successfully written.
Call this method to signal the end of the data being written to the
stream.
### Event: 'drain'
Emitted when the stream's write queue empties and it's safe to write
without buffering again. Listen for it when `stream.write()` returns
`false`.
### Event: 'close'
Emitted when the underlying resource (for example, the backing file
descriptor) has been closed. Not all streams will emit this.
### Event: 'finish'
When `end()` is called and there are no more chunks to write, this
event is emitted.
### Event: 'pipe'
* `source` {Readable Stream}
Emitted when the stream is passed to a readable stream's pipe method.
### Event 'unpipe'
* `source` {Readable Stream}
Emitted when a previously established `pipe()` is removed using the
source Readable stream's `unpipe()` method.
## Class: stream.Duplex
<!--type=class-->
A "duplex" stream is one that is both Readable and Writable, such as a
TCP socket connection.
Note that `stream.Duplex` is an abstract class designed to be
extended with an underlying implementation of the `_read(size)`
and `_write(chunk, encoding, callback)` methods as you would with a Readable or
Writable stream class.
Since JavaScript doesn't have multiple prototypal inheritance, this
class prototypally inherits from Readable, and then parasitically from
Writable. It is thus up to the user to implement both the lowlevel
`_read(n)` method as well as the lowlevel `_write(chunk, encoding, cb)` method
on extension duplex classes.
### new stream.Duplex(options)
* `options` {Object} Passed to both Writable and Readable
constructors. Also has the following fields:
* `allowHalfOpen` {Boolean} Default=true. If set to `false`, then
the stream will automatically end the readable side when the
writable side ends and vice versa.
In classes that extend the Duplex class, make sure to call the
constructor so that the buffering settings can be properly
initialized.
## Class: stream.Transform
A "transform" stream is a duplex stream where the output is causally
connected in some way to the input, such as a zlib stream or a crypto
stream.
There is no requirement that the output be the same size as the input,
the same number of chunks, or arrive at the same time. For example, a
Hash stream will only ever have a single chunk of output which is
provided when the input is ended. A zlib stream will either produce
much smaller or much larger than its input.
Rather than implement the `_read()` and `_write()` methods, Transform
classes must implement the `_transform()` method, and may optionally
also implement the `_flush()` method. (See below.)
### new stream.Transform([options])
* `options` {Object} Passed to both Writable and Readable
constructors.
In classes that extend the Transform class, make sure to call the
constructor so that the buffering settings can be properly
initialized.
### transform.\_transform(chunk, encoding, callback)
* `chunk` {Buffer | String} The chunk to be transformed. Will always
be a buffer unless the `decodeStrings` option was set to `false`.
* `encoding` {String} If the chunk is a string, then this is the
encoding type. (Ignore if `decodeStrings` chunk is a buffer.)
* `callback` {Function} Call this function (optionally with an error
argument) when you are done processing the supplied chunk.
Note: **This function MUST NOT be called directly.** It should be
implemented by child classes, and called by the internal Transform
class methods only.
All Transform stream implementations must provide a `_transform`
method to accept input and produce output.
`_transform` should do whatever has to be done in this specific
Transform class, to handle the bytes being written, and pass them off
to the readable portion of the interface. Do asynchronous I/O,
process things, and so on.
Call `transform.push(outputChunk)` 0 or more times to generate output
from this input chunk, depending on how much data you want to output
as a result of this chunk.
Call the callback function only when the current chunk is completely
consumed. Note that there may or may not be output as a result of any
particular input chunk.
This method is prefixed with an underscore because it is internal to
the class that defines it, and should not be called directly by user
programs. However, you **are** expected to override this method in
your own extension classes.
### transform.\_flush(callback)
* `callback` {Function} Call this function (optionally with an error
argument) when you are done flushing any remaining data.
Note: **This function MUST NOT be called directly.** It MAY be implemented
by child classes, and if so, will be called by the internal Transform
class methods only.
In some cases, your transform operation may need to emit a bit more
data at the end of the stream. For example, a `Zlib` compression
stream will store up some internal state so that it can optimally
compress the output. At the end, however, it needs to do the best it
can with what is left, so that the data will be complete.
In those cases, you can implement a `_flush` method, which will be
called at the very end, after all the written data is consumed, but
before emitting `end` to signal the end of the readable side. Just
like with `_transform`, call `transform.push(chunk)` zero or more
times, as appropriate, and call `callback` when the flush operation is
complete.
This method is prefixed with an underscore because it is internal to
the class that defines it, and should not be called directly by user
programs. However, you **are** expected to override this method in
your own extension classes.
### Example: `SimpleProtocol` parser
The example above of a simple protocol parser can be implemented much
more simply by using the higher level `Transform` stream class.
In this example, rather than providing the input as an argument, it
would be piped into the parser, which is a more idiomatic Node stream
approach.
```javascript
function SimpleProtocol(options) {
if (!(this instanceof SimpleProtocol))
return new SimpleProtocol(options);
Transform.call(this, options);
this._inBody = false;
this._sawFirstCr = false;
this._rawHeader = [];
this.header = null;
}
SimpleProtocol.prototype = Object.create(
Transform.prototype, { constructor: { value: SimpleProtocol }});
SimpleProtocol.prototype._transform = function(chunk, encoding, done) {
if (!this._inBody) {
// check if the chunk has a \n\n
var split = -1;
for (var i = 0; i < chunk.length; i++) {
if (chunk[i] === 10) { // '\n'
if (this._sawFirstCr) {
split = i;
break;
} else {
this._sawFirstCr = true;
}
} else {
this._sawFirstCr = false;
}
}
if (split === -1) {
// still waiting for the \n\n
// stash the chunk, and try again.
this._rawHeader.push(chunk);
} else {
this._inBody = true;
var h = chunk.slice(0, split);
this._rawHeader.push(h);
var header = Buffer.concat(this._rawHeader).toString();
try {
this.header = JSON.parse(header);
} catch (er) {
this.emit('error', new Error('invalid simple protocol data'));
return;
}
// and let them know that we are done parsing the header.
this.emit('header', this.header);
// now, because we got some extra data, emit this first.
this.push(b);
}
} else {
// from there on, just provide the data to our consumer as-is.
this.push(b);
}
done();
};
var parser = new SimpleProtocol();
source.pipe(parser)
// Now parser is a readable stream that will emit 'header'
// with the parsed header data.
```
## Class: stream.PassThrough
This is a trivial implementation of a `Transform` stream that simply
passes the input bytes across to the output. Its purpose is mainly
for examples and testing, but there are occasionally use cases where
it can come in handy.
[EventEmitter]: events.html#events_class_events_eventemitter
@@ -0,0 +1 @@
module.exports = require("./lib/_stream_duplex.js")
@@ -0,0 +1,32 @@
var Transform = require('../transform');
var inherits = require('util').inherits;
// subclass
function MyStream () {
Transform.call(this, {
lowWaterMark: 0,
encoding: 'utf8'
});
}
inherits(MyStream, Transform);
MyStream.prototype._transform = function (chunk, outputFn, callback) {
outputFn(new Buffer(String(chunk).toUpperCase()));
callback();
};
// use it!
var s = new MyStream();
process.stdin.resume();
process.stdin.pipe(s).pipe(process.stdout);
if (process.stdin.setRawMode)
process.stdin.setRawMode(true);
process.stdin.on('data', function (c) {
c = c.toString();
if (c === '\u0003' || c === '\u0004') {
process.stdin.pause();
s.end();
}
if (c === '\r')
process.stdout.write('\n');
});
@@ -0,0 +1,15 @@
var fs = require('fs');
var FSReadable = require('../fs.js');
var rst = new FSReadable(__filename);
rst.on('end', function() {
process.stdin.pause();
});
process.stdin.setRawMode(true);
process.stdin.on('data', function() {
var c = rst.read(3);
if (!c) return;
process.stdout.write(c);
});
process.stdin.resume();
@@ -0,0 +1,17 @@
var fs = require('fs');
var fst = fs.createReadStream(__filename);
var Readable = require('../readable.js');
var rst = new Readable();
rst.wrap(fst);
rst.on('end', function() {
process.stdin.pause();
});
process.stdin.setRawMode(true);
process.stdin.on('data', function() {
var c = rst.read(3);
if (!c) return setTimeout(process.exit, 500)
process.stdout.write(c);
});
process.stdin.resume();
@@ -0,0 +1,68 @@
diff --git a/lib/_stream_duplex.js b/lib/_stream_duplex.js
index c5a741c..a2e0d8e 100644
--- a/lib/_stream_duplex.js
+++ b/lib/_stream_duplex.js
@@ -26,8 +26,8 @@
module.exports = Duplex;
var util = require('util');
-var Readable = require('_stream_readable');
-var Writable = require('_stream_writable');
+var Readable = require('./_stream_readable');
+var Writable = require('./_stream_writable');
util.inherits(Duplex, Readable);
diff --git a/lib/_stream_passthrough.js b/lib/_stream_passthrough.js
index a5e9864..330c247 100644
--- a/lib/_stream_passthrough.js
+++ b/lib/_stream_passthrough.js
@@ -25,7 +25,7 @@
module.exports = PassThrough;
-var Transform = require('_stream_transform');
+var Transform = require('./_stream_transform');
var util = require('util');
util.inherits(PassThrough, Transform);
diff --git a/lib/_stream_readable.js b/lib/_stream_readable.js
index 2259d2e..e6681ee 100644
--- a/lib/_stream_readable.js
+++ b/lib/_stream_readable.js
@@ -23,6 +23,9 @@ module.exports = Readable;
Readable.ReadableState = ReadableState;
var EE = require('events').EventEmitter;
+if (!EE.listenerCount) EE.listenerCount = function(emitter, type) {
+ return emitter.listeners(type).length;
+};
var Stream = require('stream');
var util = require('util');
var StringDecoder;
diff --git a/lib/_stream_transform.js b/lib/_stream_transform.js
index e925b4b..f08b05e 100644
--- a/lib/_stream_transform.js
+++ b/lib/_stream_transform.js
@@ -64,7 +64,7 @@
module.exports = Transform;
-var Duplex = require('_stream_duplex');
+var Duplex = require('./_stream_duplex');
var util = require('util');
util.inherits(Transform, Duplex);
diff --git a/lib/_stream_writable.js b/lib/_stream_writable.js
index a26f711..56ca47d 100644
--- a/lib/_stream_writable.js
+++ b/lib/_stream_writable.js
@@ -109,7 +109,7 @@ function WritableState(options, stream) {
function Writable(options) {
// Writable ctor is applied to Duplexes, though they're not
// instanceof Writable, they're instanceof Readable.
- if (!(this instanceof Writable) && !(this instanceof Stream.Duplex))
+ if (!(this instanceof Writable) && !(this instanceof require('./_stream_duplex')))
return new Writable(options);
this._writableState = new WritableState(options, this);
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
@@ -0,0 +1,69 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
// a duplex stream is just a stream that is both readable and writable.
// Since JS doesn't have multiple prototypal inheritance, this class
// prototypally inherits from Readable, and then parasitically from
// Writable.
module.exports = Duplex;
var util = require('util');
var Readable = require('./_stream_readable');
var Writable = require('./_stream_writable');
util.inherits(Duplex, Readable);
Object.keys(Writable.prototype).forEach(function(method) {
if (!Duplex.prototype[method])
Duplex.prototype[method] = Writable.prototype[method];
});
function Duplex(options) {
if (!(this instanceof Duplex))
return new Duplex(options);
Readable.call(this, options);
Writable.call(this, options);
if (options && options.readable === false)
this.readable = false;
if (options && options.writable === false)
this.writable = false;
this.allowHalfOpen = true;
if (options && options.allowHalfOpen === false)
this.allowHalfOpen = false;
this.once('end', onend);
}
// the no-half-open enforcer
function onend() {
// if we allow half-open state, or if the writable side ended,
// then we're ok.
if (this.allowHalfOpen || this._writableState.ended)
return;
// no more data can be written.
// But allow more writes to happen in this tick.
process.nextTick(this.end.bind(this));
}
@@ -0,0 +1,41 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
// a passthrough stream.
// basically just the most minimal sort of Transform stream.
// Every written chunk gets output as-is.
module.exports = PassThrough;
var Transform = require('./_stream_transform');
var util = require('util');
util.inherits(PassThrough, Transform);
function PassThrough(options) {
if (!(this instanceof PassThrough))
return new PassThrough(options);
Transform.call(this, options);
}
PassThrough.prototype._transform = function(chunk, encoding, cb) {
cb(null, chunk);
};
@@ -0,0 +1,927 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
module.exports = Readable;
Readable.ReadableState = ReadableState;
var EE = require('events').EventEmitter;
if (!EE.listenerCount) EE.listenerCount = function(emitter, type) {
return emitter.listeners(type).length;
};
var Stream = require('stream');
var util = require('util');
var StringDecoder;
util.inherits(Readable, Stream);
function ReadableState(options, stream) {
options = options || {};
// the point at which it stops calling _read() to fill the buffer
// Note: 0 is a valid value, means "don't call _read preemptively ever"
var hwm = options.highWaterMark;
this.highWaterMark = (hwm || hwm === 0) ? hwm : 16 * 1024;
// cast to ints.
this.highWaterMark = ~~this.highWaterMark;
this.buffer = [];
this.length = 0;
this.pipes = null;
this.pipesCount = 0;
this.flowing = false;
this.ended = false;
this.endEmitted = false;
this.reading = false;
// In streams that never have any data, and do push(null) right away,
// the consumer can miss the 'end' event if they do some I/O before
// consuming the stream. So, we don't emit('end') until some reading
// happens.
this.calledRead = false;
// a flag to be able to tell if the onwrite cb is called immediately,
// or on a later tick. We set this to true at first, becuase any
// actions that shouldn't happen until "later" should generally also
// not happen before the first write call.
this.sync = true;
// whenever we return null, then we set a flag to say
// that we're awaiting a 'readable' event emission.
this.needReadable = false;
this.emittedReadable = false;
this.readableListening = false;
// object stream flag. Used to make read(n) ignore n and to
// make all the buffer merging and length checks go away
this.objectMode = !!options.objectMode;
// Crypto is kind of old and crusty. Historically, its default string
// encoding is 'binary' so we have to make this configurable.
// Everything else in the universe uses 'utf8', though.
this.defaultEncoding = options.defaultEncoding || 'utf8';
// when piping, we only care about 'readable' events that happen
// after read()ing all the bytes and not getting any pushback.
this.ranOut = false;
// the number of writers that are awaiting a drain event in .pipe()s
this.awaitDrain = 0;
// if true, a maybeReadMore has been scheduled
this.readingMore = false;
this.decoder = null;
this.encoding = null;
if (options.encoding) {
if (!StringDecoder)
StringDecoder = require('string_decoder').StringDecoder;
this.decoder = new StringDecoder(options.encoding);
this.encoding = options.encoding;
}
}
function Readable(options) {
if (!(this instanceof Readable))
return new Readable(options);
this._readableState = new ReadableState(options, this);
// legacy
this.readable = true;
Stream.call(this);
}
// Manually shove something into the read() buffer.
// This returns true if the highWaterMark has not been hit yet,
// similar to how Writable.write() returns true if you should
// write() some more.
Readable.prototype.push = function(chunk, encoding) {
var state = this._readableState;
if (typeof chunk === 'string' && !state.objectMode) {
encoding = encoding || state.defaultEncoding;
if (encoding !== state.encoding) {
chunk = new Buffer(chunk, encoding);
encoding = '';
}
}
return readableAddChunk(this, state, chunk, encoding, false);
};
// Unshift should *always* be something directly out of read()
Readable.prototype.unshift = function(chunk) {
var state = this._readableState;
return readableAddChunk(this, state, chunk, '', true);
};
function readableAddChunk(stream, state, chunk, encoding, addToFront) {
var er = chunkInvalid(state, chunk);
if (er) {
stream.emit('error', er);
} else if (chunk === null || chunk === undefined) {
state.reading = false;
if (!state.ended)
onEofChunk(stream, state);
} else if (state.objectMode || chunk && chunk.length > 0) {
if (state.ended && !addToFront) {
var e = new Error('stream.push() after EOF');
stream.emit('error', e);
} else if (state.endEmitted && addToFront) {
var e = new Error('stream.unshift() after end event');
stream.emit('error', e);
} else {
if (state.decoder && !addToFront && !encoding)
chunk = state.decoder.write(chunk);
// update the buffer info.
state.length += state.objectMode ? 1 : chunk.length;
if (addToFront) {
state.buffer.unshift(chunk);
} else {
state.reading = false;
state.buffer.push(chunk);
}
if (state.needReadable)
emitReadable(stream);
maybeReadMore(stream, state);
}
} else if (!addToFront) {
state.reading = false;
}
return needMoreData(state);
}
// if it's past the high water mark, we can push in some more.
// Also, if we have no data yet, we can stand some
// more bytes. This is to work around cases where hwm=0,
// such as the repl. Also, if the push() triggered a
// readable event, and the user called read(largeNumber) such that
// needReadable was set, then we ought to push more, so that another
// 'readable' event will be triggered.
function needMoreData(state) {
return !state.ended &&
(state.needReadable ||
state.length < state.highWaterMark ||
state.length === 0);
}
// backwards compatibility.
Readable.prototype.setEncoding = function(enc) {
if (!StringDecoder)
StringDecoder = require('string_decoder').StringDecoder;
this._readableState.decoder = new StringDecoder(enc);
this._readableState.encoding = enc;
};
// Don't raise the hwm > 128MB
var MAX_HWM = 0x800000;
function roundUpToNextPowerOf2(n) {
if (n >= MAX_HWM) {
n = MAX_HWM;
} else {
// Get the next highest power of 2
n--;
for (var p = 1; p < 32; p <<= 1) n |= n >> p;
n++;
}
return n;
}
function howMuchToRead(n, state) {
if (state.length === 0 && state.ended)
return 0;
if (state.objectMode)
return n === 0 ? 0 : 1;
if (isNaN(n) || n === null) {
// only flow one buffer at a time
if (state.flowing && state.buffer.length)
return state.buffer[0].length;
else
return state.length;
}
if (n <= 0)
return 0;
// If we're asking for more than the target buffer level,
// then raise the water mark. Bump up to the next highest
// power of 2, to prevent increasing it excessively in tiny
// amounts.
if (n > state.highWaterMark)
state.highWaterMark = roundUpToNextPowerOf2(n);
// don't have that much. return null, unless we've ended.
if (n > state.length) {
if (!state.ended) {
state.needReadable = true;
return 0;
} else
return state.length;
}
return n;
}
// you can override either this method, or the async _read(n) below.
Readable.prototype.read = function(n) {
var state = this._readableState;
state.calledRead = true;
var nOrig = n;
if (typeof n !== 'number' || n > 0)
state.emittedReadable = false;
// if we're doing read(0) to trigger a readable event, but we
// already have a bunch of data in the buffer, then just trigger
// the 'readable' event and move on.
if (n === 0 &&
state.needReadable &&
(state.length >= state.highWaterMark || state.ended)) {
emitReadable(this);
return null;
}
n = howMuchToRead(n, state);
// if we've ended, and we're now clear, then finish it up.
if (n === 0 && state.ended) {
if (state.length === 0)
endReadable(this);
return null;
}
// All the actual chunk generation logic needs to be
// *below* the call to _read. The reason is that in certain
// synthetic stream cases, such as passthrough streams, _read
// may be a completely synchronous operation which may change
// the state of the read buffer, providing enough data when
// before there was *not* enough.
//
// So, the steps are:
// 1. Figure out what the state of things will be after we do
// a read from the buffer.
//
// 2. If that resulting state will trigger a _read, then call _read.
// Note that this may be asynchronous, or synchronous. Yes, it is
// deeply ugly to write APIs this way, but that still doesn't mean
// that the Readable class should behave improperly, as streams are
// designed to be sync/async agnostic.
// Take note if the _read call is sync or async (ie, if the read call
// has returned yet), so that we know whether or not it's safe to emit
// 'readable' etc.
//
// 3. Actually pull the requested chunks out of the buffer and return.
// if we need a readable event, then we need to do some reading.
var doRead = state.needReadable;
// if we currently have less than the highWaterMark, then also read some
if (state.length - n <= state.highWaterMark)
doRead = true;
// however, if we've ended, then there's no point, and if we're already
// reading, then it's unnecessary.
if (state.ended || state.reading)
doRead = false;
if (doRead) {
state.reading = true;
state.sync = true;
// if the length is currently zero, then we *need* a readable event.
if (state.length === 0)
state.needReadable = true;
// call internal read method
this._read(state.highWaterMark);
state.sync = false;
}
// If _read called its callback synchronously, then `reading`
// will be false, and we need to re-evaluate how much data we
// can return to the user.
if (doRead && !state.reading)
n = howMuchToRead(nOrig, state);
var ret;
if (n > 0)
ret = fromList(n, state);
else
ret = null;
if (ret === null) {
state.needReadable = true;
n = 0;
}
state.length -= n;
// If we have nothing in the buffer, then we want to know
// as soon as we *do* get something into the buffer.
if (state.length === 0 && !state.ended)
state.needReadable = true;
// If we happened to read() exactly the remaining amount in the
// buffer, and the EOF has been seen at this point, then make sure
// that we emit 'end' on the very next tick.
if (state.ended && !state.endEmitted && state.length === 0)
endReadable(this);
return ret;
};
function chunkInvalid(state, chunk) {
var er = null;
if (!Buffer.isBuffer(chunk) &&
'string' !== typeof chunk &&
chunk !== null &&
chunk !== undefined &&
!state.objectMode &&
!er) {
er = new TypeError('Invalid non-string/buffer chunk');
}
return er;
}
function onEofChunk(stream, state) {
if (state.decoder && !state.ended) {
var chunk = state.decoder.end();
if (chunk && chunk.length) {
state.buffer.push(chunk);
state.length += state.objectMode ? 1 : chunk.length;
}
}
state.ended = true;
// if we've ended and we have some data left, then emit
// 'readable' now to make sure it gets picked up.
if (state.length > 0)
emitReadable(stream);
else
endReadable(stream);
}
// Don't emit readable right away in sync mode, because this can trigger
// another read() call => stack overflow. This way, it might trigger
// a nextTick recursion warning, but that's not so bad.
function emitReadable(stream) {
var state = stream._readableState;
state.needReadable = false;
if (state.emittedReadable)
return;
state.emittedReadable = true;
if (state.sync)
process.nextTick(function() {
emitReadable_(stream);
});
else
emitReadable_(stream);
}
function emitReadable_(stream) {
stream.emit('readable');
}
// at this point, the user has presumably seen the 'readable' event,
// and called read() to consume some data. that may have triggered
// in turn another _read(n) call, in which case reading = true if
// it's in progress.
// However, if we're not ended, or reading, and the length < hwm,
// then go ahead and try to read some more preemptively.
function maybeReadMore(stream, state) {
if (!state.readingMore) {
state.readingMore = true;
process.nextTick(function() {
maybeReadMore_(stream, state);
});
}
}
function maybeReadMore_(stream, state) {
var len = state.length;
while (!state.reading && !state.flowing && !state.ended &&
state.length < state.highWaterMark) {
stream.read(0);
if (len === state.length)
// didn't get any data, stop spinning.
break;
else
len = state.length;
}
state.readingMore = false;
}
// abstract method. to be overridden in specific implementation classes.
// call cb(er, data) where data is <= n in length.
// for virtual (non-string, non-buffer) streams, "length" is somewhat
// arbitrary, and perhaps not very meaningful.
Readable.prototype._read = function(n) {
this.emit('error', new Error('not implemented'));
};
Readable.prototype.pipe = function(dest, pipeOpts) {
var src = this;
var state = this._readableState;
switch (state.pipesCount) {
case 0:
state.pipes = dest;
break;
case 1:
state.pipes = [state.pipes, dest];
break;
default:
state.pipes.push(dest);
break;
}
state.pipesCount += 1;
var doEnd = (!pipeOpts || pipeOpts.end !== false) &&
dest !== process.stdout &&
dest !== process.stderr;
var endFn = doEnd ? onend : cleanup;
if (state.endEmitted)
process.nextTick(endFn);
else
src.once('end', endFn);
dest.on('unpipe', onunpipe);
function onunpipe(readable) {
if (readable !== src) return;
cleanup();
}
function onend() {
dest.end();
}
// when the dest drains, it reduces the awaitDrain counter
// on the source. This would be more elegant with a .once()
// handler in flow(), but adding and removing repeatedly is
// too slow.
var ondrain = pipeOnDrain(src);
dest.on('drain', ondrain);
function cleanup() {
// cleanup event handlers once the pipe is broken
dest.removeListener('close', onclose);
dest.removeListener('finish', onfinish);
dest.removeListener('drain', ondrain);
dest.removeListener('error', onerror);
dest.removeListener('unpipe', onunpipe);
src.removeListener('end', onend);
src.removeListener('end', cleanup);
// if the reader is waiting for a drain event from this
// specific writer, then it would cause it to never start
// flowing again.
// So, if this is awaiting a drain, then we just call it now.
// If we don't know, then assume that we are waiting for one.
if (!dest._writableState || dest._writableState.needDrain)
ondrain();
}
// if the dest has an error, then stop piping into it.
// however, don't suppress the throwing behavior for this.
function onerror(er) {
unpipe();
dest.removeListener('error', onerror);
if (EE.listenerCount(dest, 'error') === 0)
dest.emit('error', er);
}
// This is a brutally ugly hack to make sure that our error handler
// is attached before any userland ones. NEVER DO THIS.
if (!dest._events.error)
dest.on('error', onerror);
else if (Array.isArray(dest._events.error))
dest._events.error.unshift(onerror);
else
dest._events.error = [onerror, dest._events.error];
// Both close and finish should trigger unpipe, but only once.
function onclose() {
dest.removeListener('finish', onfinish);
unpipe();
}
dest.once('close', onclose);
function onfinish() {
dest.removeListener('close', onclose);
unpipe();
}
dest.once('finish', onfinish);
function unpipe() {
src.unpipe(dest);
}
// tell the dest that it's being piped to
dest.emit('pipe', src);
// start the flow if it hasn't been started already.
if (!state.flowing) {
// the handler that waits for readable events after all
// the data gets sucked out in flow.
// This would be easier to follow with a .once() handler
// in flow(), but that is too slow.
this.on('readable', pipeOnReadable);
state.flowing = true;
process.nextTick(function() {
flow(src);
});
}
return dest;
};
function pipeOnDrain(src) {
return function() {
var dest = this;
var state = src._readableState;
state.awaitDrain--;
if (state.awaitDrain === 0)
flow(src);
};
}
function flow(src) {
var state = src._readableState;
var chunk;
state.awaitDrain = 0;
function write(dest, i, list) {
var written = dest.write(chunk);
if (false === written) {
state.awaitDrain++;
}
}
while (state.pipesCount && null !== (chunk = src.read())) {
if (state.pipesCount === 1)
write(state.pipes, 0, null);
else
state.pipes.forEach(write);
src.emit('data', chunk);
// if anyone needs a drain, then we have to wait for that.
if (state.awaitDrain > 0)
return;
}
// if every destination was unpiped, either before entering this
// function, or in the while loop, then stop flowing.
//
// NB: This is a pretty rare edge case.
if (state.pipesCount === 0) {
state.flowing = false;
// if there were data event listeners added, then switch to old mode.
if (EE.listenerCount(src, 'data') > 0)
emitDataEvents(src);
return;
}
// at this point, no one needed a drain, so we just ran out of data
// on the next readable event, start it over again.
state.ranOut = true;
}
function pipeOnReadable() {
if (this._readableState.ranOut) {
this._readableState.ranOut = false;
flow(this);
}
}
Readable.prototype.unpipe = function(dest) {
var state = this._readableState;
// if we're not piping anywhere, then do nothing.
if (state.pipesCount === 0)
return this;
// just one destination. most common case.
if (state.pipesCount === 1) {
// passed in one, but it's not the right one.
if (dest && dest !== state.pipes)
return this;
if (!dest)
dest = state.pipes;
// got a match.
state.pipes = null;
state.pipesCount = 0;
this.removeListener('readable', pipeOnReadable);
state.flowing = false;
if (dest)
dest.emit('unpipe', this);
return this;
}
// slow case. multiple pipe destinations.
if (!dest) {
// remove all.
var dests = state.pipes;
var len = state.pipesCount;
state.pipes = null;
state.pipesCount = 0;
this.removeListener('readable', pipeOnReadable);
state.flowing = false;
for (var i = 0; i < len; i++)
dests[i].emit('unpipe', this);
return this;
}
// try to find the right one.
var i = state.pipes.indexOf(dest);
if (i === -1)
return this;
state.pipes.splice(i, 1);
state.pipesCount -= 1;
if (state.pipesCount === 1)
state.pipes = state.pipes[0];
dest.emit('unpipe', this);
return this;
};
// set up data events if they are asked for
// Ensure readable listeners eventually get something
Readable.prototype.on = function(ev, fn) {
var res = Stream.prototype.on.call(this, ev, fn);
if (ev === 'data' && !this._readableState.flowing)
emitDataEvents(this);
if (ev === 'readable' && this.readable) {
var state = this._readableState;
if (!state.readableListening) {
state.readableListening = true;
state.emittedReadable = false;
state.needReadable = true;
if (!state.reading) {
this.read(0);
} else if (state.length) {
emitReadable(this, state);
}
}
}
return res;
};
Readable.prototype.addListener = Readable.prototype.on;
// pause() and resume() are remnants of the legacy readable stream API
// If the user uses them, then switch into old mode.
Readable.prototype.resume = function() {
emitDataEvents(this);
this.read(0);
this.emit('resume');
};
Readable.prototype.pause = function() {
emitDataEvents(this, true);
this.emit('pause');
};
function emitDataEvents(stream, startPaused) {
var state = stream._readableState;
if (state.flowing) {
// https://github.com/isaacs/readable-stream/issues/16
throw new Error('Cannot switch to old mode now.');
}
var paused = startPaused || false;
var readable = false;
// convert to an old-style stream.
stream.readable = true;
stream.pipe = Stream.prototype.pipe;
stream.on = stream.addListener = Stream.prototype.on;
stream.on('readable', function() {
readable = true;
var c;
while (!paused && (null !== (c = stream.read())))
stream.emit('data', c);
if (c === null) {
readable = false;
stream._readableState.needReadable = true;
}
});
stream.pause = function() {
paused = true;
this.emit('pause');
};
stream.resume = function() {
paused = false;
if (readable)
process.nextTick(function() {
stream.emit('readable');
});
else
this.read(0);
this.emit('resume');
};
// now make it start, just in case it hadn't already.
stream.emit('readable');
}
// wrap an old-style stream as the async data source.
// This is *not* part of the readable stream interface.
// It is an ugly unfortunate mess of history.
Readable.prototype.wrap = function(stream) {
var state = this._readableState;
var paused = false;
var self = this;
stream.on('end', function() {
if (state.decoder && !state.ended) {
var chunk = state.decoder.end();
if (chunk && chunk.length)
self.push(chunk);
}
self.push(null);
});
stream.on('data', function(chunk) {
if (state.decoder)
chunk = state.decoder.write(chunk);
if (!chunk || !state.objectMode && !chunk.length)
return;
var ret = self.push(chunk);
if (!ret) {
paused = true;
stream.pause();
}
});
// proxy all the other methods.
// important when wrapping filters and duplexes.
for (var i in stream) {
if (typeof stream[i] === 'function' &&
typeof this[i] === 'undefined') {
this[i] = function(method) { return function() {
return stream[method].apply(stream, arguments);
}}(i);
}
}
// proxy certain important events.
var events = ['error', 'close', 'destroy', 'pause', 'resume'];
events.forEach(function(ev) {
stream.on(ev, self.emit.bind(self, ev));
});
// when we try to consume some more bytes, simply unpause the
// underlying stream.
self._read = function(n) {
if (paused) {
paused = false;
stream.resume();
}
};
return self;
};
// exposed for testing purposes only.
Readable._fromList = fromList;
// Pluck off n bytes from an array of buffers.
// Length is the combined lengths of all the buffers in the list.
function fromList(n, state) {
var list = state.buffer;
var length = state.length;
var stringMode = !!state.decoder;
var objectMode = !!state.objectMode;
var ret;
// nothing in the list, definitely empty.
if (list.length === 0)
return null;
if (length === 0)
ret = null;
else if (objectMode)
ret = list.shift();
else if (!n || n >= length) {
// read it all, truncate the array.
if (stringMode)
ret = list.join('');
else
ret = Buffer.concat(list, length);
list.length = 0;
} else {
// read just some of it.
if (n < list[0].length) {
// just take a part of the first list item.
// slice is the same for buffers and strings.
var buf = list[0];
ret = buf.slice(0, n);
list[0] = buf.slice(n);
} else if (n === list[0].length) {
// first list is a perfect match
ret = list.shift();
} else {
// complex case.
// we have enough to cover it, but it spans past the first buffer.
if (stringMode)
ret = '';
else
ret = new Buffer(n);
var c = 0;
for (var i = 0, l = list.length; i < l && c < n; i++) {
var buf = list[0];
var cpy = Math.min(n - c, buf.length);
if (stringMode)
ret += buf.slice(0, cpy);
else
buf.copy(ret, c, 0, cpy);
if (cpy < buf.length)
list[0] = buf.slice(cpy);
else
list.shift();
c += cpy;
}
}
}
return ret;
}
function endReadable(stream) {
var state = stream._readableState;
// If we get here before consuming all the bytes, then that is a
// bug in node. Should never happen.
if (state.length > 0)
throw new Error('endReadable called on non-empty stream');
if (!state.endEmitted && state.calledRead) {
state.ended = true;
process.nextTick(function() {
// Check that we didn't get one last unshift.
if (!state.endEmitted && state.length === 0) {
state.endEmitted = true;
stream.readable = false;
stream.emit('end');
}
});
}
}
@@ -0,0 +1,205 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
// a transform stream is a readable/writable stream where you do
// something with the data. Sometimes it's called a "filter",
// but that's not a great name for it, since that implies a thing where
// some bits pass through, and others are simply ignored. (That would
// be a valid example of a transform, of course.)
//
// While the output is causally related to the input, it's not a
// necessarily symmetric or synchronous transformation. For example,
// a zlib stream might take multiple plain-text writes(), and then
// emit a single compressed chunk some time in the future.
//
// Here's how this works:
//
// The Transform stream has all the aspects of the readable and writable
// stream classes. When you write(chunk), that calls _write(chunk,cb)
// internally, and returns false if there's a lot of pending writes
// buffered up. When you call read(), that calls _read(n) until
// there's enough pending readable data buffered up.
//
// In a transform stream, the written data is placed in a buffer. When
// _read(n) is called, it transforms the queued up data, calling the
// buffered _write cb's as it consumes chunks. If consuming a single
// written chunk would result in multiple output chunks, then the first
// outputted bit calls the readcb, and subsequent chunks just go into
// the read buffer, and will cause it to emit 'readable' if necessary.
//
// This way, back-pressure is actually determined by the reading side,
// since _read has to be called to start processing a new chunk. However,
// a pathological inflate type of transform can cause excessive buffering
// here. For example, imagine a stream where every byte of input is
// interpreted as an integer from 0-255, and then results in that many
// bytes of output. Writing the 4 bytes {ff,ff,ff,ff} would result in
// 1kb of data being output. In this case, you could write a very small
// amount of input, and end up with a very large amount of output. In
// such a pathological inflating mechanism, there'd be no way to tell
// the system to stop doing the transform. A single 4MB write could
// cause the system to run out of memory.
//
// However, even in such a pathological case, only a single written chunk
// would be consumed, and then the rest would wait (un-transformed) until
// the results of the previous transformed chunk were consumed.
module.exports = Transform;
var Duplex = require('./_stream_duplex');
var util = require('util');
util.inherits(Transform, Duplex);
function TransformState(options, stream) {
this.afterTransform = function(er, data) {
return afterTransform(stream, er, data);
};
this.needTransform = false;
this.transforming = false;
this.writecb = null;
this.writechunk = null;
}
function afterTransform(stream, er, data) {
var ts = stream._transformState;
ts.transforming = false;
var cb = ts.writecb;
if (!cb)
return stream.emit('error', new Error('no writecb in Transform class'));
ts.writechunk = null;
ts.writecb = null;
if (data !== null && data !== undefined)
stream.push(data);
if (cb)
cb(er);
var rs = stream._readableState;
rs.reading = false;
if (rs.needReadable || rs.length < rs.highWaterMark) {
stream._read(rs.highWaterMark);
}
}
function Transform(options) {
if (!(this instanceof Transform))
return new Transform(options);
Duplex.call(this, options);
var ts = this._transformState = new TransformState(options, this);
// when the writable side finishes, then flush out anything remaining.
var stream = this;
// start out asking for a readable event once data is transformed.
this._readableState.needReadable = true;
// we have implemented the _read method, and done the other things
// that Readable wants before the first _read call, so unset the
// sync guard flag.
this._readableState.sync = false;
this.once('finish', function() {
if ('function' === typeof this._flush)
this._flush(function(er) {
done(stream, er);
});
else
done(stream);
});
}
Transform.prototype.push = function(chunk, encoding) {
this._transformState.needTransform = false;
return Duplex.prototype.push.call(this, chunk, encoding);
};
// This is the part where you do stuff!
// override this function in implementation classes.
// 'chunk' is an input chunk.
//
// Call `push(newChunk)` to pass along transformed output
// to the readable side. You may call 'push' zero or more times.
//
// Call `cb(err)` when you are done with this chunk. If you pass
// an error, then that'll put the hurt on the whole operation. If you
// never call cb(), then you'll never get another chunk.
Transform.prototype._transform = function(chunk, encoding, cb) {
throw new Error('not implemented');
};
Transform.prototype._write = function(chunk, encoding, cb) {
var ts = this._transformState;
ts.writecb = cb;
ts.writechunk = chunk;
ts.writeencoding = encoding;
if (!ts.transforming) {
var rs = this._readableState;
if (ts.needTransform ||
rs.needReadable ||
rs.length < rs.highWaterMark)
this._read(rs.highWaterMark);
}
};
// Doesn't matter what the args are here.
// _transform does all the work.
// That we got here means that the readable side wants more data.
Transform.prototype._read = function(n) {
var ts = this._transformState;
if (ts.writechunk && ts.writecb && !ts.transforming) {
ts.transforming = true;
this._transform(ts.writechunk, ts.writeencoding, ts.afterTransform);
} else {
// mark that we need a transform, so that any data that comes in
// will get processed, now that we've asked for it.
ts.needTransform = true;
}
};
function done(stream, er) {
if (er)
return stream.emit('error', er);
// if there's nothing in the write buffer, then that means
// that nothing more will ever be provided
var ws = stream._writableState;
var rs = stream._readableState;
var ts = stream._transformState;
if (ws.length)
throw new Error('calling transform done when ws.length != 0');
if (ts.transforming)
throw new Error('calling transform done when still transforming');
return stream.push(null);
}
@@ -0,0 +1,367 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
// A bit simpler than readable streams.
// Implement an async ._write(chunk, cb), and it'll handle all
// the drain event emission and buffering.
module.exports = Writable;
Writable.WritableState = WritableState;
var util = require('util');
var assert = require('assert');
var Stream = require('stream');
util.inherits(Writable, Stream);
function WriteReq(chunk, encoding, cb) {
this.chunk = chunk;
this.encoding = encoding;
this.callback = cb;
}
function WritableState(options, stream) {
options = options || {};
// the point at which write() starts returning false
// Note: 0 is a valid value, means that we always return false if
// the entire buffer is not flushed immediately on write()
var hwm = options.highWaterMark;
this.highWaterMark = (hwm || hwm === 0) ? hwm : 16 * 1024;
// object stream flag to indicate whether or not this stream
// contains buffers or objects.
this.objectMode = !!options.objectMode;
// cast to ints.
this.highWaterMark = ~~this.highWaterMark;
this.needDrain = false;
// at the start of calling end()
this.ending = false;
// when end() has been called, and returned
this.ended = false;
// when 'finish' is emitted
this.finished = false;
// should we decode strings into buffers before passing to _write?
// this is here so that some node-core streams can optimize string
// handling at a lower level.
var noDecode = options.decodeStrings === false;
this.decodeStrings = !noDecode;
// Crypto is kind of old and crusty. Historically, its default string
// encoding is 'binary' so we have to make this configurable.
// Everything else in the universe uses 'utf8', though.
this.defaultEncoding = options.defaultEncoding || 'utf8';
// not an actual buffer we keep track of, but a measurement
// of how much we're waiting to get pushed to some underlying
// socket or file.
this.length = 0;
// a flag to see when we're in the middle of a write.
this.writing = false;
// a flag to be able to tell if the onwrite cb is called immediately,
// or on a later tick. We set this to true at first, becuase any
// actions that shouldn't happen until "later" should generally also
// not happen before the first write call.
this.sync = true;
// a flag to know if we're processing previously buffered items, which
// may call the _write() callback in the same tick, so that we don't
// end up in an overlapped onwrite situation.
this.bufferProcessing = false;
// the callback that's passed to _write(chunk,cb)
this.onwrite = function(er) {
onwrite(stream, er);
};
// the callback that the user supplies to write(chunk,encoding,cb)
this.writecb = null;
// the amount that is being written when _write is called.
this.writelen = 0;
this.buffer = [];
}
function Writable(options) {
// Writable ctor is applied to Duplexes, though they're not
// instanceof Writable, they're instanceof Readable.
if (!(this instanceof Writable) && !(this instanceof require('./_stream_duplex')))
return new Writable(options);
this._writableState = new WritableState(options, this);
// legacy.
this.writable = true;
Stream.call(this);
}
// Otherwise people can pipe Writable streams, which is just wrong.
Writable.prototype.pipe = function() {
this.emit('error', new Error('Cannot pipe. Not readable.'));
};
function writeAfterEnd(stream, state, cb) {
var er = new Error('write after end');
// TODO: defer error events consistently everywhere, not just the cb
stream.emit('error', er);
process.nextTick(function() {
cb(er);
});
}
// If we get something that is not a buffer, string, null, or undefined,
// and we're not in objectMode, then that's an error.
// Otherwise stream chunks are all considered to be of length=1, and the
// watermarks determine how many objects to keep in the buffer, rather than
// how many bytes or characters.
function validChunk(stream, state, chunk, cb) {
var valid = true;
if (!Buffer.isBuffer(chunk) &&
'string' !== typeof chunk &&
chunk !== null &&
chunk !== undefined &&
!state.objectMode) {
var er = new TypeError('Invalid non-string/buffer chunk');
stream.emit('error', er);
process.nextTick(function() {
cb(er);
});
valid = false;
}
return valid;
}
Writable.prototype.write = function(chunk, encoding, cb) {
var state = this._writableState;
var ret = false;
if (typeof encoding === 'function') {
cb = encoding;
encoding = null;
}
if (Buffer.isBuffer(chunk))
encoding = 'buffer';
else if (!encoding)
encoding = state.defaultEncoding;
if (typeof cb !== 'function')
cb = function() {};
if (state.ended)
writeAfterEnd(this, state, cb);
else if (validChunk(this, state, chunk, cb))
ret = writeOrBuffer(this, state, chunk, encoding, cb);
return ret;
};
function decodeChunk(state, chunk, encoding) {
if (!state.objectMode &&
state.decodeStrings !== false &&
typeof chunk === 'string') {
chunk = new Buffer(chunk, encoding);
}
return chunk;
}
// if we're already writing something, then just put this
// in the queue, and wait our turn. Otherwise, call _write
// If we return false, then we need a drain event, so set that flag.
function writeOrBuffer(stream, state, chunk, encoding, cb) {
chunk = decodeChunk(state, chunk, encoding);
var len = state.objectMode ? 1 : chunk.length;
state.length += len;
var ret = state.length < state.highWaterMark;
state.needDrain = !ret;
if (state.writing)
state.buffer.push(new WriteReq(chunk, encoding, cb));
else
doWrite(stream, state, len, chunk, encoding, cb);
return ret;
}
function doWrite(stream, state, len, chunk, encoding, cb) {
state.writelen = len;
state.writecb = cb;
state.writing = true;
state.sync = true;
stream._write(chunk, encoding, state.onwrite);
state.sync = false;
}
function onwriteError(stream, state, sync, er, cb) {
if (sync)
process.nextTick(function() {
cb(er);
});
else
cb(er);
stream.emit('error', er);
}
function onwriteStateUpdate(state) {
state.writing = false;
state.writecb = null;
state.length -= state.writelen;
state.writelen = 0;
}
function onwrite(stream, er) {
var state = stream._writableState;
var sync = state.sync;
var cb = state.writecb;
onwriteStateUpdate(state);
if (er)
onwriteError(stream, state, sync, er, cb);
else {
// Check if we're actually ready to finish, but don't emit yet
var finished = needFinish(stream, state);
if (!finished && !state.bufferProcessing && state.buffer.length)
clearBuffer(stream, state);
if (sync) {
process.nextTick(function() {
afterWrite(stream, state, finished, cb);
});
} else {
afterWrite(stream, state, finished, cb);
}
}
}
function afterWrite(stream, state, finished, cb) {
if (!finished)
onwriteDrain(stream, state);
cb();
if (finished)
finishMaybe(stream, state);
}
// Must force callback to be called on nextTick, so that we don't
// emit 'drain' before the write() consumer gets the 'false' return
// value, and has a chance to attach a 'drain' listener.
function onwriteDrain(stream, state) {
if (state.length === 0 && state.needDrain) {
state.needDrain = false;
stream.emit('drain');
}
}
// if there's something in the buffer waiting, then process it
function clearBuffer(stream, state) {
state.bufferProcessing = true;
for (var c = 0; c < state.buffer.length; c++) {
var entry = state.buffer[c];
var chunk = entry.chunk;
var encoding = entry.encoding;
var cb = entry.callback;
var len = state.objectMode ? 1 : chunk.length;
doWrite(stream, state, len, chunk, encoding, cb);
// if we didn't call the onwrite immediately, then
// it means that we need to wait until it does.
// also, that means that the chunk and cb are currently
// being processed, so move the buffer counter past them.
if (state.writing) {
c++;
break;
}
}
state.bufferProcessing = false;
if (c < state.buffer.length)
state.buffer = state.buffer.slice(c);
else
state.buffer.length = 0;
}
Writable.prototype._write = function(chunk, encoding, cb) {
cb(new Error('not implemented'));
};
Writable.prototype.end = function(chunk, encoding, cb) {
var state = this._writableState;
if (typeof chunk === 'function') {
cb = chunk;
chunk = null;
encoding = null;
} else if (typeof encoding === 'function') {
cb = encoding;
encoding = null;
}
if (typeof chunk !== 'undefined' && chunk !== null)
this.write(chunk, encoding);
// ignore unnecessary end() calls.
if (!state.ending && !state.finished)
endWritable(this, state, cb);
};
function needFinish(stream, state) {
return (state.ending &&
state.length === 0 &&
!state.finished &&
!state.writing);
}
function finishMaybe(stream, state) {
var need = needFinish(stream, state);
if (need) {
state.finished = true;
stream.emit('finish');
}
return need;
}
function endWritable(stream, state, cb) {
state.ending = true;
finishMaybe(stream, state);
if (cb) {
if (state.finished)
process.nextTick(cb);
else
stream.once('finish', cb);
}
state.ended = true;
}
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
@@ -0,0 +1 @@
module.exports = require("./lib/_stream_passthrough.js")
@@ -0,0 +1,6 @@
exports = module.exports = require('./lib/_stream_readable.js');
exports.Readable = exports;
exports.Writable = require('./lib/_stream_writable.js');
exports.Duplex = require('./lib/_stream_duplex.js');
exports.Transform = require('./lib/_stream_transform.js');
exports.PassThrough = require('./lib/_stream_passthrough.js');
@@ -0,0 +1,191 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
var path = require('path');
var assert = require('assert');
exports.testDir = path.dirname(__filename);
exports.fixturesDir = path.join(exports.testDir, 'fixtures');
exports.libDir = path.join(exports.testDir, '../lib');
exports.tmpDir = path.join(exports.testDir, 'tmp');
exports.PORT = 12346;
if (process.platform === 'win32') {
exports.PIPE = '\\\\.\\pipe\\libuv-test';
} else {
exports.PIPE = exports.tmpDir + '/test.sock';
}
var util = require('util');
for (var i in util) exports[i] = util[i];
//for (var i in exports) global[i] = exports[i];
function protoCtrChain(o) {
var result = [];
for (; o; o = o.__proto__) { result.push(o.constructor); }
return result.join();
}
exports.indirectInstanceOf = function(obj, cls) {
if (obj instanceof cls) { return true; }
var clsChain = protoCtrChain(cls.prototype);
var objChain = protoCtrChain(obj);
return objChain.slice(-clsChain.length) === clsChain;
};
exports.ddCommand = function(filename, kilobytes) {
if (process.platform === 'win32') {
var p = path.resolve(exports.fixturesDir, 'create-file.js');
return '"' + process.argv[0] + '" "' + p + '" "' +
filename + '" ' + (kilobytes * 1024);
} else {
return 'dd if=/dev/zero of="' + filename + '" bs=1024 count=' + kilobytes;
}
};
exports.spawnPwd = function(options) {
var spawn = require('child_process').spawn;
if (process.platform === 'win32') {
return spawn('cmd.exe', ['/c', 'cd'], options);
} else {
return spawn('pwd', [], options);
}
};
// Turn this off if the test should not check for global leaks.
exports.globalCheck = true;
process.on('exit', function() {
if (!exports.globalCheck) return;
var knownGlobals = [setTimeout,
setInterval,
global.setImmediate,
clearTimeout,
clearInterval,
global.clearImmediate,
console,
Buffer,
process,
global];
if (global.errno) {
knownGlobals.push(errno);
}
if (global.gc) {
knownGlobals.push(gc);
}
if (global.DTRACE_HTTP_SERVER_RESPONSE) {
knownGlobals.push(DTRACE_HTTP_SERVER_RESPONSE);
knownGlobals.push(DTRACE_HTTP_SERVER_REQUEST);
knownGlobals.push(DTRACE_HTTP_CLIENT_RESPONSE);
knownGlobals.push(DTRACE_HTTP_CLIENT_REQUEST);
knownGlobals.push(DTRACE_NET_STREAM_END);
knownGlobals.push(DTRACE_NET_SERVER_CONNECTION);
knownGlobals.push(DTRACE_NET_SOCKET_READ);
knownGlobals.push(DTRACE_NET_SOCKET_WRITE);
}
if (global.COUNTER_NET_SERVER_CONNECTION) {
knownGlobals.push(COUNTER_NET_SERVER_CONNECTION);
knownGlobals.push(COUNTER_NET_SERVER_CONNECTION_CLOSE);
knownGlobals.push(COUNTER_HTTP_SERVER_REQUEST);
knownGlobals.push(COUNTER_HTTP_SERVER_RESPONSE);
knownGlobals.push(COUNTER_HTTP_CLIENT_REQUEST);
knownGlobals.push(COUNTER_HTTP_CLIENT_RESPONSE);
}
if (global.ArrayBuffer) {
knownGlobals.push(ArrayBuffer);
knownGlobals.push(Int8Array);
knownGlobals.push(Uint8Array);
knownGlobals.push(Uint8ClampedArray);
knownGlobals.push(Int16Array);
knownGlobals.push(Uint16Array);
knownGlobals.push(Int32Array);
knownGlobals.push(Uint32Array);
knownGlobals.push(Float32Array);
knownGlobals.push(Float64Array);
knownGlobals.push(DataView);
}
for (var x in global) {
var found = false;
for (var y in knownGlobals) {
if (global[x] === knownGlobals[y]) {
found = true;
break;
}
}
if (!found) {
console.error('Unknown global: %s', x);
assert.ok(false, 'Unknown global found');
}
}
});
var mustCallChecks = [];
function runCallChecks() {
var failed = mustCallChecks.filter(function(context) {
return context.actual !== context.expected;
});
failed.forEach(function(context) {
console.log('Mismatched %s function calls. Expected %d, actual %d.',
context.name,
context.expected,
context.actual);
console.log(context.stack.split('\n').slice(2).join('\n'));
});
if (failed.length) process.exit(1);
}
exports.mustCall = function(fn, expected) {
if (typeof expected !== 'number') expected = 1;
var context = {
expected: expected,
actual: 0,
stack: (new Error).stack,
name: fn.name || '<anonymous>'
};
// add the exit listener only once to avoid listener leak warnings
if (mustCallChecks.length === 0) process.on('exit', runCallChecks);
mustCallChecks.push(context);
return function() {
context.actual++;
return fn.apply(this, arguments);
};
};
@@ -0,0 +1 @@
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
@@ -0,0 +1,475 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
var common = require('../common.js');
var R = require('../../lib/_stream_readable');
var assert = require('assert');
var util = require('util');
var EE = require('events').EventEmitter;
function TestReader(n) {
R.apply(this);
this._buffer = new Buffer(n || 100);
this._buffer.fill('x');
this._pos = 0;
this._bufs = 10;
}
util.inherits(TestReader, R);
TestReader.prototype.read = function(n) {
if (n === 0) return null;
var max = this._buffer.length - this._pos;
n = n || max;
n = Math.max(n, 0);
var toRead = Math.min(n, max);
if (toRead === 0) {
// simulate the read buffer filling up with some more bytes some time
// in the future.
setTimeout(function() {
this._pos = 0;
this._bufs -= 1;
if (this._bufs <= 0) {
// read them all!
if (!this.ended) {
this.emit('end');
this.ended = true;
}
} else {
this.emit('readable');
}
}.bind(this), 10);
return null;
}
var ret = this._buffer.slice(this._pos, this._pos + toRead);
this._pos += toRead;
return ret;
};
/////
function TestWriter() {
EE.apply(this);
this.received = [];
this.flush = false;
}
util.inherits(TestWriter, EE);
TestWriter.prototype.write = function(c) {
this.received.push(c.toString());
this.emit('write', c);
return true;
};
TestWriter.prototype.end = function(c) {
if (c) this.write(c);
this.emit('end', this.received);
};
////////
// tiny node-tap lookalike.
var tests = [];
var count = 0;
function test(name, fn) {
count++;
tests.push([name, fn]);
}
function run() {
var next = tests.shift();
if (!next)
return console.error('ok');
var name = next[0];
var fn = next[1];
console.log('# %s', name);
fn({
same: assert.deepEqual,
ok: assert,
equal: assert.equal,
end: function () {
count--;
run();
}
});
}
// ensure all tests have run
process.on("exit", function () {
assert.equal(count, 0);
});
process.nextTick(run);
test('a most basic test', function(t) {
var r = new TestReader(20);
var reads = [];
var expect = [ 'x',
'xx',
'xxx',
'xxxx',
'xxxxx',
'xxxxx',
'xxxxxxxx',
'xxxxxxxxx',
'xxx',
'xxxxxxxxxxxx',
'xxxxxxxx',
'xxxxxxxxxxxxxxx',
'xxxxx',
'xxxxxxxxxxxxxxxxxx',
'xx',
'xxxxxxxxxxxxxxxxxxxx',
'xxxxxxxxxxxxxxxxxxxx',
'xxxxxxxxxxxxxxxxxxxx',
'xxxxxxxxxxxxxxxxxxxx',
'xxxxxxxxxxxxxxxxxxxx' ];
r.on('end', function() {
t.same(reads, expect);
t.end();
});
var readSize = 1;
function flow() {
var res;
while (null !== (res = r.read(readSize++))) {
reads.push(res.toString());
}
r.once('readable', flow);
}
flow();
});
test('pipe', function(t) {
var r = new TestReader(5);
var expect = [ 'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx' ]
var w = new TestWriter;
var flush = true;
w.on('end', function(received) {
t.same(received, expect);
t.end();
});
r.pipe(w);
});
[1,2,3,4,5,6,7,8,9].forEach(function(SPLIT) {
test('unpipe', function(t) {
var r = new TestReader(5);
// unpipe after 3 writes, then write to another stream instead.
var expect = [ 'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx' ];
expect = [ expect.slice(0, SPLIT), expect.slice(SPLIT) ];
var w = [ new TestWriter(), new TestWriter() ];
var writes = SPLIT;
w[0].on('write', function() {
if (--writes === 0) {
r.unpipe();
t.equal(r._readableState.pipes, null);
w[0].end();
r.pipe(w[1]);
t.equal(r._readableState.pipes, w[1]);
}
});
var ended = 0;
var ended0 = false;
var ended1 = false;
w[0].on('end', function(results) {
t.equal(ended0, false);
ended0 = true;
ended++;
t.same(results, expect[0]);
});
w[1].on('end', function(results) {
t.equal(ended1, false);
ended1 = true;
ended++;
t.equal(ended, 2);
t.same(results, expect[1]);
t.end();
});
r.pipe(w[0]);
});
});
// both writers should get the same exact data.
test('multipipe', function(t) {
var r = new TestReader(5);
var w = [ new TestWriter, new TestWriter ];
var expect = [ 'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx' ];
var c = 2;
w[0].on('end', function(received) {
t.same(received, expect, 'first');
if (--c === 0) t.end();
});
w[1].on('end', function(received) {
t.same(received, expect, 'second');
if (--c === 0) t.end();
});
r.pipe(w[0]);
r.pipe(w[1]);
});
[1,2,3,4,5,6,7,8,9].forEach(function(SPLIT) {
test('multi-unpipe', function(t) {
var r = new TestReader(5);
// unpipe after 3 writes, then write to another stream instead.
var expect = [ 'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx',
'xxxxx' ];
expect = [ expect.slice(0, SPLIT), expect.slice(SPLIT) ];
var w = [ new TestWriter(), new TestWriter(), new TestWriter() ];
var writes = SPLIT;
w[0].on('write', function() {
if (--writes === 0) {
r.unpipe();
w[0].end();
r.pipe(w[1]);
}
});
var ended = 0;
w[0].on('end', function(results) {
ended++;
t.same(results, expect[0]);
});
w[1].on('end', function(results) {
ended++;
t.equal(ended, 2);
t.same(results, expect[1]);
t.end();
});
r.pipe(w[0]);
r.pipe(w[2]);
});
});
test('back pressure respected', function (t) {
function noop() {}
var r = new R({ objectMode: true });
r._read = noop;
var counter = 0;
r.push(["one"]);
r.push(["two"]);
r.push(["three"]);
r.push(["four"]);
r.push(null);
var w1 = new R();
w1.write = function (chunk) {
assert.equal(chunk[0], "one");
w1.emit("close");
process.nextTick(function () {
r.pipe(w2);
r.pipe(w3);
})
};
w1.end = noop;
r.pipe(w1);
var expected = ["two", "two", "three", "three", "four", "four"];
var w2 = new R();
w2.write = function (chunk) {
assert.equal(chunk[0], expected.shift());
assert.equal(counter, 0);
counter++;
if (chunk[0] === "four") {
return true;
}
setTimeout(function () {
counter--;
w2.emit("drain");
}, 10);
return false;
}
w2.end = noop;
var w3 = new R();
w3.write = function (chunk) {
assert.equal(chunk[0], expected.shift());
assert.equal(counter, 1);
counter++;
if (chunk[0] === "four") {
return true;
}
setTimeout(function () {
counter--;
w3.emit("drain");
}, 50);
return false;
};
w3.end = function () {
assert.equal(counter, 2);
assert.equal(expected.length, 0);
t.end();
};
});
test('read(0) for ended streams', function (t) {
var r = new R();
var written = false;
var ended = false;
r._read = function (n) {};
r.push(new Buffer("foo"));
r.push(null);
var v = r.read(0);
assert.equal(v, null);
var w = new R();
w.write = function (buffer) {
written = true;
assert.equal(ended, false);
assert.equal(buffer.toString(), "foo")
};
w.end = function () {
ended = true;
assert.equal(written, true);
t.end();
};
r.pipe(w);
})
test('sync _read ending', function (t) {
var r = new R();
var called = false;
r._read = function (n) {
r.push(null);
};
r.once('end', function () {
called = true;
})
r.read();
process.nextTick(function () {
assert.equal(called, true);
t.end();
})
});
test('adding readable triggers data flow', function(t) {
var r = new R({ highWaterMark: 5 });
var onReadable = false;
var readCalled = 0;
r._read = function(n) {
if (readCalled++ === 2)
r.push(null);
else
r.push(new Buffer('asdf'));
};
var called = false;
r.on('readable', function() {
onReadable = true;
r.read();
});
r.on('end', function() {
t.equal(readCalled, 3);
t.ok(onReadable);
t.end();
});
});
@@ -0,0 +1,50 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
var common = require('../common.js');
var R = require('../../lib/_stream_readable');
var assert = require('assert');
var util = require('util');
var EE = require('events').EventEmitter;
var ondataCalled = 0;
function TestReader() {
R.apply(this);
this._buffer = new Buffer(100);
this._buffer.fill('x');
this.on('data', function() {
ondataCalled++;
});
}
util.inherits(TestReader, R);
TestReader.prototype._read = function(n) {
this.push(this._buffer);
this._buffer = new Buffer(0);
};
var reader = new TestReader();
assert.equal(ondataCalled, 1);
@@ -0,0 +1,41 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
var common = require('../common.js');
var stream = require('../../readable');
var Buffer = require('buffer').Buffer;
var r = new stream.Readable();
r._read = function(size) {
r.push(new Buffer(size));
};
var w = new stream.Writable();
w._write = function(data, encoding, cb) {
cb(null);
};
r.pipe(w);
// This might sound unrealistic, but it happens in net.js. When
// `socket.allowHalfOpen === false`, EOF will cause `.destroySoon()` call which
// ends the writable side of net.Socket.
w.end();
@@ -0,0 +1,82 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
var common = require('../common.js');
var assert = require('assert');
// If everything aligns so that you do a read(n) of exactly the
// remaining buffer, then make sure that 'end' still emits.
var READSIZE = 100;
var PUSHSIZE = 20;
var PUSHCOUNT = 1000;
var HWM = 50;
var Readable = require('../../readable').Readable;
var r = new Readable({
highWaterMark: HWM
});
var rs = r._readableState;
r._read = push;
r.on('readable', function() {
console.error('>> readable');
do {
console.error(' > read(%d)', READSIZE);
var ret = r.read(READSIZE);
console.error(' < %j (%d remain)', ret && ret.length, rs.length);
} while (ret && ret.length === READSIZE);
console.error('<< after read()',
ret && ret.length,
rs.needReadable,
rs.length);
});
var endEmitted = false;
r.on('end', function() {
endEmitted = true;
console.error('end');
});
var pushes = 0;
function push() {
if (pushes > PUSHCOUNT)
return;
if (pushes++ === PUSHCOUNT) {
console.error(' push(EOF)');
return r.push(null);
}
console.error(' push #%d', pushes);
if (r.push(new Buffer(PUSHSIZE)))
setTimeout(push);
}
// start the flow
var ret = r.read(0);
process.on('exit', function() {
assert.equal(pushes, PUSHCOUNT + 1);
assert(endEmitted);
});
@@ -0,0 +1,348 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
var common = require('../common.js');
var Readable = require('../../lib/_stream_readable');
var Writable = require('../../lib/_stream_writable');
var assert = require('assert');
// tiny node-tap lookalike.
var tests = [];
var count = 0;
function test(name, fn) {
count++;
tests.push([name, fn]);
}
function run() {
var next = tests.shift();
if (!next)
return console.error('ok');
var name = next[0];
var fn = next[1];
console.log('# %s', name);
fn({
same: assert.deepEqual,
equal: assert.equal,
end: function() {
count--;
run();
}
});
}
// ensure all tests have run
process.on('exit', function() {
assert.equal(count, 0);
});
process.nextTick(run);
function toArray(callback) {
var stream = new Writable({ objectMode: true });
var list = [];
stream.write = function(chunk) {
list.push(chunk);
};
stream.end = function() {
callback(list);
};
return stream;
}
function fromArray(list) {
var r = new Readable({ objectMode: true });
r._read = noop;
list.forEach(function(chunk) {
r.push(chunk);
});
r.push(null);
return r;
}
function noop() {}
test('can read objects from stream', function(t) {
var r = fromArray([{ one: '1'}, { two: '2' }]);
var v1 = r.read();
var v2 = r.read();
var v3 = r.read();
assert.deepEqual(v1, { one: '1' });
assert.deepEqual(v2, { two: '2' });
assert.deepEqual(v3, null);
t.end();
});
test('can pipe objects into stream', function(t) {
var r = fromArray([{ one: '1'}, { two: '2' }]);
r.pipe(toArray(function(list) {
assert.deepEqual(list, [
{ one: '1' },
{ two: '2' }
]);
t.end();
}));
});
test('read(n) is ignored', function(t) {
var r = fromArray([{ one: '1'}, { two: '2' }]);
var value = r.read(2);
assert.deepEqual(value, { one: '1' });
t.end();
});
test('can read objects from _read (sync)', function(t) {
var r = new Readable({ objectMode: true });
var list = [{ one: '1'}, { two: '2' }];
r._read = function(n) {
var item = list.shift();
r.push(item || null);
};
r.pipe(toArray(function(list) {
assert.deepEqual(list, [
{ one: '1' },
{ two: '2' }
]);
t.end();
}));
});
test('can read objects from _read (async)', function(t) {
var r = new Readable({ objectMode: true });
var list = [{ one: '1'}, { two: '2' }];
r._read = function(n) {
var item = list.shift();
process.nextTick(function() {
r.push(item || null);
});
};
r.pipe(toArray(function(list) {
assert.deepEqual(list, [
{ one: '1' },
{ two: '2' }
]);
t.end();
}));
});
test('can read strings as objects', function(t) {
var r = new Readable({
objectMode: true
});
r._read = noop;
var list = ['one', 'two', 'three'];
list.forEach(function(str) {
r.push(str);
});
r.push(null);
r.pipe(toArray(function(array) {
assert.deepEqual(array, list);
t.end();
}));
});
test('read(0) for object streams', function(t) {
var r = new Readable({
objectMode: true
});
r._read = noop;
r.push('foobar');
r.push(null);
var v = r.read(0);
r.pipe(toArray(function(array) {
assert.deepEqual(array, ['foobar']);
t.end();
}));
});
test('falsey values', function(t) {
var r = new Readable({
objectMode: true
});
r._read = noop;
r.push(false);
r.push(0);
r.push('');
r.push(null);
r.pipe(toArray(function(array) {
assert.deepEqual(array, [false, 0, '']);
t.end();
}));
});
test('high watermark _read', function(t) {
var r = new Readable({
highWaterMark: 6,
objectMode: true
});
var calls = 0;
var list = ['1', '2', '3', '4', '5', '6', '7', '8'];
r._read = function(n) {
calls++;
};
list.forEach(function(c) {
r.push(c);
});
var v = r.read();
assert.equal(calls, 0);
assert.equal(v, '1');
var v2 = r.read();
assert.equal(calls, 1);
assert.equal(v2, '2');
t.end();
});
test('high watermark push', function(t) {
var r = new Readable({
highWaterMark: 6,
objectMode: true
});
r._read = function(n) {};
for (var i = 0; i < 6; i++) {
var bool = r.push(i);
assert.equal(bool, i === 5 ? false : true);
}
t.end();
});
test('can write objects to stream', function(t) {
var w = new Writable({ objectMode: true });
w._write = function(chunk, encoding, cb) {
assert.deepEqual(chunk, { foo: 'bar' });
cb();
};
w.on('finish', function() {
t.end();
});
w.write({ foo: 'bar' });
w.end();
});
test('can write multiple objects to stream', function(t) {
var w = new Writable({ objectMode: true });
var list = [];
w._write = function(chunk, encoding, cb) {
list.push(chunk);
cb();
};
w.on('finish', function() {
assert.deepEqual(list, [0, 1, 2, 3, 4]);
t.end();
});
w.write(0);
w.write(1);
w.write(2);
w.write(3);
w.write(4);
w.end();
});
test('can write strings as objects', function(t) {
var w = new Writable({
objectMode: true
});
var list = [];
w._write = function(chunk, encoding, cb) {
list.push(chunk);
process.nextTick(cb);
};
w.on('finish', function() {
assert.deepEqual(list, ['0', '1', '2', '3', '4']);
t.end();
});
w.write('0');
w.write('1');
w.write('2');
w.write('3');
w.write('4');
w.end();
});
test('buffers finish until cb is called', function(t) {
var w = new Writable({
objectMode: true
});
var called = false;
w._write = function(chunk, encoding, cb) {
assert.equal(chunk, 'foo');
process.nextTick(function() {
called = true;
cb();
});
};
w.on('finish', function() {
assert.equal(called, true);
t.end();
});
w.write('foo');
w.end();
});
@@ -0,0 +1,105 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
var common = require('../common');
var assert = require('assert');
var stream = require('../../readable');
(function testErrorListenerCatches() {
var count = 1000;
var source = new stream.Readable();
source._read = function(n) {
n = Math.min(count, n);
count -= n;
source.push(new Buffer(n));
};
var unpipedDest;
source.unpipe = function(dest) {
unpipedDest = dest;
stream.Readable.prototype.unpipe.call(this, dest);
};
var dest = new stream.Writable();
dest._write = function(chunk, encoding, cb) {
cb();
};
source.pipe(dest);
var gotErr = null;
dest.on('error', function(err) {
gotErr = err;
});
var unpipedSource;
dest.on('unpipe', function(src) {
unpipedSource = src;
});
var err = new Error('This stream turned into bacon.');
dest.emit('error', err);
assert.strictEqual(gotErr, err);
assert.strictEqual(unpipedSource, source);
assert.strictEqual(unpipedDest, dest);
})();
(function testErrorWithoutListenerThrows() {
var count = 1000;
var source = new stream.Readable();
source._read = function(n) {
n = Math.min(count, n);
count -= n;
source.push(new Buffer(n));
};
var unpipedDest;
source.unpipe = function(dest) {
unpipedDest = dest;
stream.Readable.prototype.unpipe.call(this, dest);
};
var dest = new stream.Writable();
dest._write = function(chunk, encoding, cb) {
cb();
};
source.pipe(dest);
var unpipedSource;
dest.on('unpipe', function(src) {
unpipedSource = src;
});
var err = new Error('This stream turned into bacon.');
var gotErr = null;
try {
dest.emit('error', err);
} catch (e) {
gotErr = e;
}
assert.strictEqual(gotErr, err);
assert.strictEqual(unpipedSource, source);
assert.strictEqual(unpipedDest, dest);
})();
@@ -0,0 +1,138 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
var common = require('../common.js');
var stream = require('../../readable');
var Readable = stream.Readable;
var Writable = stream.Writable;
var assert = require('assert');
var util = require('util');
var EE = require('events').EventEmitter;
// a mock thing a bit like the net.Socket/tcp_wrap.handle interaction
var stream = new Readable({
highWaterMark: 16,
encoding: 'utf8'
});
var source = new EE;
stream._read = function() {
console.error('stream._read');
readStart();
};
var ended = false;
stream.on('end', function() {
ended = true;
});
source.on('data', function(chunk) {
var ret = stream.push(chunk);
console.error('data', stream._readableState.length);
if (!ret)
readStop();
});
source.on('end', function() {
stream.push(null);
});
var reading = false;
function readStart() {
console.error('readStart');
reading = true;
}
function readStop() {
console.error('readStop');
reading = false;
process.nextTick(function() {
var r = stream.read();
if (r !== null)
writer.write(r);
});
}
var writer = new Writable({
decodeStrings: false
});
var written = [];
var expectWritten =
[ 'asdfgasdfgasdfgasdfg',
'asdfgasdfgasdfgasdfg',
'asdfgasdfgasdfgasdfg',
'asdfgasdfgasdfgasdfg',
'asdfgasdfgasdfgasdfg',
'asdfgasdfgasdfgasdfg' ];
writer._write = function(chunk, encoding, cb) {
console.error('WRITE %s', chunk);
written.push(chunk);
process.nextTick(cb);
};
writer.on('finish', finish);
// now emit some chunks.
var chunk = "asdfg";
var set = 0;
readStart();
data();
function data() {
assert(reading);
source.emit('data', chunk);
assert(reading);
source.emit('data', chunk);
assert(reading);
source.emit('data', chunk);
assert(reading);
source.emit('data', chunk);
assert(!reading);
if (set++ < 5)
setTimeout(data, 10);
else
end();
}
function finish() {
console.error('finish');
assert.deepEqual(written, expectWritten);
console.log('ok');
}
function end() {
source.emit('end');
assert(!reading);
writer.end(stream.read());
setTimeout(function() {
assert(ended);
});
}
@@ -0,0 +1,54 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
var common = require('../common');
var assert = require('assert');
var Readable = require('../../readable').Readable;
var r = new Readable();
var N = 256 * 1024;
// Go ahead and allow the pathological case for this test.
// Yes, it's an infinite loop, that's the point.
process.maxTickDepth = N + 2;
var reads = 0;
r._read = function(n) {
var chunk = reads++ === N ? null : new Buffer(1);
r.push(chunk);
};
r.on('readable', function onReadable() {
if (!(r._readableState.length % 256))
console.error('readable', r._readableState.length);
r.read(N * 2);
});
var ended = false;
r.on('end', function onEnd() {
ended = true;
});
r.read(0);
process.on('exit', function() {
assert(ended);
console.log('ok');
});
@@ -0,0 +1,119 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
var common = require('../common');
var assert = require('assert');
var Readable = require('../../readable').Readable;
test1();
if (!/^v0\.[0-8]\./.test(process.version))
test2();
function test1() {
var r = new Readable();
// should not end when we get a Buffer(0) or '' as the _read result
// that just means that there is *temporarily* no data, but to go
// ahead and try again later.
//
// note that this is very unusual. it only works for crypto streams
// because the other side of the stream will call read(0) to cycle
// data through openssl. that's why we set the timeouts to call
// r.read(0) again later, otherwise there is no more work being done
// and the process just exits.
var buf = new Buffer(5);
buf.fill('x');
var reads = 5;
r._read = function(n) {
switch (reads--) {
case 0:
return r.push(null); // EOF
case 1:
return r.push(buf);
case 2:
setTimeout(r.read.bind(r, 0), 10);
return r.push(new Buffer(0)); // Not-EOF!
case 3:
setTimeout(r.read.bind(r, 0), 10);
return process.nextTick(function() {
return r.push(new Buffer(0));
});
case 4:
setTimeout(r.read.bind(r, 0), 10);
return setTimeout(function() {
return r.push(new Buffer(0));
});
case 5:
return setTimeout(function() {
return r.push(buf);
});
default:
throw new Error('unreachable');
}
};
var results = [];
function flow() {
var chunk;
while (null !== (chunk = r.read()))
results.push(chunk + '');
}
r.on('readable', flow);
r.on('end', function() {
results.push('EOF');
});
flow();
process.on('exit', function() {
assert.deepEqual(results, [ 'xxxxx', 'xxxxx', 'EOF' ]);
console.log('ok');
});
}
function test2() {
var r = new Readable({ encoding: 'base64' });
var reads = 5;
r._read = function(n) {
if (!reads--)
return r.push(null); // EOF
else
return r.push(new Buffer('x'));
};
var results = [];
function flow() {
var chunk;
while (null !== (chunk = r.read()))
results.push(chunk + '');
}
r.on('readable', flow);
r.on('end', function() {
results.push('EOF');
});
flow();
process.on('exit', function() {
assert.deepEqual(results, [ 'eHh4', 'eHg=', 'EOF' ]);
console.log('ok');
});
}
@@ -0,0 +1,120 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
var assert = require('assert');
var common = require('../common.js');
var fromList = require('../../lib/_stream_readable')._fromList;
// tiny node-tap lookalike.
var tests = [];
var count = 0;
function test(name, fn) {
count++;
tests.push([name, fn]);
}
function run() {
var next = tests.shift();
if (!next)
return console.error('ok');
var name = next[0];
var fn = next[1];
console.log('# %s', name);
fn({
same: assert.deepEqual,
equal: assert.equal,
end: function () {
count--;
run();
}
});
}
// ensure all tests have run
process.on("exit", function () {
assert.equal(count, 0);
});
process.nextTick(run);
test('buffers', function(t) {
// have a length
var len = 16;
var list = [ new Buffer('foog'),
new Buffer('bark'),
new Buffer('bazy'),
new Buffer('kuel') ];
// read more than the first element.
var ret = fromList(6, { buffer: list, length: 16 });
t.equal(ret.toString(), 'foogba');
// read exactly the first element.
ret = fromList(2, { buffer: list, length: 10 });
t.equal(ret.toString(), 'rk');
// read less than the first element.
ret = fromList(2, { buffer: list, length: 8 });
t.equal(ret.toString(), 'ba');
// read more than we have.
ret = fromList(100, { buffer: list, length: 6 });
t.equal(ret.toString(), 'zykuel');
// all consumed.
t.same(list, []);
t.end();
});
test('strings', function(t) {
// have a length
var len = 16;
var list = [ 'foog',
'bark',
'bazy',
'kuel' ];
// read more than the first element.
var ret = fromList(6, { buffer: list, length: 16, decoder: true });
t.equal(ret, 'foogba');
// read exactly the first element.
ret = fromList(2, { buffer: list, length: 10, decoder: true });
t.equal(ret, 'rk');
// read less than the first element.
ret = fromList(2, { buffer: list, length: 8, decoder: true });
t.equal(ret, 'ba');
// read more than we have.
ret = fromList(100, { buffer: list, length: 6, decoder: true });
t.equal(ret, 'zykuel');
// all consumed.
t.same(list, []);
t.end();
});
@@ -0,0 +1,75 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
var common = require('../common');
var assert = require('assert');
var Stream = require('../../readable');
var Readable = Stream.Readable;
var r = new Readable();
var N = 256;
var reads = 0;
r._read = function(n) {
return r.push(++reads === N ? null : new Buffer(1));
};
var rended = false;
r.on('end', function() {
rended = true;
});
var w = new Stream();
w.writable = true;
var writes = 0;
var buffered = 0;
w.write = function(c) {
writes += c.length;
buffered += c.length;
process.nextTick(drain);
return false;
};
function drain() {
assert(buffered <= 2);
buffered = 0;
w.emit('drain');
}
var wended = false;
w.end = function() {
wended = true;
};
// Just for kicks, let's mess with the drain count.
// This verifies that even if it gets negative in the
// pipe() cleanup function, we'll still function properly.
r.on('readable', function() {
w.emit('drain');
});
r.pipe(w);
process.on('exit', function() {
assert(rended);
assert(wended);
console.error('ok');
});
@@ -0,0 +1,78 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
var assert = require('assert');
var common = require('../common.js');
var Readable = require('../../lib/_stream_readable');
var len = 0;
var chunks = new Array(10);
for (var i = 1; i <= 10; i++) {
chunks[i-1] = new Buffer(i);
len += i;
}
var test = new Readable();
var n = 0;
test._read = function(size) {
var chunk = chunks[n++];
setTimeout(function() {
test.push(chunk);
});
};
test.on('end', thrower);
function thrower() {
throw new Error('this should not happen!');
}
var bytesread = 0;
test.on('readable', function() {
var b = len - bytesread - 1;
var res = test.read(b);
if (res) {
bytesread += res.length;
console.error('br=%d len=%d', bytesread, len);
setTimeout(next);
}
test.read(0);
});
test.read(0);
function next() {
// now let's make 'end' happen
test.removeListener('end', thrower);
var endEmitted = false;
process.on('exit', function() {
assert(endEmitted, 'end should be emitted by now');
});
test.on('end', function() {
endEmitted = true;
});
// one to get the last byte
var r = test.read();
assert(r);
assert.equal(r.length, 1);
r = test.read();
assert.equal(r, null);
}
@@ -0,0 +1,312 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
var common = require('../common.js');
var assert = require('assert');
var R = require('../../lib/_stream_readable');
var util = require('util');
// tiny node-tap lookalike.
var tests = [];
var count = 0;
function test(name, fn) {
count++;
tests.push([name, fn]);
}
function run() {
var next = tests.shift();
if (!next)
return console.error('ok');
var name = next[0];
var fn = next[1];
console.log('# %s', name);
fn({
same: assert.deepEqual,
equal: assert.equal,
end: function () {
count--;
run();
}
});
}
// ensure all tests have run
process.on("exit", function () {
assert.equal(count, 0);
});
process.nextTick(run);
/////
util.inherits(TestReader, R);
function TestReader(n, opts) {
R.call(this, opts);
this.pos = 0;
this.len = n || 100;
}
TestReader.prototype._read = function(n) {
setTimeout(function() {
if (this.pos >= this.len) {
return this.push(null);
}
n = Math.min(n, this.len - this.pos);
if (n <= 0) {
return this.push(null);
}
this.pos += n;
var ret = new Buffer(n);
ret.fill('a');
console.log("this.push(ret)", ret)
return this.push(ret);
}.bind(this), 1);
};
test('setEncoding utf8', function(t) {
var tr = new TestReader(100);
tr.setEncoding('utf8');
var out = [];
var expect =
[ 'aaaaaaaaaa',
'aaaaaaaaaa',
'aaaaaaaaaa',
'aaaaaaaaaa',
'aaaaaaaaaa',
'aaaaaaaaaa',
'aaaaaaaaaa',
'aaaaaaaaaa',
'aaaaaaaaaa',
'aaaaaaaaaa' ];
tr.on('readable', function flow() {
var chunk;
while (null !== (chunk = tr.read(10)))
out.push(chunk);
});
tr.on('end', function() {
t.same(out, expect);
t.end();
});
// just kick it off.
tr.emit('readable');
});
test('setEncoding hex', function(t) {
var tr = new TestReader(100);
tr.setEncoding('hex');
var out = [];
var expect =
[ '6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161' ];
tr.on('readable', function flow() {
var chunk;
while (null !== (chunk = tr.read(10)))
out.push(chunk);
});
tr.on('end', function() {
t.same(out, expect);
t.end();
});
// just kick it off.
tr.emit('readable');
});
test('setEncoding hex with read(13)', function(t) {
var tr = new TestReader(100);
tr.setEncoding('hex');
var out = [];
var expect =
[ "6161616161616",
"1616161616161",
"6161616161616",
"1616161616161",
"6161616161616",
"1616161616161",
"6161616161616",
"1616161616161",
"6161616161616",
"1616161616161",
"6161616161616",
"1616161616161",
"6161616161616",
"1616161616161",
"6161616161616",
"16161" ];
tr.on('readable', function flow() {
console.log("readable once")
var chunk;
while (null !== (chunk = tr.read(13)))
out.push(chunk);
});
tr.on('end', function() {
console.log("END")
t.same(out, expect);
t.end();
});
// just kick it off.
tr.emit('readable');
});
test('encoding: utf8', function(t) {
var tr = new TestReader(100, { encoding: 'utf8' });
var out = [];
var expect =
[ 'aaaaaaaaaa',
'aaaaaaaaaa',
'aaaaaaaaaa',
'aaaaaaaaaa',
'aaaaaaaaaa',
'aaaaaaaaaa',
'aaaaaaaaaa',
'aaaaaaaaaa',
'aaaaaaaaaa',
'aaaaaaaaaa' ];
tr.on('readable', function flow() {
var chunk;
while (null !== (chunk = tr.read(10)))
out.push(chunk);
});
tr.on('end', function() {
t.same(out, expect);
t.end();
});
// just kick it off.
tr.emit('readable');
});
test('encoding: hex', function(t) {
var tr = new TestReader(100, { encoding: 'hex' });
var out = [];
var expect =
[ '6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161',
'6161616161' ];
tr.on('readable', function flow() {
var chunk;
while (null !== (chunk = tr.read(10)))
out.push(chunk);
});
tr.on('end', function() {
t.same(out, expect);
t.end();
});
// just kick it off.
tr.emit('readable');
});
test('encoding: hex with read(13)', function(t) {
var tr = new TestReader(100, { encoding: 'hex' });
var out = [];
var expect =
[ "6161616161616",
"1616161616161",
"6161616161616",
"1616161616161",
"6161616161616",
"1616161616161",
"6161616161616",
"1616161616161",
"6161616161616",
"1616161616161",
"6161616161616",
"1616161616161",
"6161616161616",
"1616161616161",
"6161616161616",
"16161" ];
tr.on('readable', function flow() {
var chunk;
while (null !== (chunk = tr.read(13)))
out.push(chunk);
});
tr.on('end', function() {
t.same(out, expect);
t.end();
});
// just kick it off.
tr.emit('readable');
});
@@ -0,0 +1,435 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
var assert = require('assert');
var common = require('../common.js');
var PassThrough = require('../../lib/_stream_passthrough');
var Transform = require('../../lib/_stream_transform');
// tiny node-tap lookalike.
var tests = [];
var count = 0;
function test(name, fn) {
count++;
tests.push([name, fn]);
}
function run() {
var next = tests.shift();
if (!next)
return console.error('ok');
var name = next[0];
var fn = next[1];
console.log('# %s', name);
fn({
same: assert.deepEqual,
equal: assert.equal,
ok: assert,
end: function () {
count--;
run();
}
});
}
// ensure all tests have run
process.on("exit", function () {
assert.equal(count, 0);
});
process.nextTick(run);
/////
test('writable side consumption', function(t) {
var tx = new Transform({
highWaterMark: 10
});
var transformed = 0;
tx._transform = function(chunk, encoding, cb) {
transformed += chunk.length;
tx.push(chunk);
cb();
};
for (var i = 1; i <= 10; i++) {
tx.write(new Buffer(i));
}
tx.end();
t.equal(tx._readableState.length, 10);
t.equal(transformed, 10);
t.equal(tx._transformState.writechunk.length, 5);
t.same(tx._writableState.buffer.map(function(c) {
return c.chunk.length;
}), [6, 7, 8, 9, 10]);
t.end();
});
test('passthrough', function(t) {
var pt = new PassThrough();
pt.write(new Buffer('foog'));
pt.write(new Buffer('bark'));
pt.write(new Buffer('bazy'));
pt.write(new Buffer('kuel'));
pt.end();
t.equal(pt.read(5).toString(), 'foogb');
t.equal(pt.read(5).toString(), 'arkba');
t.equal(pt.read(5).toString(), 'zykue');
t.equal(pt.read(5).toString(), 'l');
t.end();
});
test('simple transform', function(t) {
var pt = new Transform;
pt._transform = function(c, e, cb) {
var ret = new Buffer(c.length);
ret.fill('x');
pt.push(ret);
cb();
};
pt.write(new Buffer('foog'));
pt.write(new Buffer('bark'));
pt.write(new Buffer('bazy'));
pt.write(new Buffer('kuel'));
pt.end();
t.equal(pt.read(5).toString(), 'xxxxx');
t.equal(pt.read(5).toString(), 'xxxxx');
t.equal(pt.read(5).toString(), 'xxxxx');
t.equal(pt.read(5).toString(), 'x');
t.end();
});
test('async passthrough', function(t) {
var pt = new Transform;
pt._transform = function(chunk, encoding, cb) {
setTimeout(function() {
pt.push(chunk);
cb();
}, 10);
};
pt.write(new Buffer('foog'));
pt.write(new Buffer('bark'));
pt.write(new Buffer('bazy'));
pt.write(new Buffer('kuel'));
pt.end();
pt.on('finish', function() {
t.equal(pt.read(5).toString(), 'foogb');
t.equal(pt.read(5).toString(), 'arkba');
t.equal(pt.read(5).toString(), 'zykue');
t.equal(pt.read(5).toString(), 'l');
t.end();
});
});
test('assymetric transform (expand)', function(t) {
var pt = new Transform;
// emit each chunk 2 times.
pt._transform = function(chunk, encoding, cb) {
setTimeout(function() {
pt.push(chunk);
setTimeout(function() {
pt.push(chunk);
cb();
}, 10)
}, 10);
};
pt.write(new Buffer('foog'));
pt.write(new Buffer('bark'));
pt.write(new Buffer('bazy'));
pt.write(new Buffer('kuel'));
pt.end();
pt.on('finish', function() {
t.equal(pt.read(5).toString(), 'foogf');
t.equal(pt.read(5).toString(), 'oogba');
t.equal(pt.read(5).toString(), 'rkbar');
t.equal(pt.read(5).toString(), 'kbazy');
t.equal(pt.read(5).toString(), 'bazyk');
t.equal(pt.read(5).toString(), 'uelku');
t.equal(pt.read(5).toString(), 'el');
t.end();
});
});
test('assymetric transform (compress)', function(t) {
var pt = new Transform;
// each output is the first char of 3 consecutive chunks,
// or whatever's left.
pt.state = '';
pt._transform = function(chunk, encoding, cb) {
if (!chunk)
chunk = '';
var s = chunk.toString();
setTimeout(function() {
this.state += s.charAt(0);
if (this.state.length === 3) {
pt.push(new Buffer(this.state));
this.state = '';
}
cb();
}.bind(this), 10);
};
pt._flush = function(cb) {
// just output whatever we have.
pt.push(new Buffer(this.state));
this.state = '';
cb();
};
pt.write(new Buffer('aaaa'));
pt.write(new Buffer('bbbb'));
pt.write(new Buffer('cccc'));
pt.write(new Buffer('dddd'));
pt.write(new Buffer('eeee'));
pt.write(new Buffer('aaaa'));
pt.write(new Buffer('bbbb'));
pt.write(new Buffer('cccc'));
pt.write(new Buffer('dddd'));
pt.write(new Buffer('eeee'));
pt.write(new Buffer('aaaa'));
pt.write(new Buffer('bbbb'));
pt.write(new Buffer('cccc'));
pt.write(new Buffer('dddd'));
pt.end();
// 'abcdeabcdeabcd'
pt.on('finish', function() {
t.equal(pt.read(5).toString(), 'abcde');
t.equal(pt.read(5).toString(), 'abcde');
t.equal(pt.read(5).toString(), 'abcd');
t.end();
});
});
test('passthrough event emission', function(t) {
var pt = new PassThrough();
var emits = 0;
pt.on('readable', function() {
var state = pt._readableState;
console.error('>>> emit readable %d', emits);
emits++;
});
var i = 0;
pt.write(new Buffer('foog'));
console.error('need emit 0');
pt.write(new Buffer('bark'));
console.error('should have emitted readable now 1 === %d', emits);
t.equal(emits, 1);
t.equal(pt.read(5).toString(), 'foogb');
t.equal(pt.read(5) + '', 'null');
console.error('need emit 1');
pt.write(new Buffer('bazy'));
console.error('should have emitted, but not again');
pt.write(new Buffer('kuel'));
console.error('should have emitted readable now 2 === %d', emits);
t.equal(emits, 2);
t.equal(pt.read(5).toString(), 'arkba');
t.equal(pt.read(5).toString(), 'zykue');
t.equal(pt.read(5), null);
console.error('need emit 2');
pt.end();
t.equal(emits, 3);
t.equal(pt.read(5).toString(), 'l');
t.equal(pt.read(5), null);
console.error('should not have emitted again');
t.equal(emits, 3);
t.end();
});
test('passthrough event emission reordered', function(t) {
var pt = new PassThrough;
var emits = 0;
pt.on('readable', function() {
console.error('emit readable', emits)
emits++;
});
pt.write(new Buffer('foog'));
console.error('need emit 0');
pt.write(new Buffer('bark'));
console.error('should have emitted readable now 1 === %d', emits);
t.equal(emits, 1);
t.equal(pt.read(5).toString(), 'foogb');
t.equal(pt.read(5), null);
console.error('need emit 1');
pt.once('readable', function() {
t.equal(pt.read(5).toString(), 'arkba');
t.equal(pt.read(5), null);
console.error('need emit 2');
pt.once('readable', function() {
t.equal(pt.read(5).toString(), 'zykue');
t.equal(pt.read(5), null);
pt.once('readable', function() {
t.equal(pt.read(5).toString(), 'l');
t.equal(pt.read(5), null);
t.equal(emits, 4);
t.end();
});
pt.end();
});
pt.write(new Buffer('kuel'));
});
pt.write(new Buffer('bazy'));
});
test('passthrough facaded', function(t) {
console.error('passthrough facaded');
var pt = new PassThrough;
var datas = [];
pt.on('data', function(chunk) {
datas.push(chunk.toString());
});
pt.on('end', function() {
t.same(datas, ['foog', 'bark', 'bazy', 'kuel']);
t.end();
});
pt.write(new Buffer('foog'));
setTimeout(function() {
pt.write(new Buffer('bark'));
setTimeout(function() {
pt.write(new Buffer('bazy'));
setTimeout(function() {
pt.write(new Buffer('kuel'));
setTimeout(function() {
pt.end();
}, 10);
}, 10);
}, 10);
}, 10);
});
test('object transform (json parse)', function(t) {
console.error('json parse stream');
var jp = new Transform({ objectMode: true });
jp._transform = function(data, encoding, cb) {
try {
jp.push(JSON.parse(data));
cb();
} catch (er) {
cb(er);
}
};
// anything except null/undefined is fine.
// those are "magic" in the stream API, because they signal EOF.
var objects = [
{ foo: 'bar' },
100,
"string",
{ nested: { things: [ { foo: 'bar' }, 100, "string" ] } }
];
var ended = false;
jp.on('end', function() {
ended = true;
});
objects.forEach(function(obj) {
jp.write(JSON.stringify(obj));
var res = jp.read();
t.same(res, obj);
});
jp.end();
process.nextTick(function() {
t.ok(ended);
t.end();
})
});
test('object transform (json stringify)', function(t) {
console.error('json parse stream');
var js = new Transform({ objectMode: true });
js._transform = function(data, encoding, cb) {
try {
js.push(JSON.stringify(data));
cb();
} catch (er) {
cb(er);
}
};
// anything except null/undefined is fine.
// those are "magic" in the stream API, because they signal EOF.
var objects = [
{ foo: 'bar' },
100,
"string",
{ nested: { things: [ { foo: 'bar' }, 100, "string" ] } }
];
var ended = false;
js.on('end', function() {
ended = true;
});
objects.forEach(function(obj) {
js.write(obj);
var res = js.read();
t.equal(res, JSON.stringify(obj));
});
js.end();
process.nextTick(function() {
t.ok(ended);
t.end();
})
});
@@ -0,0 +1,76 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
var common = require('../common.js');
var assert = require('assert');
var stream = require('../../readable');
var crypto = require('crypto');
var util = require('util');
function TestWriter() {
stream.Writable.call(this);
}
util.inherits(TestWriter, stream.Writable);
TestWriter.prototype._write = function (buffer, encoding, callback) {
console.log('write called');
// super slow write stream (callback never called)
};
var dest = new TestWriter();
function TestReader(id) {
stream.Readable.call(this);
this.reads = 0;
}
util.inherits(TestReader, stream.Readable);
TestReader.prototype._read = function (size) {
this.reads += 1;
this.push(crypto.randomBytes(size));
};
var src1 = new TestReader();
var src2 = new TestReader();
src1.pipe(dest);
src1.once('readable', function () {
process.nextTick(function () {
src2.pipe(dest);
src2.once('readable', function () {
process.nextTick(function () {
src1.unpipe(dest);
});
});
});
});
process.on('exit', function () {
assert.equal(src1.reads, 2);
assert.equal(src2.reads, 2);
});
@@ -0,0 +1,74 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
var common = require('../common.js');
var assert = require('assert');
var stream = require('../../readable');
var chunk = new Buffer('hallo');
var util = require('util');
function TestWriter() {
stream.Writable.call(this);
}
util.inherits(TestWriter, stream.Writable);
TestWriter.prototype._write = function(buffer, encoding, callback) {
callback(null);
};
var dest = new TestWriter();
// Set this high so that we'd trigger a nextTick warning
// and/or RangeError if we do maybeReadMore wrong.
function TestReader() {
stream.Readable.call(this, { highWaterMark: 0x10000 });
}
util.inherits(TestReader, stream.Readable);
TestReader.prototype._read = function(size) {
this.push(chunk);
};
var src = new TestReader();
for (var i = 0; i < 10; i++) {
src.pipe(dest);
src.unpipe(dest);
}
assert.equal(src.listeners('end').length, 0);
assert.equal(src.listeners('readable').length, 0);
assert.equal(dest.listeners('unpipe').length, 0);
assert.equal(dest.listeners('drain').length, 0);
assert.equal(dest.listeners('error').length, 0);
assert.equal(dest.listeners('close').length, 0);
assert.equal(dest.listeners('finish').length, 0);
console.error(src._readableState);
process.on('exit', function() {
assert(src._readableState.length >= src._readableState.highWaterMark);
src._readableState.buffer.length = 0;
console.error(src._readableState);
});
@@ -0,0 +1,328 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
var common = require('../common.js');
var W = require('../../lib/_stream_writable');
var D = require('../../lib/_stream_duplex');
var assert = require('assert');
var util = require('util');
util.inherits(TestWriter, W);
function TestWriter() {
W.apply(this, arguments);
this.buffer = [];
this.written = 0;
}
TestWriter.prototype._write = function(chunk, encoding, cb) {
// simulate a small unpredictable latency
setTimeout(function() {
this.buffer.push(chunk.toString());
this.written += chunk.length;
cb();
}.bind(this), Math.floor(Math.random() * 10));
};
var chunks = new Array(50);
for (var i = 0; i < chunks.length; i++) {
chunks[i] = new Array(i + 1).join('x');
}
// tiny node-tap lookalike.
var tests = [];
var count = 0;
function test(name, fn) {
count++;
tests.push([name, fn]);
}
function run() {
var next = tests.shift();
if (!next)
return console.error('ok');
var name = next[0];
var fn = next[1];
console.log('# %s', name);
fn({
same: assert.deepEqual,
equal: assert.equal,
end: function () {
count--;
run();
}
});
}
// ensure all tests have run
process.on("exit", function () {
assert.equal(count, 0);
});
process.nextTick(run);
test('write fast', function(t) {
var tw = new TestWriter({
highWaterMark: 100
});
tw.on('finish', function() {
t.same(tw.buffer, chunks, 'got chunks in the right order');
t.end();
});
chunks.forEach(function(chunk) {
// screw backpressure. Just buffer it all up.
tw.write(chunk);
});
tw.end();
});
test('write slow', function(t) {
var tw = new TestWriter({
highWaterMark: 100
});
tw.on('finish', function() {
t.same(tw.buffer, chunks, 'got chunks in the right order');
t.end();
});
var i = 0;
(function W() {
tw.write(chunks[i++]);
if (i < chunks.length)
setTimeout(W, 10);
else
tw.end();
})();
});
test('write backpressure', function(t) {
var tw = new TestWriter({
highWaterMark: 50
});
var drains = 0;
tw.on('finish', function() {
t.same(tw.buffer, chunks, 'got chunks in the right order');
t.equal(drains, 17);
t.end();
});
tw.on('drain', function() {
drains++;
});
var i = 0;
(function W() {
do {
var ret = tw.write(chunks[i++]);
} while (ret !== false && i < chunks.length);
if (i < chunks.length) {
assert(tw._writableState.length >= 50);
tw.once('drain', W);
} else {
tw.end();
}
})();
});
test('write bufferize', function(t) {
var tw = new TestWriter({
highWaterMark: 100
});
var encodings =
[ 'hex',
'utf8',
'utf-8',
'ascii',
'binary',
'base64',
'ucs2',
'ucs-2',
'utf16le',
'utf-16le',
undefined ];
tw.on('finish', function() {
t.same(tw.buffer, chunks, 'got the expected chunks');
});
chunks.forEach(function(chunk, i) {
var enc = encodings[ i % encodings.length ];
chunk = new Buffer(chunk);
tw.write(chunk.toString(enc), enc);
});
t.end();
});
test('write no bufferize', function(t) {
var tw = new TestWriter({
highWaterMark: 100,
decodeStrings: false
});
tw._write = function(chunk, encoding, cb) {
assert(typeof chunk === 'string');
chunk = new Buffer(chunk, encoding);
return TestWriter.prototype._write.call(this, chunk, encoding, cb);
};
var encodings =
[ 'hex',
'utf8',
'utf-8',
'ascii',
'binary',
'base64',
'ucs2',
'ucs-2',
'utf16le',
'utf-16le',
undefined ];
tw.on('finish', function() {
t.same(tw.buffer, chunks, 'got the expected chunks');
});
chunks.forEach(function(chunk, i) {
var enc = encodings[ i % encodings.length ];
chunk = new Buffer(chunk);
tw.write(chunk.toString(enc), enc);
});
t.end();
});
test('write callbacks', function (t) {
var callbacks = chunks.map(function(chunk, i) {
return [i, function(er) {
callbacks._called[i] = chunk;
}];
}).reduce(function(set, x) {
set['callback-' + x[0]] = x[1];
return set;
}, {});
callbacks._called = [];
var tw = new TestWriter({
highWaterMark: 100
});
tw.on('finish', function() {
process.nextTick(function() {
t.same(tw.buffer, chunks, 'got chunks in the right order');
t.same(callbacks._called, chunks, 'called all callbacks');
t.end();
});
});
chunks.forEach(function(chunk, i) {
tw.write(chunk, callbacks['callback-' + i]);
});
tw.end();
});
test('end callback', function (t) {
var tw = new TestWriter();
tw.end(function () {
t.end();
});
});
test('end callback with chunk', function (t) {
var tw = new TestWriter();
tw.end(new Buffer('hello world'), function () {
t.end();
});
});
test('end callback with chunk and encoding', function (t) {
var tw = new TestWriter();
tw.end('hello world', 'ascii', function () {
t.end();
});
});
test('end callback after .write() call', function (t) {
var tw = new TestWriter();
tw.write(new Buffer('hello world'));
tw.end(function () {
t.end();
});
});
test('encoding should be ignored for buffers', function(t) {
var tw = new W();
var hex = '018b5e9a8f6236ffe30e31baf80d2cf6eb';
tw._write = function(chunk, encoding, cb) {
t.equal(chunk.toString('hex'), hex);
t.end();
};
var buf = new Buffer(hex, 'hex');
tw.write(buf, 'binary');
});
test('writables are not pipable', function(t) {
var w = new W();
w._write = function() {};
var gotError = false;
w.on('error', function(er) {
gotError = true;
});
w.pipe(process.stdout);
assert(gotError);
t.end();
});
test('duplexes are pipable', function(t) {
var d = new D();
d._read = function() {};
d._write = function() {};
var gotError = false;
d.on('error', function(er) {
gotError = true;
});
d.pipe(process.stdout);
assert(!gotError);
t.end();
});
test('end(chunk) two times is an error', function(t) {
var w = new W();
w._write = function() {};
var gotError = false;
w.on('error', function(er) {
gotError = true;
t.equal(er.message, 'write after end');
});
w.end('this is the end');
w.end('and so is this');
process.nextTick(function() {
assert(gotError);
t.end();
});
});
@@ -0,0 +1 @@
module.exports = require("./lib/_stream_transform.js")
@@ -0,0 +1 @@
module.exports = require("./lib/_stream_writable.js")
@@ -0,0 +1,452 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
var Transform = require('./lib/_stream_transform.js');
var binding = process.binding('zlib');
var util = require('util');
var assert = require('assert').ok;
// zlib doesn't provide these, so kludge them in following the same
// const naming scheme zlib uses.
binding.Z_MIN_WINDOWBITS = 8;
binding.Z_MAX_WINDOWBITS = 15;
binding.Z_DEFAULT_WINDOWBITS = 15;
// fewer than 64 bytes per chunk is stupid.
// technically it could work with as few as 8, but even 64 bytes
// is absurdly low. Usually a MB or more is best.
binding.Z_MIN_CHUNK = 64;
binding.Z_MAX_CHUNK = Infinity;
binding.Z_DEFAULT_CHUNK = (16 * 1024);
binding.Z_MIN_MEMLEVEL = 1;
binding.Z_MAX_MEMLEVEL = 9;
binding.Z_DEFAULT_MEMLEVEL = 8;
binding.Z_MIN_LEVEL = -1;
binding.Z_MAX_LEVEL = 9;
binding.Z_DEFAULT_LEVEL = binding.Z_DEFAULT_COMPRESSION;
// expose all the zlib constants
Object.keys(binding).forEach(function(k) {
if (k.match(/^Z/)) exports[k] = binding[k];
});
// translation table for return codes.
exports.codes = {
Z_OK: binding.Z_OK,
Z_STREAM_END: binding.Z_STREAM_END,
Z_NEED_DICT: binding.Z_NEED_DICT,
Z_ERRNO: binding.Z_ERRNO,
Z_STREAM_ERROR: binding.Z_STREAM_ERROR,
Z_DATA_ERROR: binding.Z_DATA_ERROR,
Z_MEM_ERROR: binding.Z_MEM_ERROR,
Z_BUF_ERROR: binding.Z_BUF_ERROR,
Z_VERSION_ERROR: binding.Z_VERSION_ERROR
};
Object.keys(exports.codes).forEach(function(k) {
exports.codes[exports.codes[k]] = k;
});
exports.Deflate = Deflate;
exports.Inflate = Inflate;
exports.Gzip = Gzip;
exports.Gunzip = Gunzip;
exports.DeflateRaw = DeflateRaw;
exports.InflateRaw = InflateRaw;
exports.Unzip = Unzip;
exports.createDeflate = function(o) {
return new Deflate(o);
};
exports.createInflate = function(o) {
return new Inflate(o);
};
exports.createDeflateRaw = function(o) {
return new DeflateRaw(o);
};
exports.createInflateRaw = function(o) {
return new InflateRaw(o);
};
exports.createGzip = function(o) {
return new Gzip(o);
};
exports.createGunzip = function(o) {
return new Gunzip(o);
};
exports.createUnzip = function(o) {
return new Unzip(o);
};
// Convenience methods.
// compress/decompress a string or buffer in one step.
exports.deflate = function(buffer, callback) {
zlibBuffer(new Deflate(), buffer, callback);
};
exports.gzip = function(buffer, callback) {
zlibBuffer(new Gzip(), buffer, callback);
};
exports.deflateRaw = function(buffer, callback) {
zlibBuffer(new DeflateRaw(), buffer, callback);
};
exports.unzip = function(buffer, callback) {
zlibBuffer(new Unzip(), buffer, callback);
};
exports.inflate = function(buffer, callback) {
zlibBuffer(new Inflate(), buffer, callback);
};
exports.gunzip = function(buffer, callback) {
zlibBuffer(new Gunzip(), buffer, callback);
};
exports.inflateRaw = function(buffer, callback) {
zlibBuffer(new InflateRaw(), buffer, callback);
};
function zlibBuffer(engine, buffer, callback) {
var buffers = [];
var nread = 0;
engine.on('error', onError);
engine.on('end', onEnd);
engine.end(buffer);
flow();
function flow() {
var chunk;
while (null !== (chunk = engine.read())) {
buffers.push(chunk);
nread += chunk.length;
}
engine.once('readable', flow);
}
function onError(err) {
engine.removeListener('end', onEnd);
engine.removeListener('readable', flow);
callback(err);
}
function onEnd() {
var buf = Buffer.concat(buffers, nread);
buffers = [];
callback(null, buf);
}
}
// generic zlib
// minimal 2-byte header
function Deflate(opts) {
if (!(this instanceof Deflate)) return new Deflate(opts);
Zlib.call(this, opts, binding.DEFLATE);
}
function Inflate(opts) {
if (!(this instanceof Inflate)) return new Inflate(opts);
Zlib.call(this, opts, binding.INFLATE);
}
// gzip - bigger header, same deflate compression
function Gzip(opts) {
if (!(this instanceof Gzip)) return new Gzip(opts);
Zlib.call(this, opts, binding.GZIP);
}
function Gunzip(opts) {
if (!(this instanceof Gunzip)) return new Gunzip(opts);
Zlib.call(this, opts, binding.GUNZIP);
}
// raw - no header
function DeflateRaw(opts) {
if (!(this instanceof DeflateRaw)) return new DeflateRaw(opts);
Zlib.call(this, opts, binding.DEFLATERAW);
}
function InflateRaw(opts) {
if (!(this instanceof InflateRaw)) return new InflateRaw(opts);
Zlib.call(this, opts, binding.INFLATERAW);
}
// auto-detect header.
function Unzip(opts) {
if (!(this instanceof Unzip)) return new Unzip(opts);
Zlib.call(this, opts, binding.UNZIP);
}
// the Zlib class they all inherit from
// This thing manages the queue of requests, and returns
// true or false if there is anything in the queue when
// you call the .write() method.
function Zlib(opts, mode) {
this._opts = opts = opts || {};
this._chunkSize = opts.chunkSize || exports.Z_DEFAULT_CHUNK;
Transform.call(this, opts);
// means a different thing there.
this._readableState.chunkSize = null;
if (opts.chunkSize) {
if (opts.chunkSize < exports.Z_MIN_CHUNK ||
opts.chunkSize > exports.Z_MAX_CHUNK) {
throw new Error('Invalid chunk size: ' + opts.chunkSize);
}
}
if (opts.windowBits) {
if (opts.windowBits < exports.Z_MIN_WINDOWBITS ||
opts.windowBits > exports.Z_MAX_WINDOWBITS) {
throw new Error('Invalid windowBits: ' + opts.windowBits);
}
}
if (opts.level) {
if (opts.level < exports.Z_MIN_LEVEL ||
opts.level > exports.Z_MAX_LEVEL) {
throw new Error('Invalid compression level: ' + opts.level);
}
}
if (opts.memLevel) {
if (opts.memLevel < exports.Z_MIN_MEMLEVEL ||
opts.memLevel > exports.Z_MAX_MEMLEVEL) {
throw new Error('Invalid memLevel: ' + opts.memLevel);
}
}
if (opts.strategy) {
if (opts.strategy != exports.Z_FILTERED &&
opts.strategy != exports.Z_HUFFMAN_ONLY &&
opts.strategy != exports.Z_RLE &&
opts.strategy != exports.Z_FIXED &&
opts.strategy != exports.Z_DEFAULT_STRATEGY) {
throw new Error('Invalid strategy: ' + opts.strategy);
}
}
if (opts.dictionary) {
if (!Buffer.isBuffer(opts.dictionary)) {
throw new Error('Invalid dictionary: it should be a Buffer instance');
}
}
this._binding = new binding.Zlib(mode);
var self = this;
this._hadError = false;
this._binding.onerror = function(message, errno) {
// there is no way to cleanly recover.
// continuing only obscures problems.
self._binding = null;
self._hadError = true;
var error = new Error(message);
error.errno = errno;
error.code = exports.codes[errno];
self.emit('error', error);
};
this._binding.init(opts.windowBits || exports.Z_DEFAULT_WINDOWBITS,
opts.level || exports.Z_DEFAULT_COMPRESSION,
opts.memLevel || exports.Z_DEFAULT_MEMLEVEL,
opts.strategy || exports.Z_DEFAULT_STRATEGY,
opts.dictionary);
this._buffer = new Buffer(this._chunkSize);
this._offset = 0;
this._closed = false;
this.once('end', this.close);
}
util.inherits(Zlib, Transform);
Zlib.prototype.reset = function reset() {
return this._binding.reset();
};
Zlib.prototype._flush = function(output, callback) {
var rs = this._readableState;
var self = this;
this._transform(null, output, function(er) {
if (er)
return callback(er);
// now a weird thing happens... it could be that you called flush
// but everything had already actually been consumed, but it wasn't
// enough to get over the Readable class's lowWaterMark.
// In that case, we emit 'readable' now to make sure it's consumed.
if (rs.length &&
rs.length < rs.lowWaterMark &&
!rs.ended &&
rs.needReadable)
self.emit('readable');
callback();
});
};
Zlib.prototype.flush = function(callback) {
var ws = this._writableState;
var ts = this._transformState;
if (ws.writing) {
ws.needDrain = true;
var self = this;
this.once('drain', function() {
self._flush(ts.output, callback);
});
return;
}
this._flush(ts.output, callback || function() {});
};
Zlib.prototype.close = function(callback) {
if (callback)
process.nextTick(callback);
if (this._closed)
return;
this._closed = true;
this._binding.close();
var self = this;
process.nextTick(function() {
self.emit('close');
});
};
Zlib.prototype._transform = function(chunk, output, cb) {
var flushFlag;
var ws = this._writableState;
var ending = ws.ending || ws.ended;
var last = ending && (!chunk || ws.length === chunk.length);
if (chunk !== null && !Buffer.isBuffer(chunk))
return cb(new Error('invalid input'));
// If it's the last chunk, or a final flush, we use the Z_FINISH flush flag.
// If it's explicitly flushing at some other time, then we use
// Z_FULL_FLUSH. Otherwise, use Z_NO_FLUSH for maximum compression
// goodness.
if (last)
flushFlag = binding.Z_FINISH;
else if (chunk === null)
flushFlag = binding.Z_FULL_FLUSH;
else
flushFlag = binding.Z_NO_FLUSH;
var availInBefore = chunk && chunk.length;
var availOutBefore = this._chunkSize - this._offset;
var inOff = 0;
var req = this._binding.write(flushFlag,
chunk, // in
inOff, // in_off
availInBefore, // in_len
this._buffer, // out
this._offset, //out_off
availOutBefore); // out_len
req.buffer = chunk;
req.callback = callback;
var self = this;
function callback(availInAfter, availOutAfter, buffer) {
if (self._hadError)
return;
var have = availOutBefore - availOutAfter;
assert(have >= 0, 'have should not go down');
if (have > 0) {
var out = self._buffer.slice(self._offset, self._offset + have);
self._offset += have;
// serve some output to the consumer.
output(out);
}
// exhausted the output buffer, or used all the input create a new one.
if (availOutAfter === 0 || self._offset >= self._chunkSize) {
availOutBefore = self._chunkSize;
self._offset = 0;
self._buffer = new Buffer(self._chunkSize);
}
if (availOutAfter === 0) {
// Not actually done. Need to reprocess.
// Also, update the availInBefore to the availInAfter value,
// so that if we have to hit it a third (fourth, etc.) time,
// it'll have the correct byte counts.
inOff += (availInBefore - availInAfter);
availInBefore = availInAfter;
var newReq = self._binding.write(flushFlag,
chunk,
inOff,
availInBefore,
self._buffer,
self._offset,
self._chunkSize);
newReq.callback = callback; // this same function
newReq.buffer = chunk;
return;
}
// finished with the chunk.
cb();
}
};
util.inherits(Deflate, Zlib);
util.inherits(Inflate, Zlib);
util.inherits(Gzip, Zlib);
util.inherits(Gunzip, Zlib);
util.inherits(DeflateRaw, Zlib);
util.inherits(InflateRaw, Zlib);
util.inherits(Unzip, Zlib);
@@ -0,0 +1 @@
/node_modules
@@ -0,0 +1,14 @@
# stream-counter
Keep track of how many bytes have been written to a stream.
## Usage
```js
var StreamCounter = require('stream-counter');
var counter = new StreamCounter();
counter.on('progress', function() {
console.log("progress", counter.bytes);
});
fs.createReadStream('foo.txt').pipe(counter);
```
@@ -0,0 +1,16 @@
module.exports = ByteCounter;
var Writable = require('readable-stream').Writable;
var util = require('util');
util.inherits(ByteCounter, Writable);
function ByteCounter(options) {
Writable.call(this, options);
this.bytes = 0;
}
ByteCounter.prototype._write = function(chunk, encoding, cb) {
this.bytes += chunk.length;
this.emit('progress');
cb();
};
@@ -0,0 +1,31 @@
{
"name": "stream-counter",
"version": "0.1.0",
"description": "keeps track of how many bytes have been written to a stream",
"main": "index.js",
"scripts": {
"test": "node test/test.js"
},
"repository": {
"type": "git",
"url": "git://github.com/superjoe30/node-stream-counter.git"
},
"author": {
"name": "Andrew Kelley",
"email": "superjoe30@gmail.com"
},
"license": "BSD",
"engines": {
"node": ">=0.8.0"
},
"dependencies": {
"readable-stream": "~1.0.2"
},
"readme": "# stream-counter\n\nKeep track of how many bytes have been written to a stream.\n\n## Usage\n\n```js\nvar StreamCounter = require('stream-counter');\nvar counter = new StreamCounter();\ncounter.on('progress', function() {\n console.log(\"progress\", counter.bytes);\n});\nfs.createReadStream('foo.txt').pipe(counter);\n```\n",
"readmeFilename": "README.md",
"bugs": {
"url": "https://github.com/superjoe30/node-stream-counter/issues"
},
"_id": "stream-counter@0.1.0",
"_from": "stream-counter@~0.1.0"
}
@@ -0,0 +1,20 @@
var ByteCounter = require('../');
var fs = require('fs');
var path = require('path');
var assert = require('assert');
var counter = new ByteCounter();
var remainingTests = 2;
counter.once('progress', function() {
assert.strictEqual(counter.bytes, 5);
remainingTests -= 1;
});
var is = fs.createReadStream(path.join(__dirname, 'test.txt'));
is.pipe(counter);
is.on('end', function() {
remainingTests -= 1;
assert.strictEqual(counter.bytes, 5);
});
process.on('exit', function() {
assert.strictEqual(remainingTests, 0);
});
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
@@ -0,0 +1,76 @@
var assert = require('assert')
, Form = require('../').Form
, boundary = '-----------------------------168072824752491622650073'
, mb = 100
, buffer = createMultipartBuffer(boundary, mb * 1024 * 1024)
var callbacks = {
partBegin: -1,
partEnd: -1,
headerField: -1,
headerValue: -1,
partData: -1,
end: -1,
};
var form = new Form({ boundary: boundary });
hijack('onParseHeaderField', function() {
callbacks.headerField++;
});
hijack('onParseHeaderValue', function() {
callbacks.headerValue++;
});
hijack('onParsePartBegin', function() {
callbacks.partBegin++;
});
hijack('onParsePartData', function() {
callbacks.partData++;
});
hijack('onParsePartEnd', function() {
callbacks.partEnd++;
});
form.on('finish', function() {
callbacks.end++;
});
var start = new Date();
form.write(buffer, function(err) {
var duration = new Date() - start;
assert.ifError(err);
var mbPerSec = (mb / (duration / 1000)).toFixed(2);
console.log(mbPerSec+' mb/sec');
});
process.on('exit', function() {
for (var k in callbacks) {
assert.equal(0, callbacks[k], k+' count off by '+callbacks[k]);
}
});
function createMultipartBuffer(boundary, size) {
var head =
'--'+boundary+'\r\n' +
'content-disposition: form-data; name="field1"\r\n' +
'\r\n'
, tail = '\r\n--'+boundary+'--\r\n'
, buffer = new Buffer(size);
buffer.write(head, 'ascii', 0);
buffer.write(tail, 'ascii', buffer.length - tail.length);
return buffer;
}
function hijack(name, fn) {
var oldFn = form[name];
form[name] = function() {
fn();
return oldFn.apply(this, arguments);
};
}

Alguns arquivos não foram exibidos porque demasiados arquivos foram alterados neste diff Mostrar Mais