Skip to content

Instantly share code, notes, and snippets.

@dantman
Created March 7, 2010 19:29
Show Gist options
  • Save dantman/324584 to your computer and use it in GitHub Desktop.
Save dantman/324584 to your computer and use it in GitHub Desktop.
// -*- coding: UTF-8 -*-
// Dummy module names, not final. Ignore my JS1.8isms
var {Stream} = require('stream');
var {Socket} = require('socket');
var {StreamerHelper} = require('streamer-helper'); // You can ignore this one, just one of my personal ideas
var StreamHelpers = require('stream-helpers'); // Another thing you can ignore
var {BufferStream} = require('bufferstream'); // Another user-level
function MultipartStreamParser() {
this.boundary = false;
}
MultipartStreamParser.prototype.parse = function(sh) {
if ( !this.boundary )
throw "A multipart boundary must be specified.";
var boundary = this.bounday.getBuffer("US-ASCII");
var CRLF = "\r\n".getBuffer("US-ASCII");
var dd = "--".getBuffer("US-ASCII");
var boundarystart = CRLF.concat(dd, boundary);
var s = new StreamerHelper(stream); // This is a user level idea of mine to make dealing with complex stream protocols easier
s.expand(boundary.length);
if ( s.startsWith(dd.concat(boundary)) ) {
s.shift(dd.length+boundary.length);
} else {
for(;;) {
s.expand(1024, false);
var i = s.indexOf(boundarystart.concat(CRLF));
if ( i < 0 )
continue;
if ( typeof this.onpreamble === "function" )
this.onpreamble(new BufferStream(s.slice(0, i)));
s.shift(i+boundarystart.length+CRLF.length);
}
}
if ( typeof this.onstartheaders === "function" )
this.onstartheaders();
var headerName;
for(var headers = 0;; headers++) {
if ( headers > this.maxHeaders )
throw "Too many headers sent with multipart body.";
var i = s.expandToFind(CRLF, this.maxHeaderLength);
if ( i < 0 )
throw "Header line went over the limit.";
var headerLine = s.toString("US-ASCII", 0, i);
if ( !headerLine )
// Blank line, end of headers
break;
if ( headerName && headerLine.match(/^\s+/) ) {
if ( typeof this.onheader === "function" )
this.onheader(headerName, headerLine.replace(/^\s+/, "\n "));
continue;
}
var m = headerLine.match(/^([^()<>@,;:\\"\/\[\]?={} \t]):\s*(.*)$/);
if ( !m ) {
// Invalid header, ignore
if ( typeof this.onerror === "function" )
this.onerror("Invalid header line: "+headerLine);
continue;
}
var [, headerName, headerValue] = m;
if ( typeof this.onheader === "function" )
this.onheader(headerName, headerValue);
s.shift(i+1);
}
if ( typeof this.onendheaders === "function" )
this.onendheaders();
var body = Stream.create({
nullBufferSupport: true, // Just a thought, a flag to support read: with a null buffer?
read: function(buffer, off, length) {
var i = s.indexOf(boundarystart);
if ( i < 0 ) {
if ( s.endsWithPartial(boundarystart) ) {
}
}
}
close: function() {
this.read(null);
}
});
if ( typeof this.onbody === "function" )
this.onbody(body);
body.close();
};
function FixedLengthStream(readableStream, contentLength) {
var offset = 0;
return Stream.create(SubStreamStream.prototype, {
read: function(buffer, off, length) {
length = Math.min(length, contentLength - offset);
var read = readableStream.read(buffer, off, length);
if ( read < 0 )
throw "Stream returned EOF early.";
return read;
},
close: function() {
while ( offset < contentLength ) {
var skipped = readableStream.skip(contentLength - offset);
if ( skipped < 0 )
throw "Stream returned EOF early.";
offset += skipped;
}
}
});
}
SubStreamStream.prototype = Object.create(Stream); // You can ignore my ES5isms too.
function HttpHandler() {
this.maxRequestLineLength = Infinity;
this.maxContentLength = Infinity;
this.maxHeaderLength = Infinity;
this.maxHeaders = Infinity;
}
HttpHandler.prototype.handle = function handle(socket) {
var s = socket.open("t");
var requestLine = s.readTo("\n".charCodeAt(0), this.maxRequestURILength);
if ( !requestLine.endsWith("\n".charCodeAt(0)) ) // Ignore my endsWith, that's something I'd prototype on myself
throw "Request line went over the limit.";
requestLine = requestLine.toString("US-ASCII");
var m = requestLine.match(/([A-Z]+)\s+(.+)\s+(.+)/);
if ( !m )
throw "Invalid request line.";
var req = {};
req.method = m[1].toUpperCase();
req.requestURI = m[2];
req.protocol = m[3];
req.headers = {};
var headerName;
for(var headers = 0;; headers++) {
if ( headers > this.maxHeaders )
throw "To many headers sent with request.";
var headerLine = s.readTo("\n".charAt(0), this.maxHeaderlength);
if ( !headerLine.endsWith("\n".charAt(0)) )
throw "Header line went over the size limit.";
headerLine = headerLine.toString("US-ASCII");
if ( !headerLine )
// Blank line, end of headers
break;
if ( headerName && headerLine.match(/^\s+/) ) {
req.headers[headerName] += headerLine.replace(/^\s+/, "\n ");
continue;
}
var m = headerLine.match(/^([^()<>@,;:\\"\/\[\]?={} \t]):\s*(.*)$/);
if ( !m ) {
// Invalid header, ignore
http.errors
req.errors = req.errors || [];
req.errors.push("Invalid header line: "+headerLine);
continue;
}
var [, headerName, headerValue] = m;
headerName = headerName.toLowerCase();
if ( headerName in req.headers )
req.headers[headerName] += "\n" + headerValue;
else
req.headers[headerName] = headerValue;
}
req.body = false;
if ( req.headers["content-length"] && /^\d+$/.test(req.headers["content-length"]) ) {
if ( parseInt(req.headers["content-length"], 10) > this.maxContentLength )
throw "HTTP Content length too big.";
req.body = new FixedLengthStream(s, parseInt(req.headers["content-length"], 10));
}
return true;
};
var running = true;
var server = new Socket("tcp"); // Accepts tcp, udp, possibly unix to, defaults to tcp; Extra optional numeric arg specifies backlog
server.bind(8080); // Accepts (hostname), (hostname, port), (port)
server.blocking = true;
while(running) {
var socket = server.accept(1000); // When .blocking argument optionally specifies timeout; ommit, -1, and Infinity block forever
if ( !socket )
continue;
var http = new HttpHandler();
http.maxRequestLineLength =
http.maxHeaderLength = 1 * 1024; // 1KB
http.maxContentLength = 2 * 1024 * 1024; // 2GB
http.maxHeaders = 100;
var req = http.handle(socket);
var m = (req.headers["content-type"]||"").match(/^multipart\/form-data;.+boundary=(?:"(.+?)"|([^;]))/);
if ( this.method === "POST" && m ) {
var form = {};
var multipart = new MultipartStreamParser();
multipart.boundary = m[1] || m[2];
multipart.maxHeaderLength = 1 * 1024; // 1KB
multipart.maxHeaders = 100;
var headers;
multipart.onstartheaders = function() {
headers = {};
};
multiparts.onheader = function(headerName, headerValue) {
headerName = headerName.toLowerCase();
if ( headerName in headers )
headers[headerName] += "\n" + headerValue;
else
headers[headerName] = headerValue;
};
multipart.onbody = function(body) {
var m = (headers["content-disposition"]||"").match(/form-data;.+name=(?:"(.+?)"|([^;]))/);
if ( !m )
return;
var name = m[1] || m[2];
var f = File.createTemporary(name).deleteOnExit();
var s = f.open("wb");
form[name] = form[name] || [];
form[name].push(f);
StreamHelpers.pipe(body, s); // This one is just a user level thing I'd probably write to simplify piping one stream to another
};
multipart.onend = function() {
// Do our work here
};
multipart.parse(req.body);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment