Browse Source

Merge branch 'master' into net2

v0.7.4-release
Ryan Dahl 15 years ago
parent
commit
dc01587c6c
  1. 21
      ChangeLog
  2. 3
      LICENSE
  3. 195
      doc/api.txt
  4. 4
      doc/index.html
  5. 552
      lib/multipart.js
  6. 167
      lib/sys.js
  7. 39
      src/node.cc
  8. 10
      src/node.js
  9. 21
      src/node_file.cc
  10. 8
      src/node_net.cc
  11. 39
      src/node_stdio.cc
  12. 1
      src/node_stdio.h
  13. 732
      test/mjsunit/fixtures/multipart.js
  14. 10
      test/mjsunit/fixtures/print-chars.js
  15. 32
      test/mjsunit/test-event-emitter-modify-in-emit.js
  16. 218
      test/mjsunit/test-multipart.js
  17. 1
      test/mjsunit/test-readdir.js
  18. 28
      test/mjsunit/test-stdout-flush.js
  19. 41
      test/mjsunit/test-sys.js
  20. 3
      tools/js2c.py
  21. 218
      tools/jsmin.py
  22. 1
      tools/jsmin.py
  23. 8
      wscript

21
ChangeLog

@ -1,4 +1,23 @@
2010.02.03, Version 0.1.27
2010.02.09, Version 0.1.28
* Use Google's jsmin.py which can be used for evil.
* Add posix.truncate()
* Throw errors from server.listen()
* stdio bugfix (test by Mikeal Rogers)
* Module system refactor (Felix Geisendörfer, Blaine Cook)
* Add process.setuid(), getuid() (Michael Carter)
* sys.inspect refactor (Tim Caswell)
* Multipart library rewrite (isaacs)
2010.02.03, Version 0.1.27, 0cfa789cc530848725a8cb5595224e78ae7b9dd0
* Implemented __dirname (Felix Geisendörfer)

3
LICENSE

@ -9,9 +9,6 @@ are:
This code is copyrighted by Marc Alexander Lehmann. Both are dually
licensed under MIT and GPL2.
- JSMin JavaScript minifier, located at tools/jsmin.py. This code is
copyrighted by Douglas Crockford and Baruch Even and has an MIT license.
- WAF build system, located at tools/waf. Copyrighted Thomas Nagy.
Released under an MIT license.

195
doc/api.txt

@ -1,7 +1,7 @@
NODE(1)
=======
Ryan Dahl <ry@tinyclouds.org>
Version, 0.1.27, 2010.02.03
Version, 0.1.28, 2010.02.09
== NAME
@ -136,6 +136,9 @@ success code 0.
+process.cwd()+::
Returns the current working directory of the process.
+process.getuid(), process.setuid(id)+::
Gets/sets the user identity of the process. (See setuid(2).)
+process.chdir(directory)+::
Changes the current working directory of the process.
@ -205,8 +208,8 @@ Like +puts()+ but without the trailing new-line.
A synchronous output function. Will block the process and
output the string immediately to stdout.
+inspect(object)+ ::
Return a string representation of the +object+. (For debugging.)
+inspect(object, showHidden)+ ::
Return a string representation of the +object+. (For debugging.) If showHidden is true, then the object's non-enumerable properties will be shown too.
+exec(command)+::
Executes the command as a child process, buffers the output and returns it
@ -653,6 +656,11 @@ sys.puts("stats: " + JSON.stringify(stats));
- on success: no parameters.
- on error: no parameters.
+posix.truncate(fd, len)+ ::
See ftruncate(2).
- on success: no parameters.
- on error: no parameters.
+posix.stat(path)+ ::
See stat(2).
@ -1176,73 +1184,158 @@ After emitted no other events will be emitted on the response.
=== Multipart Parsing
A library to parse HTTP requests with +multipart/form-data+ is included with
A library to parse +multipart+ internet messages is included with
Node. To use it, +require("multipart")+.
+multipart.parse(options)+ ::
- on success: Returns an object where each key holds the value of one part of
the stream. +options+ can either be an instance of
+http.ServerRequest+ or an object containing a "boundary" and
an optional "data" key.
- on error: Returns an instanceof Error object. Right now only the request
content-type / boundary option is checked. The stream data itself
is not validated.
+multipart.parse(message)+ ::
Returns a multipart.Stream wrapper around a streaming message.
The message must contain a `headers` member, and may be either an
HTTP request object or a JSGI-style request object with either a
forEachable or String body.
+
See the Stream class below.
+multipart.cat(message)+ ::
Returns a promise.
- on success: Returns a multipart.Stream object representing the completed
message. The body of each part is saved on the `body` member.
- on error: Returns an instanceof Error object. This indicates
that the message was malformed in some way.
+
*Note*: This function saves the *entire* message into memory. As such,
it is ill-suited to parsing actual incoming messages from an HTTP request!
If a user uploads a very large file, then it may cause serious problems.
No checking is done to ensure that the file does not overload the memory.
Only use multipart.cat with known and trusted input!
==== +multipart.Stream+
Here is an example for parsing a +multipart/form-data+ request:
----------------------------------------
var multipart = require("multipart");
var stream = new multipart.Stream(options);
var parts = {};
stream.addListener("part", function (part) {
var buffer = "";
part.addListener("body", function(chunk) {
buffer = buffer + chunk;
});
part.addListener("complete", function() {
parts[part.name] = buffer;
});
});
stream.addListener("complete", function() {
// The parts object now contains all parts and data
});
----------------------------------------
The multipart.Stream class is a streaming parser wrapped around a message.
The Stream also contains the properties described for the +part+ objects below,
and is a reference to the top-level message.
===== Events
[cols="1,2,10",options="header"]
|=========================================================
|Event | Parameters | Notes
|+"part"+ | +part+ | Emitted when a new part is found in the stream.
+part+ is an instance of +multipart.Part+.
|+"partBegin"+ | +part+ | Emitted when a new part is found in the stream.
+part+ is a +part object+, described below.
|+"partEnd"+ | +part+ | Emitted when a part is done.
|+"body"+ | +chunk+ | Emitted when a chunk of the body is read.
|+"complete"+ | | Emitted when the end of the stream is reached.
|+"error"+ | +error+ | Emitted when a parse error is encountered. This
indicates that the message is malformed.
|=========================================================
+stream.bytesTotal+::
The amount of bytes this stream is expected to have.
===== Properties
+stream.bytesReceived+::
The amount of bytes received by this stream so far.
+stream.part+::
The current part being processed. This is important, for instance, when responding
to the +body+ event.
==== +multipart.Part+
+stream.isMultiPart+::
True if the stream is a multipart message. Generally this will be true, but non-multipart
messages will behave the same as a multipart message with a single part, and +isMultiPart+
will be set to +false+.
[cols="1,2,10",options="header"]
|=========================================================
|Event | Parameters | Notes
|+"body"+ | +chunk+ | Emitted when a chunk of body is read.
|+"complete"+ | | Emitted when the end of the part is reached.
|=========================================================
+stream.parts+::
An array of the parts contained within the message. Each is a +part+ object.
+part.name+::
The field name of this part.
===== Methods
+stream.pause+::
If the underlying message supports pause and resume, then this will pause the stream.
+stream.resume+::
If the underlying message supports pause and resume, then this will resume the paused stream.
==== Part Objects
As it parses the message, the Stream object will create +Part+ objects.
===== Properties
+part.parent+::
The message that contains this part.
+part.headers+::
The headers object for this message.
+part.filename+::
The filename of this part. Only set for file uploads.
The filename, if specified in the +content-disposition+ or +content-type+ header.
For uploads, downloads, and attachments, this is the intended filename for the
attached file.
+part.name+::
The name, if specified in the +content-disposition+ or +content-type+ header. For
+multipart/form-data+ messages, this is the name of the field that was posted, and the
body specifies the value.
+part.isMultiPart+::
True if this part is a multipart message.
+part.parts+::
Array of children contained within a multipart message, or falsey.
+part.boundary+::
For multipart messages, this is the boundary that separates subparts.
+part.type+::
For multipart messages, this is the multipart type specified in the +content-type+ header.
For example, a message with +content-type: multipart/form-data+ will have a +type+
property of +form-data+.
==== Example
Here is an example for parsing a +multipart/form-data+ request:
----------------------------------------
var multipart = require("multipart"),
sys = require("sys"),
http = require("http");
http.createServer(function (req, res) {
var mp = multipart.parse(req),
fields = {},
name, filename;
mp.addListener("error", function (er) {
res.sendHeader(400, {"content-type":"text/plain"});
res.sendBody("You sent a bad message!\n"+er.message);
res.finish();
});
mp.addListener("partBegin", function (part) {
name = part.name;
filename = part.filename;
if (name) fields[name] = "";
});
mp.addListener("body", function (chunk) {
if (name) {
// just a demo. in reality, you'd probably
// want to sniff for base64 encoding, decode,
// and write the bytes to a file or something.
if (fields[name].length > 1024) return;
fields[name] += chunk;
}
});
mp.addListener("complete", function () {
var response = "You posted: \n" + sys.inspect(fields);
res.sendHeader(200, {
"content-type" : "text/plain",
"content-length" : response.length
});
res.sendBody(response);
res.finish();
})
});
----------------------------------------
==== Nested Multipart Messages
Nested multipart parsing is supported. The +stream.part+ object always refers
to the current part. If +part.isMultiPart+ is set, then that part is a
multipart message, which contains other parts. You can inspect its +parts+
array to see the list of sub-parts, which may also be multipart, and contain
sub-parts.
=== TCP

4
doc/index.html

@ -97,9 +97,9 @@ server.listen(7000, "localhost");</pre>
<a href="http://github.com/ry/node/tree/master">git repo</a>
</p>
<p>
2010.02.03
2010.02.09
<a
href="http://s3.amazonaws.com/four.livejournal/20100203/node-v0.1.27.tar.gz">node-v0.1.27.tar.gz</a>
href="http://s3.amazonaws.com/four.livejournal/20100209/node-v0.1.28.tar.gz">node-v0.1.28.tar.gz</a>
</p>
<h2 id="build">Build</h2>

552
lib/multipart.js

@ -1,194 +1,408 @@
var sys = require("sys");
var events = require('events');
exports.parse = function(options) {
var promise = new events.Promise();
try {
var stream = new exports.Stream(options);
} catch (e) {
process.nextTick(function() {
promise.emitError(e);
});
return promise;
}
var parts = {};
stream.addListener('part', function(part) {
var name = part.name;
var buffer = '';
part.addListener('body', function(chunk) {
buffer = buffer + chunk;
});
var sys = require("sys"),
events = require("events"),
wrapExpression = /^[ \t]+/,
multipartExpression = new RegExp(
"^multipart\/(" +
"mixed|rfc822|message|digest|alternative|" +
"related|report|signed|encrypted|form-data|" +
"x-mixed-replace|byteranges)", "i"),
boundaryExpression = /boundary=([^;]+)/i,
CR = "\r",
LF = "\n",
CRLF = CR+LF,
MAX_BUFFER_LENGTH = 16 * 1024,
// parser states.
s = 0,
S_NEW_PART = s++,
S_HEADER = s++,
S_BODY = s++;
exports.parse = parse;
exports.cat = cat;
exports.Stream = Stream;
// Parse a streaming message to a stream.
// If the message has a "body" and no "addListener", then
// just take it in and write() the body.
function parse (message) {
return new Stream(message);
};
part.addListener('complete', function() {
parts[name] = buffer;
});
// WARNING: DONT EVER USE THE CAT FUNCTION IN PRODUCTION WEBSITES!!
// It works pretty great, and it's a nice test function. But if
// you use this function to parse an HTTP request from a live web
// site, then you're essentially giving the world permission to
// rack up as much memory usage as they can manage. This function
// buffers the whole message, which is very convenient, but also
// very much the wrong thing to do in most cases.
function cat (message) {
var p = new (events.Promise),
stream = parse(message);
stream.files = {};
stream.fields = {};
stream.addListener("partBegin", function (part) {
if (part.filename) stream.files[part.filename] = part;
if (part.name) stream.fields[part.name] = part;
});
stream.addListener('complete', function() {
promise.emitSuccess(parts);
stream.addListener("body", function (chunk) {
stream.part.body = (stream.part.body || "") + chunk;
});
return promise;
};
exports.Stream = function(options) {
events.EventEmitter.call(this);
this.init(options);
stream.addListener("error", function (e) { p.emitError(e) });
stream.addListener("complete", function () { p.emitSuccess(stream) });
return p;
};
sys.inherits(exports.Stream, events.EventEmitter);
var proto = exports.Stream.prototype;
proto.init = function(options) {
this.buffer = '';
this.bytesReceived = 0;
this.bytesTotal = 0;
this.part = null;
if ('headers' in options) {
var req = options, contentType = req.headers['content-type'];
if (!contentType) {
throw new Error('Content-Type header not set');
// events:
// "partBegin", "partEnd", "body", "complete"
// everything emits on the Stream directly.
// the stream's "parts" object is a nested collection of the header objects
// check the stream's "part" member to know what it's currently chewin on.
// this.part.parent refers to that part's containing message (which may be
// the stream itself)
// child messages inherit their parent's headers
// A non-multipart message looks just like a multipart message with a
// single part.
function Stream (message) {
var isMultiPart = multipartHeaders(message, this),
w = isMultiPart ? writer(this) : simpleWriter(this),
e = ender(this);
if (message.addListener) {
message.addListener("body", w);
message.addListener("complete", e);
if (message.pause && message.resume) {
this._pause = message;
}
if (!contentType.match(/^multipart\/form-data/i)) {
throw new Error('Content-Type is not multipart: "'+contentType+'"');
} else if (message.body) {
var self = this;
if (message.body.pause && message.body.resume) {
this._pause = message.body;
}
var boundary = contentType.match(/boundary=([^;]+)/i)
if (!boundary) {
throw new Error('No boundary in Content-Type header: "'+contentType+'"');
if (message.body.addListener) {
message.body.addListener("data", w);
message.body.addListener("end", e);
} if (message.body.forEach) {
var p = message.body.forEach(w);
if (p && p.addCallback) p.addCallback(e);
else e();
} else {
// just write a string.
w(message.body);
e();
}
}
};
Stream.prototype = {
__proto__ : events.EventEmitter.prototype,
error : function (ex) {
this._error = ex;
this.emit("error", ex);
},
pause : function () {
if (this._pause) return this._pause.pause();
throw new Error("Unsupported");
},
resume : function () {
if (this._pause) return this._pause.resume();
throw new Error("Unsupported");
}
};
this.boundary = '--'+boundary[1];
this.bytesTotal = req.headers['content-length'];
var self = this;
req
.addListener('body', function(chunk) {
self.write(chunk);
})
.addListener('complete', function() {
self.emit('complete');
});
} else {
if (!options.boundary) {
throw new Error('No boundary option given');
// check the headers of the message. If it wants to be multipart,
// then we'll be returning true. Regardless, if supplied, then
// stream will get a headers object that inherits from message's.
// If no stream object is supplied, then this function just inspects
// the message's headers for multipartness, and modifies the message
// directly. This divergence is so that we can avoid modifying
// the original message when we want a wrapper, but still have the
// info available when it's one of our own objects.
function multipartHeaders (message, stream) {
var field, val, contentType, contentDisposition = "";
if (stream) stream.headers = {};
for (var h in message.headers) if (message.headers.hasOwnProperty(h)) {
val = message.headers[h];
field = h.toLowerCase();
if (stream) stream.headers[field] = val;
if (field === "content-type") {
contentType = val;
} else if (field === "content-disposition") {
contentDisposition = val;
}
this.boundary = options.boundary;
this.write(options.data || '');
}
};
proto.write = function(chunk) {
this.bytesReceived = this.bytesReceived + chunk.length;
this.buffer = this.buffer + chunk;
while (this.buffer.length) {
var offset = this.buffer.indexOf(this.boundary);
if (offset === 0) {
this.buffer = this.buffer.substr(offset + this.boundary.length + 2);
} else if (offset == -1) {
if (this.buffer === "\r\n") {
this.buffer = '';
} else {
this.part = (this.part || new Part(this));
this.part.write(this.buffer);
this.buffer = [];
if (!Array.isArray(contentDisposition)) {
contentDisposition = contentDisposition.split(",");
}
contentDisposition = contentDisposition[contentDisposition.length - 1];
var mutate = (stream || message);
// Name and filename can come along with either content-disposition
// or content-type. Well-behaved agents use CD rather than CT,
// but sadly not all agents are well-behaved.
[contentDisposition, contentType].forEach(function (h) {
if (!h) return;
var cd = h.split(/; */);
cd.shift();
for (var i = 0, l = cd.length; i < l; i ++) {
var bit = cd[i].split("="),
name = bit.shift(),
val = stripQuotes(bit.join("="));
if (name === "filename" || name === "name") {
mutate[name] = val;
}
} else if (offset > 0) {
this.part = (this.part || new Part(this));
this.part.write(this.buffer.substr(0, offset - 2));
}
});
this.part.emit('complete');
if (!contentType) {
return false;
}
this.part = new Part(this);
this.buffer = this.buffer.substr(offset + this.boundary.length + 2);
}
// legacy
// TODO: Update this when/if jsgi-style headers are supported.
// this will keep working, but is less efficient than it could be.
if (!Array.isArray(contentType)) {
contentType = contentType.split(",");
}
};
contentType = contentType[contentType.length-1];
function Part(stream) {
events.EventEmitter.call(this);
// make sure it's actually multipart.
var mpType = multipartExpression.exec(contentType);
if (!mpType) {
return false;
}
this.headers = {};
this.name = null;
this.filename = null;
this.buffer = '';
this.bytesReceived = 0;
// make sure we have a boundary.
var boundary = boundaryExpression.exec(contentType);
if (!boundary) {
return false;
}
// Avoids turning Part into a circular JSON object
this.getStream = function() {
return stream;
};
mutate.type = mpType[1];
mutate.boundary = "--" + boundary[1];
mutate.isMultiPart = true;
this._headersComplete = false;
return true;
};
function simpleWriter (stream) {
stream.part = stream;
stream.type = false;
var started = false;
return function (chunk) {
if (!started) {
stream.emit("partBegin", stream);
started = true;
}
stream.emit("body", chunk);
};
}
sys.inherits(Part, events.EventEmitter);
Part.prototype.parsedHeaders = function() {
for (var header in this.headers) {
var parts = this.headers[header].split(/; ?/), parsedHeader = {};
for (var i = 0; i < parts.length; i++) {
var pair = parts[i].split('=');
if (pair.length < 2) {
function writer (stream) {
var buffer = "",
state = S_NEW_PART,
part = stream.part = stream;
stream.parts = [];
stream.parent = stream;
return function (chunk) {
if (stream._error) return;
// write to the buffer, and then process the buffer.
buffer += chunk;
while (buffer.length > 0) {
while (buffer.substr(0, 2) === CRLF) buffer = buffer.substr(2);
switch (state) {
case S_NEW_PART:
// part is a multipart message.
// we're either going to start reading a new part, or we're going to
// end the current part, depending on whether the boundary has -- at
// the end. either way, we expect --boundary right away.
var boundary = part.boundary,
len = boundary.length,
offset = buffer.indexOf(boundary);
if (offset === -1) {
if (buffer.length > MAX_BUFFER_LENGTH) {
return stream.error(new Error(
"Malformed: boundary not found at start of message"));
}
// keep waiting for it.
return;
}
if (offset > 0) {
return stream.error(Error("Malformed: data before the boundary"));
}
if (buffer.length < (len + 2)) {
// we'll need to see either -- or CRLF after the boundary.
// get it on the next pass.
return;
}
if (buffer.substr(len, 2) === "--") {
// this message is done.
// chomp off the boundary and crlf and move up
if (part !== stream) {
// wait to see the crlf, unless this is the top-level message.
if (buffer.length < (len + 4)) {
return;
}
if (buffer.substr(len+2, 2) !== CRLF) {
return stream.error(new Error(
"Malformed: CRLF not found after boundary"));
}
}
buffer = buffer.substr(len + 4);
stream.emit("partEnd", part);
stream.part = part = part.parent;
state = S_NEW_PART;
continue;
}
if (part !== stream) {
// wait to see the crlf, unless this is the top-level message.
if (buffer.length < (len + 2)) {
return;
}
if (buffer.substr(len, 2) !== CRLF) {
return stream.error(new Error(
"Malformed: CRLF not found after boundary"));
}
}
// walk past the crlf
buffer = buffer.substr(len + 2);
// mint a new child part, and start parsing headers.
stream.part = part = startPart(part);
state = S_HEADER;
continue;
case S_HEADER:
// just grab everything to the double crlf.
var headerEnd = buffer.indexOf(CRLF+CRLF);
if (headerEnd === -1) {
if (buffer.length > MAX_BUFFER_LENGTH) {
return stream.error(new Error(
"Malformed: header unreasonably long."));
}
return;
}
var headerString = buffer.substr(0, headerEnd);
// chomp off the header and the empty line.
buffer = buffer.substr(headerEnd + 4);
try {
parseHeaderString(part.headers, headerString);
} catch (ex) {
return stream.error(ex);
}
multipartHeaders(part);
// let the world know
stream.emit("partBegin", part);
if (part.isMultiPart) {
// it has a boundary and we're ready to grab parts out.
state = S_NEW_PART;
} else {
// it doesn't have a boundary, and is about to
// start spitting out body bits.
state = S_BODY;
}
continue;
case S_BODY:
// look for part.parent.boundary
var boundary = part.parent.boundary,
offset = buffer.indexOf(boundary);
if (offset === -1) {
// emit and wait for more data, but be careful, because
// we might only have half of the boundary so far.
// make sure to leave behind the boundary's length, so that we'll
// definitely get it next time if it's on its way.
var emittable = buffer.length - boundary.length;
if (buffer.substr(-1) === CR) emittable -= 1;
if (buffer.substr(-2) === CRLF) emittable -= 2;
if (emittable > 0) {
stream.emit("body", buffer.substr(0, emittable));
buffer = buffer.substr(emittable);
}
// haven't seen the boundary, so wait for more bytes.
return;
}
if (offset > 0) {
var emit = buffer.substr(0, offset);
if (emit.substr(-2) === CRLF) emit = emit.substr(0, emit.length-2);
if (emit) stream.emit("body", emit);
buffer = buffer.substr(offset);
}
// let em know we're done.
stream.emit("partEnd", part);
// now buffer starts with boundary.
if (buffer.substr(boundary.length, 2) === "--") {
// message end.
// parent ends, look for a new part in the grandparent.
stream.part = part = part.parent;
stream.emit("partEnd", part);
stream.part = part = part.parent;
state = S_NEW_PART;
buffer = buffer.substr(boundary.length + 4);
} else {
// another part coming for the parent message.
stream.part = part = part.parent;
state = S_NEW_PART;
}
continue;
}
var key = pair[0].toLowerCase(), val = pair[1] || '';
val = stripslashes(val).substr(1);
val = val.substr(0, val.length - 1);
parsedHeader[key] = val;
}
};
};
if (header == 'content-disposition') {
this.name = parsedHeader.name || null;
this.filename = parsedHeader.filename || null;
function parseHeaderString (headers, string) {
var lines = string.split(CRLF),
field, value, line;
for (var i = 0, l = lines.length; i < l; i ++) {
line = lines[i];
if (line.match(wrapExpression)) {
if (!field) {
throw new Error("Malformed. First header starts with whitespace.");
}
value += line.replace(wrapExpression, " ");
continue;
} else if (field) {
// now that we know it's not wrapping, put it on the headers obj.
affixHeader(headers, field, value);
}
this.headers[header] = parsedHeader;
line = line.split(":");
field = line.shift().toLowerCase();
if (!field) {
throw new Error("Malformed: improper field name.");
}
value = line.join(":").replace(/^\s+/, "");
}
// now affix the last field.
affixHeader(headers, field, value);
};
Part.prototype.write = function(chunk) {
if (this._headersComplete) {
this.bytesReceived = this.bytesReceived + chunk.length;
this.emit('body', chunk);
return;
function affixHeader (headers, field, value) {
if (!headers.hasOwnProperty(field)) {
headers[field] = value;
} else if (Array.isArray(headers[field])) {
headers[field].push(value);
} else {
headers[field] = [headers[field], value];
}
};
this.buffer = this.buffer + chunk;
while (this.buffer.length) {
var offset = this.buffer.indexOf("\r\n");
if (offset === 0) {
this._headersComplete = true;
this.parsedHeaders();
this.getStream().emit('part', this);
this.buffer = this.buffer.substr(2);
this.bytesReceived = this.bytesReceived + this.buffer.length;
this.emit('body', this.buffer);
this.buffer = '';
return;
} else if (offset > 0) {
var header = this.buffer.substr(0, offset).split(/: ?/);
this.headers[header[0].toLowerCase()] = header[1];
this.buffer = this.buffer.substr(offset+2);
} else if (offset === -1) {
return;
}
}
function startPart (parent) {
var part = {
headers : {},
parent : parent
};
parent.parts = parent.parts || [];
parent.parts.push(part);
return part;
};
function ender (stream) { return function () {
if (stream._error) return;
if (!stream.isMultiPart) stream.emit("partEnd", stream);
stream.emit("complete");
}};
function stripslashes(str) {
// + original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// + improved by: Ates Goral (http://magnetiq.com)
@ -198,20 +412,24 @@ function stripslashes(str) {
// + improved by: rezna
// + input by: Rick Waldron
// + reimplemented by: Brett Zamir (http://brett-zamir.me)
// * example 1: stripslashes('Kevin\'s code');
// * example 1: stripslashes("Kevin\'s code");
// * returns 1: "Kevin's code"
// * example 2: stripslashes('Kevin\\\'s code');
// * example 2: stripslashes("Kevin\\\'s code");
// * returns 2: "Kevin\'s code"
return (str+'').replace(/\\(.?)/g, function (s, n1) {
return (str+"").replace(/\\(.?)/g, function (s, n1) {
switch(n1) {
case '\\':
return '\\';
case '0':
return '\0';
case '':
return '';
case "\\":
return "\\";
case "0":
return "\0";
case "":
return "";
default:
return n1;
}
});
}
};
function stripQuotes (str) {
str = stripslashes(str);
return str.substr(1, str.length - 2);
};

167
lib/sys.js

@ -21,9 +21,99 @@ exports.error = function (x) {
* in the best way possible given the different types.
*
* @param {Object} value The object to print out
* @param {Boolean} showHidden Flag that shows hidden (not enumerable) properties of objects.
*/
exports.inspect = function (value) {
return formatter(value, '', []);
exports.inspect = function (obj, showHidden) {
var seen = [];
function format(value) {
// Primitive types cannot have properties
switch (typeof value) {
case 'undefined': return 'undefined';
case 'string': return JSON.stringify(value);
case 'number': return '' + value;
case 'boolean': return '' + value;
}
// For some reason typeof null is "object", so special case here.
if (value === null) {
return 'null';
}
// Look up the keys of the object.
var keys = showHidden ? Object.getOwnPropertyNames(value).map(function (key) {
return '' + key;
}) : Object.keys(value);
var visible_keys = Object.keys(value);
// Functions without properties can be shortcutted.
if (typeof value === 'function' && keys.length === 0) {
if (value instanceof RegExp) {
return '' + value;
} else {
return '[Function]';
}
}
var base, type, braces;
// Determine the object type
if (value instanceof Array) {
type = 'Array';
braces = ["[", "]"];
} else {
type = 'Object';
braces = ["{", "}"];
}
// Make functions say that they are functions
if (typeof value === 'function') {
base = (value instanceof RegExp) ? ' ' + value : ' [Function]';
} else {
base = "";
}
seen.push(value);
if (keys.length === 0) {
return braces[0] + base + braces[1];
}
return braces[0] + base + "\n" + (keys.map(function (key) {
var name, str;
if (value.__lookupGetter__) {
if (value.__lookupGetter__(key)) {
if (value.__lookupSetter__(key)) {
str = "[Getter/Setter]";
} else {
str = "[Getter]";
}
} else {
if (value.__lookupSetter__(key)) {
str = "[Setter]";
}
}
}
if (visible_keys.indexOf(key) < 0) {
name = "[" + key + "]";
}
if (!str) {
if (seen.indexOf(value[key]) < 0) {
str = format(value[key]);
} else {
str = '[Circular]';
}
}
if (typeof name === 'undefined') {
if (type === 'Array' && key.match(/^\d+$/)) {
return str;
}
name = JSON.stringify('' + key);
}
return name + ": " + str;
}).join(",\n")).split("\n").map(function (line) {
return ' ' + line;
}).join('\n') + "\n" + braces[1];
}
return format(obj);
};
exports.p = function (x) {
@ -70,76 +160,3 @@ exports.exec = function (command) {
*/
exports.inherits = process.inherits;
/**
* A recursive function to format an object - used by inspect.
*
* @param {Object} value
* the value to format
* @param {String} indent
* the indent level of any nested objects, since they are formatted over
* more than one line
* @param {Array} parents
* contains all objects above the current one in the heirachy, used to
* prevent getting stuck in a loop on circular references
*/
var formatter = function(value, indent, parents) {
switch(typeof(value)) {
case 'string': return JSON.stringify(value);
case 'number': return '' + value;
case 'function': return '[Function]';
case 'boolean': return '' + value;
case 'undefined': return 'undefined';
case 'object':
if (value == null) return 'null';
if (parents.indexOf(value) >= 0) return '[Circular]';
parents.push(value);
if (value instanceof Array && Object.keys(value).length === value.length) {
return formatObject(value, indent, parents, '[]', function(x, f) {
return f(value[x]);
});
} else {
return formatObject(value, indent, parents, '{}', function(x, f) {
var child;
if (value.__lookupGetter__(x)) {
if (value.__lookupSetter__(x)) {
child = "[Getter/Setter]";
} else {
child = "[Getter]";
}
} else {
if (value.__lookupSetter__(x)) {
child = "[Setter]";
} else {
child = f(value[x]);
}
}
return f(x) + ': ' + child;
});
}
return buffer;
default:
throw('inspect unimplemented for ' + typeof(value));
}
}
/**
* Helper function for formatting either an array or an object, used internally by formatter
*/
var formatObject = function(obj, indent, parents, parenthesis, entryFormatter) {
var buffer = parenthesis[0];
var values = [];
var x;
var localFormatter = function(value) {
return formatter(value, indent + ' ', parents);
};
for (x in obj) {
values.push(indent + ' ' + entryFormatter(x, localFormatter));
}
if (values.length > 0) {
buffer += "\n" + values.join(",\n") + "\n" + indent;
}
buffer += parenthesis[1];
return buffer;
}

39
src/node.cc

@ -9,6 +9,8 @@
#include <unistd.h>
#include <errno.h>
#include <dlfcn.h> /* dlopen(), dlsym() */
#include <sys/types.h>
#include <unistd.h> /* setuid, getuid */
#include <node_buffer.h>
#include <node_io_watcher.h>
@ -468,12 +470,37 @@ static Handle<Value> Umask(const Arguments& args){
return scope.Close(Uint32::New(old));
}
static Handle<Value> GetUid(const Arguments& args) {
HandleScope scope;
int uid = getuid();
return scope.Close(Integer::New(uid));
}
static Handle<Value> SetUid(const Arguments& args) {
HandleScope scope;
if (args.Length() < 1) {
return ThrowException(Exception::Error(
String::New("setuid requires 1 argument")));
}
Local<Integer> given_uid = args[0]->ToInteger();
int uid = given_uid->Int32Value();
int result;
if ((result = setuid(uid)) != 0) {
return ThrowException(Exception::Error(String::New(strerror(errno))));
}
return Undefined();
}
v8::Handle<v8::Value> Exit(const v8::Arguments& args) {
int r = 0;
if (args.Length() > 0)
r = args[0]->IntegerValue();
HandleScope scope;
fflush(stderr);
exit(r);
Stdio::Flush();
exit(args[0]->IntegerValue());
return Undefined();
}
@ -958,6 +985,8 @@ static void Load(int argc, char *argv[]) {
NODE_SET_METHOD(process, "reallyExit", Exit);
NODE_SET_METHOD(process, "chdir", Chdir);
NODE_SET_METHOD(process, "cwd", Cwd);
NODE_SET_METHOD(process, "getuid", GetUid);
NODE_SET_METHOD(process, "setuid", SetUid);
NODE_SET_METHOD(process, "umask", Umask);
NODE_SET_METHOD(process, "dlopen", DLOpen);
NODE_SET_METHOD(process, "kill", Kill);
@ -1170,6 +1199,8 @@ int main(int argc, char *argv[]) {
// so your next reading stop should be node::Load()!
node::Load(argc, argv);
node::Stdio::Flush();
#ifndef NDEBUG
// Clean up.
context.Dispose();

10
src/node.js

@ -571,6 +571,16 @@ var posixModule = createInternalModule("posix", function (exports) {
return process.fs.rename(oldPath, newPath);
};
exports.truncate = function (fd, len) {
var promise = new events.Promise();
process.fs.truncate(fd, len, callback(promise));
return promise;
};
exports.truncateSync = function (fd, len) {
return process.fs.truncate(fd, len);
};
exports.rmdir = function (path) {
var promise = new events.Promise();
process.fs.rmdir(path, callback(promise));

21
src/node_file.cc

@ -51,6 +51,7 @@ static int After(eio_req *req) {
case EIO_UNLINK:
case EIO_RMDIR:
case EIO_MKDIR:
case EIO_FTRUNCATE:
argc = 0;
break;
@ -193,6 +194,25 @@ static Handle<Value> Rename(const Arguments& args) {
}
}
static Handle<Value> Truncate(const Arguments& args) {
HandleScope scope;
if (args.Length() < 2 || !args[0]->IsInt32()) {
return THROW_BAD_ARGS;
}
int fd = args[0]->Int32Value();
off_t len = args[1]->Uint32Value();
if (args[2]->IsFunction()) {
ASYNC_CALL(ftruncate, args[2], fd, len)
} else {
int ret = ftruncate(fd, len);
if (ret != 0) return ThrowException(errno_exception(errno));
return Undefined();
}
}
static Handle<Value> Unlink(const Arguments& args) {
HandleScope scope;
@ -403,6 +423,7 @@ void File::Initialize(Handle<Object> target) {
NODE_SET_METHOD(target, "open", Open);
NODE_SET_METHOD(target, "read", Read);
NODE_SET_METHOD(target, "rename", Rename);
NODE_SET_METHOD(target, "truncate", Truncate);
NODE_SET_METHOD(target, "rmdir", RMDir);
NODE_SET_METHOD(target, "mkdir", MKDir);
NODE_SET_METHOD(target, "sendfile", SendFile);

8
src/node_net.cc

@ -821,6 +821,14 @@ Handle<Value> Server::Listen(const Arguments& args) {
if (address_list) freeaddrinfo(address_list);
if (server->server_.errorno) {
Local<Value> e = Exception::Error(
String::NewSymbol(strerror(server->server_.errorno)));
Local<Object> obj = e->ToObject();
obj->Set(String::NewSymbol("errno"), Integer::New(server->server_.errorno));
return ThrowException(e);
}
return Undefined();
}

39
src/node_stdio.cc

@ -4,6 +4,8 @@
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
using namespace v8;
using namespace node;
@ -42,6 +44,15 @@ EmitClose (void)
emit->Call(stdio, 1, argv);
}
static inline Local<Value> errno_exception(int errorno) {
Local<Value> e = Exception::Error(String::NewSymbol(strerror(errorno)));
Local<Object> obj = e->ToObject();
obj->Set(String::NewSymbol("errno"), Integer::New(errorno));
return e;
}
/* STDERR IS ALWAY SYNC */
static Handle<Value>
WriteError (const Arguments& args)
@ -53,8 +64,19 @@ WriteError (const Arguments& args)
String::Utf8Value msg(args[0]->ToString());
fprintf(stderr, "%s", *msg);
fflush(stderr);
ssize_t r;
size_t written = 0;
while (written < msg.length()) {
r = write(STDERR_FILENO, (*msg) + written, msg.length() - written);
if (r < 0) {
if (errno == EAGAIN || errno == EIO) {
usleep(100);
continue;
}
return ThrowException(errno_exception(errno));
}
written += (size_t)r;
}
return Undefined();
}
@ -197,6 +219,19 @@ Close (const Arguments& args)
return Undefined();
}
void Stdio::Flush() {
if (stdout_fd >= 0) {
close(stdout_fd);
stdout_fd = -1;
}
if (stdout_coupling) {
coupling_join(stdout_coupling);
coupling_destroy(stdout_coupling);
stdout_coupling = NULL;
}
}
void
Stdio::Initialize (v8::Handle<v8::Object> target)
{

1
src/node_stdio.h

@ -11,6 +11,7 @@ namespace node {
class Stdio {
public:
static void Initialize (v8::Handle<v8::Object> target);
static void Flush ();
};
} // namespace node

732
test/mjsunit/fixtures/multipart.js

@ -1,11 +1,721 @@
exports.reply = ["--AaB03x\r",
"content-disposition: form-data; name=\"reply\"\r",
"\r",
"yes\r",
"--AaB03x\r",
"content-disposition: form-data; name=\"fileupload\"; filename=\"dj.jpg\"\r",
"Content-Type: image/jpeg\r",
"Content-Transfer-Encoding: base64\r",
"\r",
"/9j/4AAQSkZJRgABAQAAAQABAAD//gA+Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcg\r",
"--AaB03x--\r\n"].join("\n");
// each message contains a header, body, and a list of the parts that are
// expected. Any properties in the expected objects will be matched against
// the parsed parts.
var messages = exports.messages = [];
var bad = exports.badMessages = [];
var longString = "";
for (var i = 0; i < (16*1024); i ++) longString += Math.random();
// content before the first boundary
bad.push({
headers : { "Content-Type":"multipart/mixed; boundary=boundary" },
body : "blerg\r\n--boundary\r\nblarggghhh"
});
// no boundary
bad.push({
headers : { "Content-Type":"multipart/mixed; boundary=boundary" },
body : longString
});
// header unreasonably long.
bad.push({
headers : { "Content-Type":"multipart/mixed; boundary=boundary" },
body : "--boundary\r\ncontent-type: "+longString+"\r\n"+longString
});
// CRLF not found after boundary
bad.push({
headers : { "Content-Type":"multipart/mixed; boundary=boundary" },
body : "--boundary"+longString
});
// start first header with whitespace.
bad.push({
headers : { "Content-Type":"multipart/mixed; boundary=boundary" },
body : "--boundary\r\n fail: blahrg\r\n\r\n"+longString
});
// The comments in this first test case tell a story about what the parser is
// doing at each step. If you mean to touch the code, it's best to read through
// this test case first so that you know what you're getting into.
messages.push({
expect : [
{ type : "mixed", boundary : "--inner1" },
{ type : "mixed", boundary : "--inner2" },
{ filename : "hello.txt" },
{ filename : "hello2.txt" },
{ type : "mixed", boundary : "--inner3" },
{ filename : "hello3.txt" },
{ filename : "hello4.txt" },
{ filename : "hello-outer.txt" }
],
headers : {
"Content-Type":"multipart/mixed; boundary=outer"
}, body : [
// s=new part, part = stream, part.boundary=--outer
"--outer",// chomp to here, because it matches the boundary.
// mint a new part without a boundary, parent=old part, set state to header
"Content-Type: multipart/mixed; boundary=inner1",// move along
"", // found the end of the header. chomp to here, parse the headers onto
// the current part. Once we do that, we know that the current part
// is multipart, and has a boundary of --inner1
// s=new part, part = --inner1
"--inner1", // chomp to here.
// mint a new part without a boundary, parent=--inner1, s=header
"Content-type: multipart/mixed; boundary=inner2", // move along
"", // again, found the end of the header. chomp to here, parse headers
// onto the newly minted part. Then find out that this part has a
// boundary of --inner2.
// s=new part, part=--inner2
"--inner2", // chomp to here.
// mint a new part without a boundary, parent=--inner2
"Content-type: text/plain", // move along
"content-disposition: inline; filename=\"hello.txt\"", // move along
"", // chomp to here. found end of header. parse headers
// then we know that it's not multipart, so we'll be looking for
// the parent's boundary and emitting body bits.
// also, we can set part.filename to "hello.txt"
// s=body, part=hello.txt
"hello, world", // chomp, emit the body, looking for parent-boundary
"--inner2", // found parent.boundary. leave it on the buffer, and
// set part=part.parent, s=new part
// on the next pass, we'll chomp to here, mint a new part
// without a boundary, set s=header
"content-type: text/plain", // header...
"content-disposition: inline; filename=\"hello2.txt\"", // header...
"", // chomp to here, parse header onto the current part.
// since it's not multipart, we're looking for parent.boundary
"hello to the world", // body, looking for parent.boundary=--inner
"--inner2--", // found parent.boundary. In this case, we have the
// trailing --, indicating that no more parts are coming
// for this set. We need to back up to the grandparent,
// and then do the new part bit. Chomp off the --inner2--
// s=new part, part=part.parent.parent=--inner1
"--inner1", // chomp to here, because this is part.boundary
// mint a new part without a boundary
// s=header, part = (new)
"Content-type: multipart/mixed; boundary=inner3", // header...
"", // chomp to here, parse headers onto the new part.
// it's multipart, so set the boundary=--inner3,
// s=new part, part = --inner3
"--inner3", // chomp to here. mint a new part with no boundary, parse headers
"Content-type: text/plain", // header
"content-disposition: inline; filename=\"hello3.txt\"", // header
"", // end of header. parse headers onto part, whereupon we find that it is
// not multipart, and has a filename of hello3.txt.
// s=body, part=hello3.txt, looking for part.parent.boundary=--inner3
"hello, free the world", // body...
"--inner3", // found parent.boundary, and it's not the end.
// s = new part, part = part.parent
// next pass:
// mint a new part without a boundary, s=header
"content-type: text/plain", // header
"content-disposition: inline; filename=\"hello4.txt\"", // header
"", // chomp to here, parse headers on to boundaryless part.
// s=body, part = hello4.txt
"hello for the world", // body, looking for part.parent.boundary=--inner3
"--inner3--", // found parent.boundary, and it's the end
// chomp this off the buffer, part = part.parent.parent=--inner1
// s = new part
"--inner1", // chomp to here, because part.boundary = --inner1
// mint a new boundariless part, s = header
"Content-type: text/plain", // header...
"content-disposition: inline; filename=\"hello-outer.txt\"", // header...
"", // chomp to here, parse headers onto the current part.
// has no boundary, so we're gonna go into body mode.
// s = body, boundary = parent.boundary = --inner1, part = hello-outer.txt
"hello, outer world", // body, looking for parent.boundary=--inner1
"--inner1--", // found the parent.boundary, and it's the end.
// chomp off the --inner1--, part = part.parent.parent, s = new part
"--outer--" // we're looking for a new part, but found the ending.
// chomp off the --outer--, part = part.parent, s = new part.
].join("\r\n")
});
messages.push({
headers : {
"Content-Type": "multipart/form-data; boundary=AaB03x",
},
body : [
"--AaB03x",
"content-disposition: form-data; name=\"reply\"",
"",
"yes",
"--AaB03x",
"content-disposition: form-data; name=\"fileupload\"; filename=\"dj.jpg\"",
"Content-Type: image/jpeg",
"Content-Transfer-Encoding: base64",
"",
"/9j/4AAQSkZJRgABAQAAAQABAAD//gA+Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcg",
"--AaB03x--", ""
].join("\r\n"),
expect : [
{ name : "reply" },
{ name : "fileupload", filename : "dj.jpg" }
]
});
// one that's not multipart, just for kicks.
// verify that it ducks as a multipart message with one part.
messages.push({
headers: { "content-type" : "text/plain" },
body : "Hello, world!",
// not much to say about this one, since it's just
// validating that a part was created, not that it has
// any particular properties.
expect : [{}]
});
// An actual email message sent from felixge to isaacs.
// Addresses and signatures obscured, but the unicycle pic is preserved for posterity.
messages.push({
headers: {
// TODO: When node's parser supports header-wrapping, these should actually be wrapped,
// because that's how they appear in real life.
"Delivered-To":"isaacs...@gmail.com",
"Received":"by 10.142.240.14 with SMTP id n14cs252101wfh; Wed, 3 Feb 2010 14:24:08 -0800 (PST)",
"Received":"by 10.223.4.139 with SMTP id 11mr194455far.61.1265235847416; Wed, 03 Feb 2010 14:24:07 -0800 (PST)",
"Return-Path":"<isaacs+caf_=isaacs...=gmail.com@izs.me>",
"Received":"from mail-fx0-f219.google.com (mail-fx0-f219.google.com [209.85.220.219]) by mx.google.com with ESMTP id d13si118373fka.17.2010.02.03.14.24.05; Wed, 03 Feb 2010 14:24:06 -0800 (PST)",
"Received-SPF":"neutral (google.com: 209.85.220.219 is neither permitted nor denied by best guess record for domain of isaacs+caf_=isaacs...=gmail.com@izs.me) client-ip=209.85.220.219;",
"Authentication-Results":"mx.google.com; spf=neutral (google.com: 209.85.220.219 is neither permitted nor denied by best guess record for domain of isaacs+caf_=isaacs...=gmail.com@izs.me) smtp.mail=isaacs+caf_=isaacs...=gmail.com@izs.me; dkim=pass (test mode) header.i=@gmail.com",
"Received":"by mail-fx0-f219.google.com with SMTP id 19so626487fxm.25 for <isaacs...@gmail.com>; Wed, 03 Feb 2010 14:24:05 -0800 (PST)",
"Received":"by 10.216.91.15 with SMTP id g15mr146196wef.24.1265235845694; Wed, 03 Feb 2010 14:24:05 -0800 (PST)",
"X-Forwarded-To":"isaacs...@gmail.com",
"X-Forwarded-For":"isaacs@izs.me isaacs...@gmail.com",
"Delivered-To":"i@izs.me",
"Received":"by 10.216.12.146 with SMTP id 18cs33122wez; Wed, 3 Feb 2010 14:24:00 -0800 (PST)",
"Received":"by 10.213.97.28 with SMTP id j28mr2627124ebn.82.1265235838786; Wed, 03 Feb 2010 14:23:58 -0800 (PST)",
"Return-Path":"<hai...@gmail.com>",
"Received":"from ey-out-2122.google.com (ey-out-2122.google.com [74.125.78.25]) by mx.google.com with ESMTP id 4si11869270ewy.8.2010.02.03.14.23.54; Wed, 03 Feb 2010 14:23:57 -0800 (PST)",
"Received-SPF":"pass (google.com: domain of hai...@gmail.com designates 74.125.78.25 as permitted sender) client-ip=74.125.78.25;",
"Received":"by ey-out-2122.google.com with SMTP id d26so431288eyd.17 for <i@izs.me>; Wed, 03 Feb 2010 14:23:54 -0800 (PST)",
"DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=gamma; h=domainkey-signature:mime-version:sender:received:from:date :x-google-sender-auth:message-id:subject:to:content-type; bh=JXfvYIRzerOieADuqMPGlnlFbIGyPuTssL5icEtSLWw=; b=QDzgOCEbYk8cEdBe+HYx/MJrTWmZyx4qENADOcnnn9Xuk1Q6e/c7b3UsvLf/sMoYrG z96RQhUVOKi9IAzkQhNnOCWDuF1KNxtFnCGhEXMARXBM3qjXe3QmAqXNhJrI0E9bMeme d5aX5GMrz5mIark462cDsTmrFgaYE6JtwASho=",
"DomainKey-Signature":"a=rsa-sha1; c=nofws; d=gmail.com; s=gamma; h=mime-version:sender:from:date:x-google-sender-auth:message-id :subject:to:content-type; b=VYkN8OeNNJyxAseCAPH8u2aBfGmZaFesmieoWEDymQ1DsWg/aXbaWt4JGQlefIfmMK hOXd4EN2/iEix10aWDzKpuUV9gU9Wykm93t3pxD7BCz50Kagwp7NVyDJQLK0H5JSNEU/ IVRp90kKNBsb3v76vPsQydi9awLh/jYrFQVMY=",
"MIME-Version":"1.0",
"Sender":"hai...@gmail.com",
"Received":"by 10.216.86.201 with SMTP id w51mr154937wee.8.1265235834101; Wed, 03 Feb 2010 14:23:54 -0800 (PST)",
"From":"Felix Geisendoerfer <f...@debuggable.com>",
"Date":"Wed, 3 Feb 2010 23:23:34 +0100",
"X-Google-Sender-Auth":"0217977a92fcbed0",
"Message-ID":"<56dbc1211002031423g750ba93fs4a2f22ce22431590@mail.gmail.com>",
"Subject":"Me on my unicycle",
"To":"i@izs.me",
"Content-Type":"multipart/mixed; boundary=0016e6d99d0572dfaf047eb9ac2e",
},
expect : [
{ type : "alternative", boundary : "--0016e6d99d0572dfa5047eb9ac2c" },
{}, // the first bit, text/plain
{}, // the second bit, text/html
{ name : "unicycle.jpg", filename : "unicycle.jpg" }
],
body : [
"--0016e6d99d0572dfaf047eb9ac2e", // beginpart->header
"Content-Type: multipart/alternative; boundary=0016e6d99d0572dfa5047eb9ac2c", // headers. isMultipart
"", // bodybegin->beginpart
"--0016e6d99d0572dfa5047eb9ac2c",//header
"Content-Type: text/plain; charset=ISO-8859-1",
"",//bodybegin->body
"*This was 4 years ago, I miss riding my unicycle !*",
"",
"-- fg",
"",
"--0016e6d99d0572dfa5047eb9ac2c", //partend->partbegin
"Content-Type: text/html; charset=ISO-8859-1",
"",
"<b><font class=\"Apple-style-span\" color=\"#CC0000\">This was 4 years ago, I miss riding my <span class=\"Apple-style-span\" style=\"background-color: rgb(0, 0, 0);\">unicycle</span> !</font></b><div><br clear=\"all\">-- fg<br><br>",
"",
"",
"</div>",
"",
"--0016e6d99d0572dfa5047eb9ac2c--",//partend, walk up tree-->partbegin
"--0016e6d99d0572dfaf047eb9ac2e",//beginpart->header
"Content-Type: image/jpeg; name=\"unicycle.jpg\"",//header
"Content-Disposition: attachment; filename=\"unicycle.jpg\"",//header
"Content-Transfer-Encoding: base64",//header
"X-Attachment-Id: f_g58opqah0",//header
"",//bodybegin->body
"/9j/4AAQSkZJRgABAQEASABIAAD/4gUoSUNDX1BST0ZJTEUAAQEAAAUYYXBwbAIgAABzY25yUkdC",//bodybodybody
"IFhZWiAH0wAHAAEAAAAAAABhY3NwQVBQTAAAAABhcHBsAAAAAAAAAAAAAAAAAAAAAAAA9tYAAQAA",//bodybodybody
"AADTLWFwcGwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAty",//bodybodybody
"WFlaAAABCAAAABRnWFlaAAABHAAAABRiWFlaAAABMAAAABR3dHB0AAABRAAAABRjaGFkAAABWAAA",//bodybodybody
"ACxyVFJDAAABhAAAAA5nVFJDAAABhAAAAA5iVFJDAAABhAAAAA5kZXNjAAABlAAAAD1jcHJ0AAAE",
"1AAAAEFkc2NtAAAB1AAAAv5YWVogAAAAAAAAdEsAAD4dAAADy1hZWiAAAAAAAABacwAArKYAABcm",
"WFlaIAAAAAAAACgYAAAVVwAAuDNYWVogAAAAAAAA81IAAQAAAAEWz3NmMzIAAAAAAAEMQgAABd7/",
"//MmAAAHkgAA/ZH///ui///9owAAA9wAAMBsY3VydgAAAAAAAAABAjMAAGRlc2MAAAAAAAAAE0Nh",
"bWVyYSBSR0IgUHJvZmlsZQAAAAAAAAAAAAAAE0NhbWVyYSBSR0IgUHJvZmlsZQAAAABtbHVjAAAA",
"AAAAAA8AAAAMZW5VUwAAACQAAAKeZXNFUwAAACwAAAFMZGFESwAAADQAAAHaZGVERQAAACwAAAGY",
"ZmlGSQAAACgAAADEZnJGVQAAADwAAALCaXRJVAAAACwAAAJybmxOTAAAACQAAAIObm9OTwAAACAA",
"AAF4cHRCUgAAACgAAAJKc3ZTRQAAACoAAADsamFKUAAAABwAAAEWa29LUgAAABgAAAIyemhUVwAA",
"ABoAAAEyemhDTgAAABYAAAHEAEsAYQBtAGUAcgBhAG4AIABSAEcAQgAtAHAAcgBvAGYAaQBpAGwA",
"aQBSAEcAQgAtAHAAcgBvAGYAaQBsACAAZgD2AHIAIABLAGEAbQBlAHIAYTCrMOEw6QAgAFIARwBC",
"ACAw1zDtMNUwoTCkMOtleE9NdvhqXwAgAFIARwBCACCCcl9pY8+P8ABQAGUAcgBmAGkAbAAgAFIA",
"RwBCACAAcABhAHIAYQAgAEMA4QBtAGEAcgBhAFIARwBCAC0AawBhAG0AZQByAGEAcAByAG8AZgBp",
"AGwAUgBHAEIALQBQAHIAbwBmAGkAbAAgAGYA/AByACAASwBhAG0AZQByAGEAc3b4ZzoAIABSAEcA",
"QgAgY8+P8GWHTvYAUgBHAEIALQBiAGUAcwBrAHIAaQB2AGUAbABzAGUAIAB0AGkAbAAgAEsAYQBt",
"AGUAcgBhAFIARwBCAC0AcAByAG8AZgBpAGUAbAAgAEMAYQBtAGUAcgBhznS6VLd8ACAAUgBHAEIA",
"INUEuFzTDMd8AFAAZQByAGYAaQBsACAAUgBHAEIAIABkAGUAIABDAOIAbQBlAHIAYQBQAHIAbwBm",
"AGkAbABvACAAUgBHAEIAIABGAG8AdABvAGMAYQBtAGUAcgBhAEMAYQBtAGUAcgBhACAAUgBHAEIA",
"IABQAHIAbwBmAGkAbABlAFAAcgBvAGYAaQBsACAAUgBWAEIAIABkAGUAIABsIBkAYQBwAHAAYQBy",
"AGUAaQBsAC0AcABoAG8AdABvAAB0ZXh0AAAAAENvcHlyaWdodCAyMDAzIEFwcGxlIENvbXB1dGVy",
"IEluYy4sIGFsbCByaWdodHMgcmVzZXJ2ZWQuAAAAAP/hAIxFeGlmAABNTQAqAAAACAAGAQYAAwAA",
"AAEAAgAAARIAAwAAAAEAAQAAARoABQAAAAEAAABWARsABQAAAAEAAABeASgAAwAAAAEAAgAAh2kA",
"BAAAAAEAAABmAAAAAAAAAEgAAAABAAAASAAAAAEAAqACAAQAAAABAAAAyKADAAQAAAABAAABWwAA",
"AAD/2wBDAAICAgICAQICAgICAgIDAwYEAwMDAwcFBQQGCAcICAgHCAgJCg0LCQkMCggICw8LDA0O",
"Dg4OCQsQEQ8OEQ0ODg7/2wBDAQICAgMDAwYEBAYOCQgJDg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4O",
"Dg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg7/wAARCAFbAMgDASIAAhEBAxEB/8QAHwAAAQUBAQEB",
"AQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1Fh",
"ByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZ",
"WmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXG",
"x8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAEC",
"AwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHB",
"CSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0",
"dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX",
"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",
"CivqJ00meTzNH//Z",
"--0016e6d99d0572dfaf047eb9ac2e--",//partend, then complete
""
].join("\r\n")
});

10
test/mjsunit/fixtures/print-chars.js

@ -0,0 +1,10 @@
process.mixin(require("../common"));
var n = parseInt(process.argv[2]);
var s = "";
for (var i = 0; i < n-1; i++) {
s += 'c';
}
puts(s); // \n is the nth char.

32
test/mjsunit/test-event-emitter-modify-in-emit.js

@ -0,0 +1,32 @@
process.mixin(require("./common"));
var events = require('events');
var callbacks_called = [ ];
var e = new events.EventEmitter();
function callback1() {
callbacks_called.push("callback1");
e.addListener("foo", callback2);
e.removeListener("foo", callback1);
}
function callback2() {
callbacks_called.push("callback2");
e.removeListener("foo", callback2);
}
e.addListener("foo", callback1);
assert.equal(1, e.listeners("foo").length);
e.emit("foo");
assert.equal(1, e.listeners("foo").length);
assert.deepEqual(["callback1"], callbacks_called);
e.emit("foo");
assert.equal(0, e.listeners("foo").length);
assert.deepEqual(["callback1", "callback2"], callbacks_called);
e.emit("foo");
assert.equal(0, e.listeners("foo").length);
assert.deepEqual(["callback1", "callback2"], callbacks_called);

218
test/mjsunit/test-multipart.js

@ -1,129 +1,123 @@
process.mixin(require("./common"));
http = require("http");
var
var http = require("http"),
multipart = require("multipart"),
sys = require("sys"),
PORT = 8222,
multipart = require('multipart'),
fixture = require('./fixtures/multipart'),
requests = 0,
badRequests = 0,
partsReceived = 0,
partsComplete = 0,
respond = function(res, text) {
requests++;
if (requests == 5) {
server.close();
fixture = require("./fixtures/multipart"),
events = require("events"),
testPart = function (expect, part) {
if (!expect) {
throw new Error("Got more parts than expected: "+
JSON.stringify(part.headers));
}
for (var i in expect) {
assert.equal(expect[i], part[i]);
}
res.sendHeader(200, {"Content-Type": "text/plain"});
res.sendBody(text);
res.finish();
};
var server = http.createServer(function(req, res) {
if (req.headers['x-use-simple-api']) {
multipart.parse(req)
.addCallback(function() {
respond(res, 'thanks');
})
.addErrback(function() {
badRequests++;
respond(res, 'no thanks');
});
return;
}
try {
var stream = new multipart.Stream(req);
} catch (e) {
badRequests++;
respond(res, 'no thanks');
var emails = fixture.messages.slice(0),
chunkSize = 1, // set to minimum to forcibly expose boundary conditions.
// in a real scenario, this would be much much bigger.
firstPart = new (events.Promise);
// test streaming messages through directly, as if they were in a file or something.
(function testEmails () {
var email = emails.pop(),
curr = 0;
if (!email) {
firstPart.emitSuccess();
return;
}
var expect = email.expect;
var parts = {};
stream.addListener('part', function(part) {
partsReceived++;
var message = new (events.EventEmitter);
message.headers = email.headers;
var name = part.name;
if (partsReceived == 1) {
assert.equal('reply', name);
} else if (partsReceived == 2) {
assert.equal('fileupload', name);
var mp = multipart.parse(message);
mp.addListener("partBegin", function (part) {
testPart(email.expect[curr ++], part);
});
mp.addListener("complete", function () {
process.nextTick(testEmails);
});
// stream it through in chunks.
var emailBody = email.body;
process.nextTick(function s () {
if (emailBody) {
message.emit("body", emailBody.substr(0, chunkSize));
emailBody = emailBody.substr(chunkSize);
process.nextTick(s);
} else {
message.emit("complete");
}
parts[name] = '';
part.addListener('body', function(chunk) {
parts[name] += chunk;
});
})();
// run good HTTP messages test after previous test ends.
var secondPart = new (events.Promise),
server = http.createServer(function (req, res) {
var mp = multipart.parse(req),
curr = 0;
req.setBodyEncoding("binary");
if (req.url !== "/bad") {
mp.addListener("partBegin", function (part) {
testPart(message.expect[curr ++], part);
});
}
mp.addListener("error", function (er) {
res.sendHeader(400, {});
res.sendBody("bad");
res.finish();
});
part.addListener('complete', function(chunk) {
assert.equal(0, part.buffer.length);
if (partsReceived == 1) {
assert.equal('yes', parts[name]);
} else if (partsReceived == 2) {
assert.equal(
'/9j/4AAQSkZJRgABAQAAAQABAAD//gA+Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcg',
parts[name]
);
}
partsComplete++;
mp.addListener("complete", function () {
res.sendHeader(200, {});
res.sendBody("ok");
res.finish();
});
});
stream.addListener('complete', function() {
respond(res, 'thanks');
});
});
}),
message,
client = http.createClient(PORT);
server.listen(PORT);
var client = http.createClient(PORT);
var request = client.request('POST', '/', {
'Content-Type': 'multipart/form-data; boundary=AaB03x',
'Content-Length': fixture.reply.length
});
request.sendBody(fixture.reply, 'binary');
request.finish();
var simpleRequest = client.request('POST', '/', {
'X-Use-Simple-Api': 'yes',
'Content-Type': 'multipart/form-data; boundary=AaB03x',
'Content-Length': fixture.reply.length
});
simpleRequest.sendBody(fixture.reply, 'binary');
simpleRequest.finish();
var badRequest = client.request('POST', '/', {
'Content-Type': 'invalid!',
'Content-Length': fixture.reply.length
});
badRequest.sendBody(fixture.reply, 'binary');
badRequest.finish();
var simpleBadRequest = client.request('POST', '/', {
'X-Use-Simple-Api': 'yes',
'Content-Type': 'something',
'Content-Length': fixture.reply.length
});
simpleBadRequest.sendBody(fixture.reply, 'binary');
simpleBadRequest.finish();
var requestWithCharset = client.request('POST', '/', {
'X-Use-Simple-Api': 'yes',
'Content-Type': 'multipart/form-data; charset=utf-8; boundary=AaB03x',
'Content-Length': fixture.reply.length
// could dry these two up a bit.
firstPart.addCallback(function testGoodMessages () {
var httpMessages = fixture.messages.slice(0);
process.nextTick(function testHTTP () {
message = httpMessages.pop();
if (!message) {
secondPart.emitSuccess();
return;
}
var req = client.request("POST", "/", message.headers);
req.sendBody(message.body, "binary");
req.finish(function (res) {
var buff = "";
res.addListener("body", function (chunk) { buff += chunk });
res.addListener("complete", function () {
assert.equal(buff, "ok");
process.nextTick(testHTTP);
});
});
});
});
requestWithCharset.sendBody(fixture.reply, 'binary');
requestWithCharset.finish();
process.addListener('exit', function() {
puts("done");
assert.equal(2, partsComplete);
assert.equal(2, partsReceived);
assert.equal(2, badRequests);
secondPart.addCallback(function testBadMessages () {
var httpMessages = fixture.badMessages.slice(0);
process.nextTick(function testHTTP () {
message = httpMessages.pop();
if (!message) {
server.close()
return;
}
var req = client.request("POST", "/bad", message.headers);
req.sendBody(message.body, "binary");
req.finish(function (res) {
var buff = "";
res.addListener("body", function (chunk) { buff += chunk });
res.addListener("complete", function () {
assert.equal(buff, "bad");
process.nextTick(testHTTP);
});
});
});
});

1
test/mjsunit/test-readdir.js

@ -13,6 +13,7 @@ promise.addCallback(function (files) {
, 'echo.js'
, 'multipart.js'
, 'nested-index'
, 'print-chars.js'
, 'test_ca.pem'
, 'test_cert.pem'
, 'test_key.pem'

28
test/mjsunit/test-stdout-flush.js

@ -0,0 +1,28 @@
process.mixin(require("./common"));
var sub = path.join(fixturesDir, 'print-chars.js');
n = 100000;
var child = process.createChildProcess(process.argv[0], [sub, n]);
var count = 0;
child.addListener("error", function (data){
if (data) {
puts("parent stderr: " + data);
assert.ok(false);
}
});
child.addListener("output", function (data){
if (data) {
count += data.length;
puts(count);
}
});
child.addListener("exit", function (data) {
assert.equal(n, count);
puts("okay");
});

41
test/mjsunit/test-sys.js

@ -9,6 +9,7 @@ assert.equal('"hello"', inspect("hello"));
assert.equal("[Function]", inspect(function() {}));
assert.equal('undefined', inspect(undefined));
assert.equal('null', inspect(null));
assert.equal('/foo(bar\\n)?/gi', inspect(/foo(bar\n)?/gi));
assert.equal("\"\\n\\u0001\"", inspect("\n\u0001"));
@ -23,6 +24,24 @@ assert.equal('{\n "a": [Function]\n}', inspect({a: function() {}}));
assert.equal('{\n "a": 1,\n "b": 2\n}', inspect({a: 1, b: 2}));
assert.equal('{\n "a": {}\n}', inspect({'a': {}}));
assert.equal('{\n "a": {\n "b": 2\n }\n}', inspect({'a': {'b': 2}}));
assert.equal('[\n 1,\n 2,\n 3,\n [length]: 3\n]', inspect([1,2,3], true));
assert.equal("{\n \"visible\": 1\n}",
inspect(Object.create({}, {visible:{value:1,enumerable:true},hidden:{value:2}}))
);
assert.equal("{\n [hidden]: 2,\n \"visible\": 1\n}",
inspect(Object.create({}, {visible:{value:1,enumerable:true},hidden:{value:2}}), true)
);
// Objects without prototype
assert.equal(
"{\n [hidden]: \"secret\",\n \"name\": \"Tim\"\n}",
inspect(Object.create(null, {name: {value: "Tim", enumerable: true}, hidden: {value: "secret"}}), true)
);
assert.equal(
"{\n \"name\": \"Tim\"\n}",
inspect(Object.create(null, {name: {value: "Tim", enumerable: true}, hidden: {value: "secret"}}))
);
// Dynamic properties
assert.equal(
@ -35,12 +54,28 @@ value['a'] = value;
assert.equal('{\n "a": [Circular]\n}', inspect(value));
value = Object.create([]);
value.push(1);
assert.equal('{\n "0": 1,\n "length": 1\n}', inspect(value));
assert.equal("[\n 1,\n \"length\": 1\n]", inspect(value));
// Array with dynamic properties
value = [1,2,3];
value.__defineGetter__('growingLength', function () { this.push(true); return this.length; });
assert.equal(
"{\n \"0\": 1,\n \"1\": 2,\n \"2\": 3,\n \"growingLength\": [Getter]\n}",
"[\n 1,\n 2,\n 3,\n \"growingLength\": [Getter]\n]",
inspect(value)
);
);
// Function with properties
value = function () {};
value.aprop = 42;
assert.equal(
"{ [Function]\n \"aprop\": 42\n}",
inspect(value)
);
// Regular expressions with properties
value = /123/ig;
value.aprop = 42;
assert.equal(
"{ /123/gi\n \"aprop\": 42\n}",
inspect(value)
);

3
tools/js2c.py

@ -49,7 +49,8 @@ def CompressScript(lines, do_jsmin):
# If we're not expecting this code to be user visible, we can run it through
# a more aggressive minifier.
if do_jsmin:
return jsmin.jsmin(lines)
minifier = JavaScriptMinifier()
return minifier.JSMinify(lines)
# Remove stuff from the source that we don't want to appear when
# people print the source code using Function.prototype.toString().

218
tools/jsmin.py

@ -1,218 +0,0 @@
#!/usr/bin/python
# This code is original from jsmin by Douglas Crockford, it was translated to
# Python by Baruch Even. The original code had the following copyright and
# license.
#
# /* jsmin.c
# 2007-05-22
#
# Copyright (c) 2002 Douglas Crockford (www.crockford.com)
#
# 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 shall be used for Good, not Evil.
#
# 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.
# */
from StringIO import StringIO
def jsmin(js):
ins = StringIO(js)
outs = StringIO()
JavascriptMinify().minify(ins, outs)
str = outs.getvalue()
if len(str) > 0 and str[0] == '\n':
str = str[1:]
return str
def isAlphanum(c):
"""return true if the character is a letter, digit, underscore,
dollar sign, or non-ASCII character.
"""
return ((c >= 'a' and c <= 'z') or (c >= '0' and c <= '9') or
(c >= 'A' and c <= 'Z') or c == '_' or c == '$' or c == '\\' or (c is not None and ord(c) > 126));
class UnterminatedComment(Exception):
pass
class UnterminatedStringLiteral(Exception):
pass
class UnterminatedRegularExpression(Exception):
pass
class JavascriptMinify(object):
def _outA(self):
self.outstream.write(self.theA)
def _outB(self):
self.outstream.write(self.theB)
def _get(self):
"""return the next character from stdin. Watch out for lookahead. If
the character is a control character, translate it to a space or
linefeed.
"""
c = self.theLookahead
self.theLookahead = None
if c == None:
c = self.instream.read(1)
if c >= ' ' or c == '\n':
return c
if c == '': # EOF
return '\000'
if c == '\r':
return '\n'
return ' '
def _peek(self):
self.theLookahead = self._get()
return self.theLookahead
def _next(self):
"""get the next character, excluding comments. peek() is used to see
if an unescaped '/' is followed by a '/' or '*'.
"""
c = self._get()
if c == '/' and self.theA != '\\':
p = self._peek()
if p == '/':
c = self._get()
while c > '\n':
c = self._get()
return c
if p == '*':
c = self._get()
while 1:
c = self._get()
if c == '*':
if self._peek() == '/':
self._get()
return ' '
if c == '\000':
raise UnterminatedComment()
return c
def _action(self, action):
"""do something! What you do is determined by the argument:
1 Output A. Copy B to A. Get the next B.
2 Copy B to A. Get the next B. (Delete A).
3 Get the next B. (Delete B).
action treats a string as a single character. Wow!
action recognizes a regular expression if it is preceded by ( or , or =.
"""
if action <= 1:
self._outA()
if action <= 2:
self.theA = self.theB
if self.theA == "'" or self.theA == '"':
while 1:
self._outA()
self.theA = self._get()
if self.theA == self.theB:
break
if self.theA <= '\n':
raise UnterminatedStringLiteral()
if self.theA == '\\':
self._outA()
self.theA = self._get()
if action <= 3:
self.theB = self._next()
if self.theB == '/' and (self.theA == '(' or self.theA == ',' or
self.theA == '=' or self.theA == ':' or
self.theA == '[' or self.theA == '?' or
self.theA == '!' or self.theA == '&' or
self.theA == '|' or self.theA == ';' or
self.theA == '{' or self.theA == '}' or
self.theA == '\n'):
self._outA()
self._outB()
while 1:
self.theA = self._get()
if self.theA == '/':
break
elif self.theA == '\\':
self._outA()
self.theA = self._get()
elif self.theA <= '\n':
raise UnterminatedRegularExpression()
self._outA()
self.theB = self._next()
def _jsmin(self):
"""Copy the input to the output, deleting the characters which are
insignificant to JavaScript. Comments will be removed. Tabs will be
replaced with spaces. Carriage returns will be replaced with linefeeds.
Most spaces and linefeeds will be removed.
"""
self.theA = '\n'
self._action(3)
while self.theA != '\000':
if self.theA == ' ':
if isAlphanum(self.theB):
self._action(1)
else:
self._action(2)
elif self.theA == '\n':
if self.theB in ['{', '[', '(', '+', '-']:
self._action(1)
elif self.theB == ' ':
self._action(3)
else:
if isAlphanum(self.theB):
self._action(1)
else:
self._action(2)
else:
if self.theB == ' ':
if isAlphanum(self.theA):
self._action(1)
else:
self._action(3)
elif self.theB == '\n':
if self.theA in ['}', ']', ')', '+', '-', '"', '\'']:
self._action(1)
else:
if isAlphanum(self.theA):
self._action(1)
else:
self._action(3)
else:
self._action(1)
def minify(self, instream, outstream):
self.instream = instream
self.outstream = outstream
self.theA = '\n'
self.theB = None
self.theLookahead = None
self._jsmin()
self.instream.close()
if __name__ == '__main__':
import sys
jsm = JavascriptMinify()
jsm.minify(sys.stdin, sys.stdout)

1
tools/jsmin.py

@ -0,0 +1 @@
../deps/v8/tools/jsmin.py

8
wscript

@ -7,7 +7,7 @@ from os.path import join, dirname, abspath
from logging import fatal
cwd = os.getcwd()
VERSION="0.1.27"
VERSION="0.1.28"
APPNAME="node.js"
import js2c
@ -91,7 +91,7 @@ def conf_subproject (conf, subdir, command=None):
copytree(src, default_tgt, True)
if command:
if os.system("cd %s && %s" % (default_tgt, command)) != 0:
if os.system("cd \"%s\" && %s" % (default_tgt, command)) != 0:
conf.fatal("Configuring %s failed." % (subdir))
debug_tgt = join(conf.blddir, "debug", subdir)
@ -191,7 +191,7 @@ def build_udns(bld):
static_lib = bld.env["staticlib_PATTERN"] % "udns"
rule = 'cd %s && make'
rule = 'cd "%s" && make'
default = bld.new_task_gen(
target= join("deps/udns", static_lib),
@ -234,7 +234,7 @@ def v8_cmd(bld, variant):
else:
mode = "debug"
cmd_R = 'python %s -C %s -Y %s visibility=default mode=%s %s library=static snapshot=on'
cmd_R = 'python "%s" -C "%s" -Y "%s" visibility=default mode=%s %s library=static snapshot=on'
cmd = cmd_R % ( scons
, bld.srcnode.abspath(bld.env_of_name(variant))

Loading…
Cancel
Save