Created
March 7, 2010 19:29
-
-
Save dantman/324584 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// -*- 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