diff --git a/cgi.d b/cgi.d index 2c2086d..f831054 100644 --- a/cgi.d +++ b/cgi.d @@ -57,6 +57,8 @@ mixin template ForwardCgiConstructors() { this(string[] headers, immutable(ubyte)[] data, string address, void delegate(const(ubyte)[]) _rawDataOutput = null, int pathInfoStarts = 0, void delegate() _flush = null) { super(headers, data, address, _rawDataOutput, pathInfoStarts, _flush); } + + this(BufferedInputRange ir) { super(ir); } } @@ -729,6 +731,96 @@ class Cgi { // This space intentionally left blank. } + /// Initializes the cgi from completely raw HTTP data. The ir must have a Socket source. + this(BufferedInputRange ir) { + import al = std.algorithm; + + auto idx = al.indexOf(ir.front(), "\r\n\r\n"); + while(idx == -1) { + ir.popFront(0); + idx = al.indexOf(ir.front(), "\r\n\r\n"); + } + + assert(idx != -1); + + string[] headers; + foreach(line; al.splitter(ir.front()[0 .. idx], "\r\n")) + if(line.length) + headers ~= cast(string)(line.idup); + + ir.consume(idx + 4); + + + bool chunked = false; + bool closeConnection = false; + int contentLength; + + // FIXME: integrate all this below + + if(headers[0].indexOf("HTTP/1.0") != -1) + closeConnection = true; // always one request per connection with 1.0 + + foreach(h; headers[1..$]) { + auto colon = h.indexOf(":"); + if(colon == -1) + throw new Exception("Http headers need colons " ~ h); + string name = h[0..colon].toLower; + string value = h[colon+2..$]; // FIXME? + + switch(name) { + case "transfer-encoding": + if(value == "chunked") + chunked = true; + break; + case "content-length": + contentLength = to!int(value); + break; + case "connection": + if(value == "close") + closeConnection = true; + break; + default: + } + } + + immutable(ubyte)[] data; + void log(in ubyte[] d) { + data ~= d; + } + + auto a = ir.front(); + // reading Content-Length type data + // We need to read up the data we have, and write it out as a chunk. + if(!chunked) { + if(a.length <= contentLength) { + log(a); + contentLength -= a.length; + a = ir.consume(a.length); + // we just finished it off, terminate the chunks + if(contentLength == 0) { + // goto finish; + } else { + ir.popFront(); + a = ir.front(); + } + } else { + // we actually have *more* here than we need.... + log(a[0..contentLength]); + contentLength = 0; + ir.consume(contentLength); + // goto finish; + } + } else { + dechunk(contentLength, &log, ir); + } + + void rdo(const(ubyte)[] d) { + sendAll(ir.source, d); + } + + this(headers, data, ir.source.remoteAddress().toString(), &rdo); + } + /** Initializes it from some almost* raw HTTP request data headers[0] should be the "GET / HTTP/1.1" line @@ -1222,10 +1314,11 @@ class Cgi { } if(!autoBuffer || isAll) { if(rawDataOutput !is null) - if(nph && responseChunked) + if(nph && responseChunked) { rawDataOutput(makeChunk(cast(const(ubyte)[]) t)); - else + } else { rawDataOutput(cast(const(ubyte)[]) t); + } else stdout.rawWrite(t); } @@ -1573,6 +1666,28 @@ version(embedded_httpd) return; } + version(httpd2) { + // this might replace embedded_httpd entirely in the near future + auto manager = new ListeningConnectionManager(8085); + foreach(connection; manager) { + scope(failure) { + // FIXME + sendAll(connection, "HTTP/1.0 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n"); + connection.close(); + } + auto ir = new BufferedInputRange(connection); + while(!ir.empty) { + auto cgi = new Cgi(ir); + fun(cgi); + cgi.close(); + cgi.dispose(); + + if(!ir.empty) + ir.popFront(); // get the next???? + } + } + } + version(scgi) { import std.exception; import al = std.algorithm; @@ -1920,7 +2035,7 @@ class BufferedInputRange { if(minBytesToSettleFor > underlyingBuffer.length || view.length == underlyingBuffer.length) { if(allowGrowth) { auto viewStart = view.ptr - underlyingBuffer.ptr; - auto growth = 4096; + size_t growth = 4096; // make sure we have enough for what we're being asked for if(minBytesToSettleFor - underlyingBuffer.length > growth) growth = minBytesToSettleFor - underlyingBuffer.length; @@ -2017,6 +2132,7 @@ class ListeningConnectionManager { auto sn = listener.accept(); auto thread = new ConnectionThread(sn, &broken, dg); thread.start(); + // broken = dg(sn); } return broken; @@ -2029,6 +2145,8 @@ void sendAll(Socket s, const(void)[] data) { ptrdiff_t amount; do { amount = s.send(data); + if(amount == Socket.ERROR) + throw new Exception("wtf in send"); data = data[amount .. $]; } while(data.length); } @@ -2116,3 +2234,94 @@ Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) */ + + +void dechunk(int contentLength, void delegate(in ubyte[]) log, BufferedInputRange ir) { + another_chunk: + // If here, we are at the beginning of a chunk. + auto a = ir.front(); + int chunkSize; + int loc = locationOf(a, "\r\n"); + while(loc == -1) { + ir.popFront(); + a = ir.front(); + loc = locationOf(a, "\r\n"); + } + + string hex; + hex = ""; + for(int i = 0; i < loc; i++) { + char c = a[i]; + if(c >= 'A' && c <= 'Z') + c += 0x20; + if((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z')) { + hex ~= c; + } else { + break; + } + } + + assert(hex.length); + + int power = 1; + int size = 0; + foreach(cc1; retro(hex)) { + dchar cc = cc1; + if(cc >= 'a' && cc <= 'z') + cc -= 0x20; + int val = 0; + if(cc >= '0' && cc <= '9') + val = cc - '0'; + else + val = cc - 'A' + 10; + + size += power * val; + power *= 16; + } + + chunkSize = size; + assert(size >= 0); + + if(loc + 2 > a.length) { + ir.popFront(0, a.length + loc + 2); + a = ir.front(); + } + + a = ir.consume(loc + 2); + + if(chunkSize == 0) { // we're done with the response + // if we got here, will change must be true.... + more_footers: + loc = locationOf(a, "\r\n"); + if(loc == -1) { + ir.popFront(); + a = ir.front; + goto more_footers; + } else { + assert(loc == 0); + ir.consume(loc + 2); + goto finish; + } + } else { + // if we got here, will change must be true.... + if(a.length < chunkSize + 2) { + ir.popFront(0, chunkSize + 2); + a = ir.front(); + } + + log(a[0..chunkSize]); + + if(!(a.length > chunkSize + 2)) { + ir.popFront(0, chunkSize + 2); + a = ir.front(); + } + assert(a[chunkSize] == 13); + assert(a[chunkSize+1] == 10); + a = ir.consume(chunkSize + 2); + chunkSize = 0; + goto another_chunk; + } + + finish: + return; +}