latte_web3
Version:
428 lines (419 loc) • 11.5 kB
JavaScript
(function(define) { 'use strict';
define("latte_web/post/index", ["require", "exports", "module", "window"],
function(require, exports, module, window) {
var latte_lib = require("latte_lib")
, events = latte_lib.events
, DataParser = require("./dataParser")
, File = require("./file")
, Path = require("path")
, QuerystringParser = require("./querystringParser")
, Stream = require("stream").Stream
, JSONParser = require("./jsonParser");
var defaultConfig = {
maxFields: 1000,
maxFieldsSize: 2 * 1024 * 1024,
keepExtensions : false,
uploadDir: require("os").tmpDir(),
encoding: "utf-8",
hash: false,
multiples: false
};
function Post(opts) {
if(!(this instanceof Post)) return new Post(opts);
opts = opts || {};
this.error = null;
this.ended = false;
this.config = latte_lib.merger(defaultConfig, opts);
this.headers = null;
this.type = null;
this.bytesReceived = null;
this.bytesExpected = null;
this._parser = null;
this._flushing = 0;
this._fieldsSize = 0;
this.openedFiles = [];
return this;
};
latte_lib.inherits(Post, events);
(function() {
this.pause = function() {
var req = this.req;
if(!req) {
return false;
}
try {
req.pause();
} catch(err) {
if(!this.ended) {
this.onError(err);
}
return false;
}
return true;
}
this.resume = function() {
var req = this.req;
if(!req) {
return false;
}
try {
req.resume();
}catch(err) {
if(!this.ended) {
this.onError(err);
}
return false;
}
return true;
}
this.parse = function(req, callback) {
this.req = req;
this.writeHeaders(req.headers) ;
var self = this;
req.on("error", function(err) {
self.onError(err);
}).on("aborted", function() {
self.emit("aborted");
self.onError(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.onError(err);
}
});
return this;
}
this.writeHeaders = function(headers) {
this.headers = headers;
this._parseContentLength();
this._parseContentType();
}
this._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);
}
}
var getType = function(headers) {
if(headers["content-type"].match(/octet-stream/i)) {
return "octet-stream";
}
if(headers["content-type"].match(/urlencoded/i)) {
return "urlencoded";
}
if(headers["content-type"].match(/multipart/i)) {
return "multipart";
}
if(headers["content-type"].match(/json/i)) {
return "json";
}
if(headers["content-type"].match(/text/i)) {
return "text";
}
return "text";
}
function dummyParser(self) {
return {
end: function () {
self.ended = true;
self._maybeEnd();
return null;
}
};
};
this._parseContentType = function() {
if(this.bytesExpected === 0) {
this._parser = dummyParser(this);
return;
}
var self = this;
switch(getType(this.headers)) {
case "octet-stream":
self._initOctetStream();
break;
case "urlencoded":
self._initUrlencoded();
break;
case "json":
self._initJSONencoded();
break;
case "text":
self._initTextencoded();
break;
default:
//dummyParser(this);
this.onError(new Error("bad content-type headers, unknown content-type: "+ this.headers["content-type"] ));
break;
}
}
this._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
});
this.emit("fileBegin", filename, file);
file.open();
this._flushing++;
var self = this;
self._parser = new DataParser();
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() {
file.end(function() {
self.emit("file", filename || file.path, file);
self._maybeEnd();
});
}
if(outstandingWrites === 0) {
done();
} else {
self._parser.once("doneWritingFile", done);
}
});
}
this._initTextencoded = function() {
var self = this;
self._parser = new DataParser();
var data = "";
self._parser.on("data", function(buffer) {
data += buffer;
});
self._parser.on("end", function() {
self.emit("text", data);
self.ended = true;
self._maybeEnd();
});
}
this._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]+).*/i, "$1");
name += ext;
}
return Path.join(this.config.uploadDir, name);
}
this._maybeEnd = function() {
if(!this.ended || this._flushing || this.error) {
return;
}
this.emit("end");
}
this._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;
}
this._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;
}
this._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 = headerValue.match(/\bname="([^"]+)"/i);
if(headerField == "content-disposition") {
if(m) {
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;
parser.onPartData = function(b, start, end) {
part.transferBuffer += b.slice(start, end).toString("ascii");
var offset = parseInt(part.transferBuffer.length / 4, 10 )*4;
part.emit("data", new Buffer(part.transferBuffer.substring(0, offset), "base64"));
};
parser.onPartEnd = function() {
part.emit("data", new Buffer(part.transferBuffer, "base64"));
part.emit("end");
};
break;
default:
return self.onError(new Error("unknown transfer-encoding"));
}
self.onPart(part);
};
parser.onEnd = function() {
self.ended = true;
self._maybeEnd();
};
this._parser = parser;
}
this.write = function(buffer) {
if(this.error) {
return;
}
if(!this._parser) {
this.onError(new Error("uninitialized parser"));
return;
}
this.bytesReceived += buffer.length;
this.emit("progress", this.bytesReceived, this.bytesExpected);
var bytesParsed = this._parser.write(buffer);
if(bytesParsed !== buffer.length) {
this.onError(new Error("parser error, " + bytesParsed + " of " + buffer.length + " bytes parsed"));
}
return bytesParsed;
}
this.handlePart = this.onPart = 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.onError(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) {
if(buffer.length == 0) {
return;
}
self.pause();
file.write(buffer, function() {
self.resume();
});
});
part.on("end" , function() {
file.end(function() {
self._flishing--;
self.emit("file", part.name, file);
self._maybeEnd();
});
});
}
this.onError = function(err) {
if(this.error) {
return;
}
this.error = err;
this.pause();
this.emit("error", err);
}
this._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;
};
}).call(Post.prototype);
module.exports = Post;
});
})(typeof define === "function"? define: function(name, reqs, factory) { factory(require, exports, module); });