Permalink
Browse files

Merge remote branch 'mj/master'

  • Loading branch information...
2 parents 0b77a68 + 164ba4b commit 762f12985565b85cdcc8f1ed20b43d66f5ee6a97 @isaacs committed Jun 13, 2010
Showing with 326 additions and 126 deletions.
  1. +3 −1 lib/parse.js
  2. +142 −60 lib/write.js
  3. +33 −0 test/fixture.js
  4. +148 −0 test/write.js
  5. +0 −65 test/writer.js
View
@@ -44,6 +44,7 @@ function Parser () {
this.position = this.column = this.line = 0;
this.parent = this;
this.type = this.headers = this.isMultiPart = this.boundary = null;
+ this.test = true;
}
Parser.prototype.write = function (chunk) {
@@ -180,7 +181,8 @@ Parser.prototype.write = function (chunk) {
// message end.
// parent ends, look for a new part in the grandparent.
parser.part = parser.part.parent;
- emit(parser, "onPartEnd", parser.part);
+ if (parser.part !== parser)
+ emit(parser, "onPartEnd", parser.part);
parser.part = parser.part.parent;
parser.state = S.NEW_PART;
parser.buffer = parser.buffer.substr(boundary.length + 4);
View
@@ -6,7 +6,7 @@
// var w = writer();
// w.boundary = "foo-bar-bazfj3980haf38h";
-// w.type = "form-data";
+// w.contentType = "form-data";
// w.headers = {...};
// w.partBegin({...}); // send the headers, causes the initial "data" event to emit
// w.write("..."); // encode the data, wrap it, etc., and emit more "data" events
@@ -17,11 +17,20 @@
// w.partEnd(); // close off the parent
// w.close(); // close off all open parts, and emit "end" event
-var sys = require("sys"),
- utils = require("./utils"),
- error = utils.error,
- emit = utils.emit,
- EVENTS = exports.EVENTS = ["onData", "onEnd", "onError"];
+var sys = require("sys")
+ , utils = require("./utils")
+ , error = utils.error
+ , emit = utils.emit
+ , multipartExpression = new RegExp(
+ "^multipart\/(" +
+ "mixed|rfc822|message|digest|alternative|" +
+ "related|report|signed|encrypted|form-data|" +
+ "x-mixed-replace|byteranges)", "i")
+ , EVENTS = exports.EVENTS = ["onData", "onEnd", "onError"]
+ , CR = "\r"
+ , LF = "\n"
+ , CRLF = CR+LF
+ ;
var S = 0;
exports.STATE =
@@ -41,78 +50,151 @@ function NYI () { throw new Error("Not yet implemented") }
// Returns a new writer.
// Attaches event handlers to it, and they'll get notified
-// call myWriter.write(chunk) and then myWriter.close() when it's done.
+// This writer does not write http headers.
+// It only emits a properly encoded multipart stream suitable to being streamed to
+// a http body or any other place multipart data is needed
function writer () { return new Writer() }
-function end (writer) {
- // close the whole stack of open parts, and emit "end"
- throw new Error("TODO");
-}
-function newPart (writer) {
- var p =
- { headers:{}
- , parent : writer.part
- };
- parent.parts = parent.parts || [];
- parent.parts.push(p);
-}
-
function Writer () {
this.firstPartReceived = false;
this.state = S.STARTED;
+ this.depth = 0;
+ this.parts = [];
}
+// Starts a part or nested part.
+// Emits data events to listeners with headers for part.
+//
+// Errors if part is added to a part of type other than multipart,
+// or if new part is started before the old one is written correctly.
+//
+// Params: A part headers object
+// These should be of the form:
+// {name:"test", type:"multipart/mixed", filename="foo.txt"}
+// Type is required and is encoded as "Content-Disposition"
+// Name is required and is encoded as the name of the part.
+// If set, filename is set as the filename of the part.
+// Optionally pass in a headers object of other headers
+// e.g Content-Length, which will be appended to the headers.
+Writer.prototype.partBegin = function (part, headers) {
+ var writer = this
+ , type = part["type"]
+ , partString = ""
+ , boundary = part["boundary"]
+ , name = part["name"]
+ , filename = part["filename"]
+ , currentPart = writer.parts[writer.parts.length-1];
+ ;
+
+ //sys.debug("writer depth:" + writer.depth);
+ //sys.debug('writing part: ' + sys.inspect(part));
+
+ if (!writer.boundary) error(writer, "Missing property boundary on writer");
+
+ if (!type && !filename) {
+ error(writer, "Missing required type property on part.");
+ }
+
+ if (!type && filename) {
+ type = "inline";
+ } else {
+ type = "multipart/" + type;
+ }
+
+ if (writer.state === S.WRITING)
+ return error(writer, "Illegal state. Cannot begin new part right now.");
+
+ if (writer.state === S.PART_STARTED && currentPart.type !== "mixed")
+ return error(writer, "Bad format. Cannot add part to non multipart parent.");
+
+ partString += "--" + writer.boundary + CRLF;
+
+ //write the Content-Disposition
+ partString += "Content-Type: " + type;
+ if (name) partString += "; name:'" + name + "'";
+ if (filename) partString += "; filename='" + filename + "'";
+ if (boundary) partString += "; boundary=" + boundary;
+ partString += CRLF;
+ // go down a nested part
+ if (type === "mixed") writer.depth++;
+
+ //write out any optional other headers
+ if (headers) {
+ Object.keys(headers).forEach(function (key) {
+ partString += key + ": " + headers[key] + CRLF;
+ });
+ }
+ emit(writer, "onData", partString + CRLF);
+ writer.state = S.PART_STARTED;
+ writer.parts.push(part);
+}
// Writes a chunk to the multipart stream
// Sets error if not called after a partBegin
-Writer.prototype.write = write;
-function write (chunk) {
+Writer.prototype.write = function (chunk) {
var writer = this;
- //ye old state machine
+
if (chunk === null) return;
- if (writer.state !== S.PART_STARTED) {
+
+ if (writer.state !== S.PART_STARTED)
error(writer, "Illegal state. Must call partBegin before writing.");
- return;
- }
- // TODO - encode the data if base64 content-transfer-encoding
+
+ // TODO - encode the data if base64 content-transfer-encoding
emit(writer, "onData", chunk);
+ writer.state = S.WRITING;
}
-Writer.prototype.close = NYI;
+// Writes the part end
+// E.g. /r/n--boundary--
+Writer.prototype.partEnd = function () {
+ var writer = this
+ , currentPart = writer.parts[writer.parts.length-1]
+ ;
+
+ if (currentPart && currentPart.type !== "mixed" && writer.state !== S.WRITING)
+ error(writer, "Illegal state. Must write at least one chunk to this part before calling partEnd");
+
+ emit(writer, "onData", CRLF + "--" + writer.boundary + "--" + CRLF);
+ writer.state = S.PART_ENDED;
+ currentPart = writer.parts.pop();
+ if (currentPart && currentPart.type === "mixed") writer.depth--;
+}
-// Starts a part or nested part.
-// Emits data events to listeners with headers for part.
-// If first part, will emit http headers plus headers of first part.
-// Sets error if part is added to a part of type other than multipart,
-// or if new part is started before the old one is written correctly.
+
+Writer.prototype.close = function () {
+ var writer = this;
+ if (!writer.depth && writer.state === S.PART_ENDED) return emit(writer, "onEnd");
+ error(writer, "Illegal state. Multiparts not written completely before close.")
+};
+
+
+
+// Write the headers plus 2 CRLFs, e.g.:
+// Content-Type: multipart/form-data; boundary=---------------------------7d44e178b0434
+// Host: api.flickr.com
+// Content-Length: 35261
+//
//
-// Params: object describing header for event
-// e.g. { "content-type" : "text/plain", filename : "foo.txt" }
-Writer.prototype.partBegin = partBegin;
-function partBegin (partDesc) {
- var writer = this;
- if (!writer.boundary || writer.boundary.length < 1) {
- error(writer, "Missing boundary. Set boundary property.");
- return;
- }
- if (writer.state !== S.STARTED && writer.state !== S.PART_ENDED) {
- error(writer, "Illegal state. Cannot begin new part right now.");
- return;
- }
- else if (this.currentPart && !isMulti(this.currentPart)) {
- error(writer, "Bad format. Cannot add part to non multipart parent.");
- return;
- } else {
- // TODO - check for invalid state before adding the new part
- //newPart(writer);
- partChunk = "";
- if (!writer.firstPartReceived) {
- //write the http headers
- }
- // TODO - encode part headers based on partDesc
- emit(writer, "onData", "TODO: encoded chunk");
- }
+function writeHttpHeaders(writer) {
+ if (!writer.contentType)
+ error(writer, "Missing contentType property. Must set this property on writer.");
+
+ if (!multipartExpression.test(writer.contentType))
+ error(writer, "Invalid property 'contentType'. Must be one of multipart(|" +
+ "mixed|rfc822|message|digest|alternative|" +
+ "related|report|signed|encrypted|form-data|" +
+ "x-mixed-replace|byteranges)");
+
+ if (!writer.boundary)
+ error(writer, "Missing data. Must set boundary property on writer.");
+
+ writer.headers = writer.headers || {}
+ var headerString = "";
+ writer.headers["Content-Type"] = writer.contentType + "; boundary=" + writer.boundary;
+ Object.keys(writer.headers).forEach(function (key) {
+ headerString += key + ": " + writer.headers[key];
+ });
+ emit(writer, "onData", headerString + CRLF + CRLF);
}
-Writer.prototype.partEnd = NYI;
View
@@ -710,3 +710,36 @@ messages.push({
""
].join("\r\n")
});
+
+
+exports.aSimpleMessage = {
+ headers: {
+ "Content-Type": "multipart/form-data; boundary=AaB03x",
+ },
+ boundary : "AaB03x",
+ parts: [ {
+ part: {
+ "type": "form-data", "name": "test"
+ }
+ , body: "hello"
+ , encoded: [
+ "--AaB03x"
+ , "content-disposition: form-data; name=\"test\""
+ , ""
+ , "hello"
+ ].join(",")
+ }
+ , {
+ part: {
+ "type": "form-data", "name": "test1"
+ }
+ , body: "hello1"
+ , encoded: [
+ "--AaB03x"
+ , "content-disposition: form-data; name=\"test1\""
+ , ""
+ , "hello1"
+ ].join(",")
+ }
+ ]
+};
Oops, something went wrong.

1 comment on commit 762f129

this looks like a regression (merge old version into new one), am i correct?

Please sign in to comment.