use strict; use warnings; package App::DubiousHTTP::Tests::Broken; use App::DubiousHTTP::Tests::Common; SETUP( 'broken', "Various broken responses", <<'DESC', This test tries various kinds of broken HTTP responses like DESC # ------------------------- Tests ---------------------------------------- [ MUSTBE_VALID, 'ok' => 'VALID: simple request with content-length'], [ UNCOMMON_VALID, 'http09' => 'HTTP 0.9 response (no header)'], [ INVALID, '\012http09' => 'HTTP 0.9 response (no header) prefix with \n'], [ INVALID, '\012\012http09' => 'HTTP 0.9 response (no header) prefix with \n\n'], [ INVALID, 'HTT\012\012http09' => 'response prefixed with HTT\n\n'], [ INVALID, 'HTTP\012\012http09' => 'response prefixed with HTTP\n\n'], [ INVALID, 'hTtp\012\012http09' => 'response prefixed with hTtp\n\n'], [ INVALID, 'HTTP\012http09' => 'response prefixed with HTTP\n'], [ INVALID, 'hTtp\012http09' => 'response prefixed with hTtp\n'], [ INVALID, 'HTTP/\012\012http09' => 'response prefixed with HTTP/\n\n'], [ INVALID, 'HTTP.\012\012http09' => 'response prefixed with HTTP.\n\n'], [ INVALID, 'HTTPx\012\012http09' => 'response prefixed with HTTPx\n\n'], [ INVALID, 'HTTP/1.1\040100\040ok\012\012http09' => 'response prefixed with HTTP/1.1 100 ok\n\n'], [ INVALID, 'HTTP/1.1\040\053100\040ok\012\012http09' => 'response prefixed with HTTP/1.1 +100 ok\n\n'], [ 'INVALID: junk data around transfer-encoding' ], [ INVALID, 'chunked;177' => 'Transfer-Encoding: chunked\r\n\177\r\n, served chunked' ], [ INVALID, 'hdrfirst;space;chunked' => 'Transfer-Encoding: chunked as first header line, served chunked' ], [ INVALID, 'hdrfirst;tab;chunked' => 'Transfer-Encoding: chunked as first header line, served chunked' ], [ INVALID, 'hdrfirst;space;chunked;do_clen' => 'Transfer-Encoding: chunked as first header line, not served chunked' ], [ INVALID, 'hdrfirst;tab;chunked;do_clen' => 'Transfer-Encoding: chunked as first header line, not served chunked' ], [ INVALID, 'hdrfirst;space;chunked;do_close' => 'Transfer-Encoding: chunked as first header line, not served chunked and no content-length' ], [ INVALID, 'hdrfirst;tab;chunked;do_close' => 'Transfer-Encoding: chunked as first header line, not served chunked and no content-length' ], [ INVALID, 'somehdr;space;chunked' => 'Transfer-Encoding: chunked as continuation of some header line, served chunked' ], [ INVALID, 'somehdr;tab;chunked' => 'Transfer-Encoding: chunked as continuation of some header line, served chunked' ], [ UNCOMMON_VALID, 'somehdr;space;chunked;do_clen' => 'Transfer-Encoding: chunked as continuation of some header line, not served chunked' ], [ UNCOMMON_VALID, 'somehdr;tab;chunked;do_clen' => 'Transfer-Encoding: chunked as continuation of some header line, not served chunked' ], [ COMMON_INVALID, '8bitkey;chunked' => 'line using 8bit field name, followed by TE chunked, served chunked'], [ INVALID, '8bitkey;chunked;do_clen' => 'line using 8bit field name, followed by TE chunked, not served chunked'], [ COMMON_INVALID, 'data:foo:b\001ar\015\012;chunked' => 'line using binary value, followed by TE chunked, served chunked'], [ INVALID, 'data:foo:b\001ar\015\012;chunked;do_clen' => 'line using binary value, followed by TE chunked, not served chunked'], [ COMMON_INVALID, 'data:foo:b\000ar\015\012;chunked' => 'line using binary value \000, followed by TE chunked, served chunked'], [ INVALID, 'data:foo:b\000ar\015\012;chunked;do_clen' => 'line using binary value \000, followed by TE chunked, not served chunked'], [ COMMON_INVALID, 'data:foo:b\200ar\015\012;chunked' => 'line using 8bit value, followed by TE chunked, served chunked'], [ INVALID, 'data:foo:b\200ar\015\012;chunked;do_clen' => 'line using 8bit value, followed by TE chunked, not served chunked'], [ INVALID, 'colon;chunked' => 'line with empty field name (single colon on line), followed by TE chunked, served chunked'], [ INVALID, 'colon;chunked;do_clen' => 'line with empty field name (single colon on line), followed by TE chunked, not served chunked'], [ INVALID, '177;chunked' => 'line \177\r\n, followed by TE chunked, served chunked' ], [ INVALID, '177;chunked;do_clen' => 'line \177\r\n, followed by TE chunked, not served chunked' ], [ INVALID, 'data:\000;chunked' => 'line \000\r\n, followed by TE chunked, served chunked' ], [ INVALID, 'data:\000;chunked;do_clen' => 'line \000\r\n, followed by TE chunked, not served chunked' ], [ COMMON_INVALID, 'junkline;chunked' => 'ASCII junk line w/o colon, followed by TE chunked, served chunked'], [ INVALID, 'junkline;chunked;do_clen' => 'ASCII junk line w/o colon, followed by TE chunked, not served chunked'], [ COMMON_INVALID, 'spacehdr;chunked' => 'header containing space, followed by TE chunked, served chunked'], [ INVALID, 'spacehdr;chunked;do_clen' => 'header containing space, followed by TE chunked, not served chunked'], [ COMMON_INVALID, 'conthdr;chunked' => 'header with continuation line, followed by TE chunked, served chunked'], [ INVALID, 'conthdr;chunked;do_clen' => 'header with continuation line, followed by TE chunked, not served chunked'], [ INVALID, 'cr;chunked' => 'line just containing : \r\r\n, followed by TE chunked'], [ INVALID, 'lf;chunked' => 'line just containing : \n\r\n, followed by TE chunked'], [ INVALID, 'crcr;chunked' => 'line just containing : \r\r\r\n, followed by TE chunked'], [ INVALID, 'lfcr;chunked' => 'line just containing : \n\r\r\n, followed by TE chunked'], [ INVALID, 'crcronly;chunked' => 'TE chunked prefixed with ,served chunked' ], [ INVALID, 'cr-cronly;chunked' => 'TE chunked prefixed with , served chunked' ], [ INVALID, 'crcronly;chunked;do_clen' => 'TE chunked prefixed with , not served chunked' ], [ INVALID, 'cr-cronly;chunked;do_clen' => 'TE chunked prefixed with , not served chunked' ], [ INVALID, 'lflfonly;chunked' => 'TE chunked prefixed with ,served chunked' ], [ INVALID, 'lf-lfonly;chunked' => 'TE chunked prefixed with , served chunked' ], [ INVALID, 'lflfonly;chunked;do_clen' => 'TE chunked prefixed with , not served chunked' ], [ INVALID, 'lf-lfonly;chunked;do_clen' => 'TE chunked prefixed with , not served chunked' ], [ INVALID, 'crlf-crlfonly;chunked' => 'TE chunked prefixed with , served chunked' ], [ INVALID, 'crlf-crlfonly;chunked;do_clen' => 'TE chunked prefixed with , not served chunked' ], [ INVALID, 'te:chu\000nked;do_chunked' => '"Transfer-Encoding:chu\000nked", served chunked' ], [ INVALID, 'te:chu\015\012\040nked;do_chunked' => '"Transfer-Encoding:chu\r\n nked", served chunked' ], [ INVALID, 'te:chu\015\012\040nked;do_clen' => '"Transfer-Encoding:chu\r\n nked", not served chunked' ], [ INVALID, 'te:chu\015\012nked;do_chunked' => '"Transfer-Encoding:chu\r\nnked", served chunked' ], [ INVALID, 'te:chu\015\012nked;do_clen' => '"Transfer-Encoding:chu\r\nnked", not served chunked' ], [ INVALID, 'data:Transfer\000-encoding:chunked\015\012;do_chunked' => '"Transfer\000-Encoding:chunked", served chunked' ], [ INVALID, 'data:Transfer\000-encoding:chun\000ked\015\012;do_chunked' => '"Transfer\000-Encoding:chun\000ked", served chunked' ], [ INVALID, 'te\000:chunked;do_chunked' => '"Transfer-Encoding\000:chunked", served chunked' ], [ INVALID, 'te\040\011\040\011\040:chunked;do_chunked' => '"Transfer-Encoding \t \t :chunked", served chunked' ], [ INVALID, 'te\013:chunked;do_chunked' => '"Transfer-Encoding\v:chunked", served chunked' ], [ INVALID, 'te\014:chunked;do_chunked' => '"Transfer-Encoding\f:chunked", served chunked' ], [ INVALID, 'te\012\040:chunked;do_chunked' => '"Transfer-Encoding\n :chunked", served chunked' ], [ INVALID, 'te\015\040:chunked;do_chunked' => '"Transfer-Encoding\r :chunked", served chunked' ], [ INVALID, 'te\015\012\040:chunked;do_chunked' => '"Transfer-Encoding\r\n :chunked", served chunked' ], [ INVALID, 'te\015\012\040:\015\012\040chunked;do_chunked' => '"Transfer-Encoding\r\n :\r\n chunked", served chunked' ], [ INVALID, 'te\015\012\040\015\012\040:\015\012\040chunked;do_chunked' => '"Transfer-Encoding\r\n \r\n :\r\n chunked", served chunked' ], [ INVALID, 'te\012\000:chunked;do_chunked' => '"Transfer-Encoding\n\000:chunked", served chunked' ], [ INVALID, 'te:,chunked;do_chunked' => '"Transfer-Encoding:,chunked", served chunked' ], [ INVALID, 'te:\073chunked;do_chunked' => '"Transfer-Encoding:;chunked", served chunked' ], [ INVALID, 'te:\000chunked;do_chunked' => '"Transfer-Encoding:\000chunked", served chunked' ], [ INVALID, 'te:\013chunked;do_chunked' => '"Transfer-Encoding:\vchunked", served chunked' ], [ INVALID, 'te:\014chunked;do_chunked' => '"Transfer-Encoding:\fchunked", served chunked' ], [ INVALID, 'te:\240chunked;do_chunked' => '"Transfer-Encoding:\240chunked", served chunked' ], [ INVALID, 'te:\012\000chunked;do_chunked' => '"Transfer-Encoding:\n\000chunked", served chunked' ], [ INVALID, 'te:\012\013chunked;do_chunked' => '"Transfer-Encoding:\n\vchunked", served chunked' ], [ INVALID, 'te:\012\014chunked;do_chunked' => '"Transfer-Encoding:\n\fchunked", served chunked' ], [ INVALID, 'te:\012\240chunked;do_chunked' => '"Transfer-Encoding:\n\240chunked", served chunked' ], [ INVALID, 'te:chunked,;do_chunked' => '"Transfer-Encoding:chunked,", served chunked' ], [ INVALID, 'te:chunked\073;do_chunked' => '"Transfer-Encoding:chunked;", served chunked' ], [ INVALID, 'te:chunked\000;do_chunked' => '"Transfer-Encoding:chunked\000", served chunked' ], [ INVALID, 'te:\177chunked;do_chunked' => '"Transfer-Encoding:\177chunked", served chunked' ], [ INVALID, 'te:chunked\177;do_chunked' => '"Transfer-Encoding:chunked\177", served chunked' ], [ INVALID, 'te:chu\177nked;do_chunked' => '"Transfer-Encoding:chu\177nked", served chunked' ], [ INVALID, 'te:\357\273\277chunked;do_chunked' => '"Transfer-Encoding:chunked", served chunked' ], [ INVALID, 'te:\302\204chunked;do_chunked' => '"Transfer-Encoding:chunked", served chunked' ], [ INVALID, 'ce:gz\000ip;do_gzip' => '"Content-Encoding:gz\000ip", served gzipped' ], [ INVALID, 'data:Content\000-encoding:gzip\015\012;do_gzip' => '"Content\000-Encoding:gzip", served gzipped' ], [ INVALID, 'ce\000:gzip;do_gzip' => '"Content-Encoding\000:gzip", served gzipped' ], [ INVALID, 'ce\015\012\040:gzip;do_gzip' => '"Content-Encoding\r\n :gzip", served gzipped' ], [ INVALID, 'ce\015\012\040:\015\012\040gzip;do_gzip' => '"Content-Encoding\r\n :\r\n gzip", served gzipped' ], [ INVALID, 'ce\013:gzip;do_gzip' => '"Content-Encoding\v:gzip", served gzipped' ], [ INVALID, 'ce\014:gzip;do_gzip' => '"Content-Encoding\f:gzip", served gzipped' ], [ INVALID, 'ce:,gzip;do_gzip' => '"Content-Encoding:,gzip", served gzipped' ], [ INVALID, 'ce:\073gzip;do_gzip' => '"Content-Encoding:;gzip", served gzipped' ], [ INVALID, 'ce:\000gzip;do_gzip' => '"Content-Encoding:\000gzip", served gzipped' ], [ INVALID, 'ce:\013gzip;do_gzip' => '"Content-Encoding:\vgzip", served gzipped' ], [ INVALID, 'ce:\014gzip;do_gzip' => '"Content-Encoding:\fgzip", served gzipped' ], [ INVALID, 'ce:\240gzip;do_gzip' => '"Content-Encoding:\240gzip", served gzipped' ], [ INVALID, 'ce:\012\000gzip;do_gzip' => '"Content-Encoding:\n\000gzip", served gzipped' ], [ INVALID, 'ce:\012\013gzip;do_gzip' => '"Content-Encoding:\n\vgzip", served gzipped' ], [ INVALID, 'ce:\012\014gzip;do_gzip' => '"Content-Encoding:\n\fgzip", served gzipped' ], [ INVALID, 'ce:\012\240gzip;do_gzip' => '"Content-Encoding:\n\240gzip", served gzipped' ], [ INVALID, 'ce:gzip,;do_gzip' => '"Content-Encoding:gzip,", served gzipped' ], [ INVALID, 'ce:gzip\073;do_gzip' => '"Content-Encoding:gzip;", served gzipped' ], [ INVALID, 'ce:gzip\000;do_gzip' => '"Content-Encoding:gzip\000", served gzipped' ], [ INVALID, 'ce:gzip\013;do_gzip' => '"Content-Encoding:gzip\v", served gzipped' ], [ INVALID, 'ce:gzip\014;do_gzip' => '"Content-Encoding:gzip\f", served gzipped' ], [ INVALID, 'ce:gzip\240;do_gzip' => '"Content-Encoding:gzip\240", served gzipped' ], [ INVALID, 'ce:def\000late;do_deflate' => '"Content-Encoding:def\000late", served with deflate' ], [ INVALID, 'data:Content\000-encoding:deflate\015\012;do_deflate' => '"Content\000-Encoding:deflate", served with deflate' ], [ INVALID, 'ce\000:deflate;do_deflate' => '"Content-Encoding\000:deflate", served with deflate' ], [ INVALID, 'ce\015\012\040:deflate;do_deflate' => '"Content-Encoding\r\n :deflate", served with deflate' ], [ INVALID, 'ce\015\012\040:\015\012\040deflate;do_deflate' => '"Content-Encoding\r\n :\r\n deflate", served with deflate' ], [ INVALID, 'ce\013:deflate;do_deflate' => '"Content-Encoding\v:deflate", served with deflate' ], [ INVALID, 'ce\014:deflate;do_deflate' => '"Content-Encoding\f:deflate", served with deflate' ], [ INVALID, 'ce:,deflate;do_deflate' => '"Content-Encoding:,deflate", served with deflate' ], [ INVALID, 'ce:\073deflate;do_deflate' => '"Content-Encoding:;deflate", served with deflate' ], [ INVALID, 'ce:\000deflate;do_deflate' => '"Content-Encoding:\000deflate", served with deflate' ], [ INVALID, 'ce:\013deflate;do_deflate' => '"Content-Encoding:\vdeflate", served with deflate' ], [ INVALID, 'ce:\014deflate;do_deflate' => '"Content-Encoding:\fdeflate", served with deflate' ], [ INVALID, 'ce:\240deflate;do_deflate' => '"Content-Encoding:\240deflate", served with deflate' ], [ INVALID, 'ce:deflate,;do_deflate' => '"Content-Encoding:deflate,", served with deflate' ], [ INVALID, 'ce:\012\000deflate;do_deflate' => '"Content-Encoding:\n\000deflate", served with deflate' ], [ INVALID, 'ce:\012\013deflate;do_deflate' => '"Content-Encoding:\n\vdeflate", served with deflate' ], [ INVALID, 'ce:\012\014deflate;do_deflate' => '"Content-Encoding:\n\fdeflate", served with deflate' ], [ INVALID, 'ce:\012\240deflate;do_deflate' => '"Content-Encoding:\n\240deflate", served with deflate' ], [ INVALID, 'ce:deflate\073;do_deflate' => '"Content-Encoding:deflate;", served with deflate' ], [ INVALID, 'ce:deflate\000;do_deflate' => '"Content-Encoding:deflate\000", served with deflate' ], [ INVALID, 'ce:deflate\013;do_deflate' => '"Content-Encoding:deflate\v", served with deflate' ], [ INVALID, 'ce:deflate\014;do_deflate' => '"Content-Encoding:deflate\f", served with deflate' ], [ INVALID, 'ce:deflate\240;do_deflate' => '"Content-Encoding:deflate\240", served with deflate' ], [ 'INVALID: various broken responses' ], [ INVALID, 'emptycont' => 'empty continuation line'], [ INVALID, '8bitkey' => 'line using 8bit field name'], [ INVALID, 'colon' => 'line with empty field name (single colon on line)'], [ INVALID, 'data:\000' => 'line \000\r\n' ], [ INVALID, '177' => 'line \177\r\n' ], [ INVALID, '177;only' => 'line \177\r\n and then the body, no other header after status line' ], [ INVALID, 'junkline' => 'ASCII junk line w/o colon'], [ INVALID, 'spacehdr' => 'header containing space'], [ INVALID, 'cr' => 'line just containing : \r\r\n'], [ INVALID, 'lf' => 'line just containing : \n\r\n'], [ INVALID, 'crcr' => 'line just containing : \r\r\r\n'], [ INVALID, 'lfcr' => 'line just containing : \n\r\r\n'], [ INVALID, 'code-only' => 'status line stops after code, no phrase'], [ INVALID, 'http-lower' => 'version given as http/1.1 instead of HTTP/1.1'], [ INVALID, 'proto:HTTP/0.9' => 'version given as HTTP/0.9 instead of HTTP/1.1'], [ INVALID, 'proto:HTTP/1.10' => 'version given as HTTP/1.10 instead of HTTP/1.1'], [ INVALID, 'proto:HTTP/1.00' => 'version given as HTTP/1.00 instead of HTTP/1.0'], [ INVALID, 'proto:HTTP/1.01' => 'version given as HTTP/1.01 instead of HTTP/1.0'], [ INVALID, 'proto:HTTP/1.2' => 'version given as HTTP/1.2 instead of HTTP/1.1'], [ INVALID, 'proto:HTTP/2.0' => 'version given as HTTP/2.0 instead of HTTP/1.1'], [ INVALID, 'proto:HTTP/1.1-space' => 'HTTP/1.1+SPACE: space after version in status line'], [ INVALID, 'proto:HTTP/1.1-tab' => 'HTTP/1.1+TAB: tab after version in status line'], [ INVALID, 'proto:HTTP/1.1-cr' => 'HTTP/1.1+CR: \r after version in status line'], [ INVALID, 'proto:HTTP/1.1-lf' => 'HTTP/1.1+LF: \n after version in status line'], [ INVALID, "proto:space-HTTP/1.1" => 'version prefixed with space: SPACE+HTTP/1.1'], [ INVALID, 'proto:FTP/1.1' => 'version FTP/1.1 instead of HTTP/1.1'], [ INVALID, 'proto:ICY' => 'version ICY instead of HTTP/1.0'], [ INVALID, 'proto:ICY;gzip' => 'version ICY instead of HTTP/1.0 compressed with gzip'], [ INVALID, 'proto:HTTP\1.1' => 'version HTTP\1.1 instead of HTTP/1.1'], [ INVALID, 'proto:HTTP/1.010;chunked' => 'version HTTP/1.010 instead of HTTP/1.1 and chunked'], [ INVALID, 'proto:HTTP/1.010;chunked;do_clen' => 'version HTTP/0.010 instead of HTTP/1.1 and TE chunked but not served chunked'], [ INVALID, 'proto:HTTP/+1.+1;chunked' => 'version HTTP/+1.+1 instead of HTTP/1.1 and chunked'], [ INVALID, 'proto:HTTP/+1.+1;chunked;do_clen' => 'version HTTP/+1.+1 instead of HTTP/1.1 and TE chunked but not served chunked'], [ INVALID, 'proto:HTTP/1A.1B;chunked' => 'version HTTP/1A.1B instead of HTTP/1.1 and chunked'], [ INVALID, 'proto:HTTP/1A.1B;chunked;do_clen' => 'version HTTP/1A.1B instead of HTTP/1.1 and TE chunked but not served chunked'], [ INVALID, 'proto:HTTP/2.B;chunked' => 'version HTTP/2.B instead of HTTP/1.1 and chunked'], [ INVALID, 'proto:HTTP/2.B;chunked;do_clen' => 'version HTTP/2.B instead of HTTP/1.1 and TE chunked but not served chunked'], [ INVALID, 'proto:HTTP/9.-1;chunked' => 'version HTTP/9.-1 instead of HTTP/1.1 and chunked'], [ INVALID, 'proto:HTTP/9.-1;chunked;do_clen' => 'version HTTP/9.-1 instead of HTTP/1.1 and TE chunked but not served chunked'], [ INVALID, 'proto:HTTP/1\040.1;chunked' => 'version HTTP/1.1 instead of HTTP/1.1 and chunked'], [ INVALID, 'proto:HTTP/1\040.1;chunked;do_clen' => 'version HTTP/1.1 instead of HTTP/1.1 and TE chunked but not served chunked'], [ INVALID, 'proto:HTTP/A.B;gzip' => 'version HTTP/A.B instead of HTTP/1.0 and gzipped'], [ INVALID, 'status:HTTP/1.1' => 'HTTP/1.1 without code'], [ INVALID, 'status:HTTP/1.1(cr)Transfer-Encoding:chunked;do_clen' => 'HTTP/1.1\rTransfer-Encoding:chunked, not served chunked'], [ INVALID, 'status:HTTP/1.1(cr)Transfer-Encoding:chunked;do_chunked' => 'HTTP/1.1\rTransfer-Encoding:chunked, served chunked'], [ INVALID, 'status:HTTP/1.1(lf)Transfer-Encoding:chunked;do_clen' => 'HTTP/1.1\nTransfer-Encoding:chunked, not served chunked'], [ INVALID, 'status:HTTP/1.1(lf)Transfer-Encoding:chunked;do_chunked' => 'HTTP/1.1\nTransfer-Encoding:chunked, served chunked'], [ INVALID, 'status:HTTP/1.1(cr)(lf)Transfer-Encoding:chunked;do_clen' => 'HTTP/1.1\r\nTransfer-Encoding:chunked, not served chunked'], [ INVALID, 'status:HTTP/1.1(cr)(lf)Transfer-Encoding:chunked;do_chunked' => 'HTTP/1.1\r\nTransfer-Encoding:chunked, served chunked'], [ INVALID, 'status:HTTP/1.1(space)200(cr)Transfer-Encoding:chunked;do_clen' => 'HTTP/1.1 200\rTransfer-Encoding:chunked, not served chunked'], [ INVALID, 'status:HTTP/1.1(space)200(cr)Transfer-Encoding:chunked;do_chunked' => 'HTTP/1.1 200\rTransfer-Encoding:chunked, served chunked'], [ INVALID, 'status:HTTP/1.1(space)200(lf)Transfer-Encoding:chunked;do_clen' => 'HTTP/1.1 200\nTransfer-Encoding:chunked, not served chunked'], [ INVALID, 'status:HTTP/1.1(space)200(lf)Transfer-Encoding:chunked;do_chunked' => 'HTTP/1.1 200\nTransfer-Encoding:chunked, served chunked'], [ INVALID, 'status:HTTP/1.1(space)200(space)(cr)Transfer-Encoding:chunked;do_clen' => 'HTTP/1.1 200 \rTransfer-Encoding:chunked, not served chunked'], [ INVALID, 'status:HTTP/1.1(space)200(space)(cr)Transfer-Encoding:chunked;do_chunked' => 'HTTP/1.1 200 \rTransfer-Encoding:chunked, served chunked'], [ INVALID, 'status:HTTP/1.1(space)200(space)(lf)Transfer-Encoding:chunked;do_clen' => 'HTTP/1.1 200 \nTransfer-Encoding:chunked, not served chunked'], [ INVALID, 'status:HTTP/1.1(space)200(space)(lf)Transfer-Encoding:chunked;do_chunked' => 'HTTP/1.1 200 \nTransfer-Encoding:chunked, served chunked'], [ INVALID, 'status:HTTP/1.1(space)200(space)ok(cr)Transfer-Encoding:chunked;do_clen' => 'HTTP/1.1 200 ok\rTransfer-Encoding:chunked, not served chunked'], [ INVALID, 'status:HTTP/1.1(space)200(space)ok(cr)Transfer-Encoding:chunked;do_chunked' => 'HTTP/1.1 200 ok\rTransfer-Encoding:chunked, served chunked'], [ INVALID, 'status:HTTP/1.1(space)200(space)ok(lf)Transfer-Encoding:chunked;do_clen' => 'HTTP/1.1 200 ok\nTransfer-Encoding:chunked, not served chunked'], [ INVALID, 'status:HTTP/1.1(space)200(space)ok(lf)Transfer-Encoding:chunked;do_chunked' => 'HTTP/1.1 200 ok\nTransfer-Encoding:chunked, served chunked'], [ INVALID, 'status:\000HTTP/1.1(space)200(space)ok;chunked' => '\000HTTP/1.1 200 ok\r\n and chunked'], [ INVALID, 'status:HTT\000P/1.1(space)200(space)ok;chunked' => 'HTT\000P/1.1 200 ok\r\n and chunked'], [ INVALID, 'status:HTTP/1\000.1(space)200(space)ok;chunked' => 'HTTP/1\000.1 200 ok\r\n and chunked'], [ INVALID, 'status:HTTP/1.1\000(space)200(space)ok;chunked' => 'HTTP/1.1\000 200 ok\r\n and chunked'], [ INVALID, 'status:HTTP/1.1(space)2\00000(space)ok;chunked' => 'HTTP/1.1 2\00000 ok\r\n and chunked'], [ INVALID, 'status:HTTP/1.1(space)200\000(space)ok;chunked' => 'HTTP/1.1 200\000 ok\r\n and chunked'], [ INVALID, 'status:HTTP/1.1\000200\000ok;chunked' => 'HTTP/1.1\000200\000ok\r\n and chunked'], [ INVALID, 'status:HTTP/1.1\013200\013ok;chunked' => 'HTTP/1.1\013200\013ok\r\n and chunked'], [ INVALID, 'status:HTTP/1.1(space)-65336(space);chunked' => 'HTTP/1.1 -65336 ok\r\n and chunked'], # uint_16 -> 200 [ INVALID, 'status:HTTP/1.1foobar;chunked' => 'HTTP/1.1foobar\r\n and chunked'], [ INVALID, 'status:HTTP/1.1foobar(cr)Transfer-Encoding:chunked;do_chunked' => 'HTTP/1.1foobar\r and chunked'], [ INVALID, 'status:HTTP/1.foobar;chunked' => 'HTTP/1.foobar\r\n and chunked'], [ INVALID, 'status:HTTP/1foobar;chunked' => 'HTTP/1foobar\r\n and chunked'], [ INVALID, 'status:HTTP/foobar;chunked' => 'HTTP/foobar\r\n and chunked'], [ INVALID, 'status:HTTP/;chunked' => 'HTTP/\r\n and chunked'], [ INVALID, 'status:HTTP;chunked' => 'HTTP\r\n and chunked'], [ INVALID, 'status:HTTP/1.1\011204(space)ok;chunked' => 'HTTP/1.1\t204 ok with chunked content'], [ INVALID, 'status:HTTP/1.1\011304(space)ok;chunked' => 'HTTP/1.1\t304 ok with chunked content'], [ INVALID, 'status:HTTP/1.1\040\040\040\040204(space)ok;chunked' => 'HTTP/1.1 204 ok with chunked content'], [ INVALID, 'status:HTTP/1.1\040\040\040\040304(space)ok;chunked' => 'HTTP/1.1 304 ok with chunked content'], [ INVALID, 'status:HTTP\1.1(space)200(space)ok;chunked' => 'HTTP\1.1 instead of HTTP/1.1 with chunked content'], [ INVALID, 'status:Transfer-Encoding:chunked;do_clen' => 'no status line but Transfer-Encoding:chunked, not served chunked'], [ INVALID, 'status:Transfer-Encoding:chunked;do_chunked' => 'no status line but Transfer-Encoding:chunked, served chunked'], [ INVALID, 'cr-no-crlf' => 'single \r instead of \r\n' ], [ INVALID, 'cr-no-crlf;end-crlflf' => 'single \r instead of \r\n, but end \r\n\n' ], [ INVALID, 'cr-no-crlf;end-crlfcrlf' => 'single \r instead of \r\n, but end \r\n\r\n' ], [ INVALID, 'lf-no-crlf' => 'single \n instead of \r\n' ], [ INVALID, 'crcr-no-crlf' => '\r\r instead of \r\n' ], [ INVALID, 'lfcr-no-crlf' => '\n\r instead of \r\n' ], [ INVALID, 'cr\000lf-no-crlf' => '\r\000\n instead of \r\n' ], [ INVALID, 'chunked;cr-no-crlf' => 'single \r instead of \r\n and chunked' ], [ INVALID, 'chunked;cr-no-crlf;end-crlflf' => 'single \r instead of \r\n, but end \r\n\n and chunked' ], [ INVALID, 'chunked;cr-no-crlf;end-crlfcrlf' => 'single \r instead of \r\n, but end \r\n\r\n and chunked' ], [ INVALID, 'chunked;lf-no-crlf' => 'single \n instead of \r\n and chunked' ], [ INVALID, 'chunked;crcr-no-crlf' => '\r\r instead of \r\n and chunked' ], [ INVALID, 'chunked;crcr-no-crlf;end-crlfcrlf' => '\r\r instead of \r\n, but end \r\n\r\n and chunked' ], [ INVALID, 'chunked;lfcr-no-crlf' => '\n\r instead of \r\n and chunked' ], [ INVALID, 'chunked;lfcr-no-crlf;end-crlfcrlf' => '\n\r instead of \r\n, but end \r\n\r\n and chunked' ], [ INVALID, 'chunked;cr\000lf-no-crlf' => '\r\000\n instead of \r\n and chunked' ], [ INVALID, 'chunked;lfcrcrlf-no-crlf;end-crlfcrlf' => '\n\r\r\n instead of \r\n as line delimiter, but end \r\n\r\n and chunked' ], [ INVALID, 'end-crcr' => 'header end \r\r' ], [ INVALID, 'end-crlf\000crlf' => 'header end \r\n\000\r\n' ], [ INVALID, 'end-cr\000crlf' => 'header end \r\000\r\n' ], [ COMMON_INVALID, 'end-lflf' => 'header end \n\n' ], [ UNCOMMON_INVALID, 'end-lflf;chunked' => 'header end \n\n, chunked' ], [ UNCOMMON_INVALID, 'end-lflf;gzip' => 'header end \n\n, gzip' ], [ UNCOMMON_INVALID, 'end-lfcrlf' => 'header end \n\r\n' ], [ UNCOMMON_INVALID, 'end-lfcrlf;chunked' => 'header end \n\r\n, chunked' ], [ UNCOMMON_INVALID, 'end-lfcrlf;gzip' => 'header end \n\r\n, gzip' ], [ INVALID, 'end-lfcrcrlf' => 'header end \n\r\r\n' ], [ INVALID, 'end-lfcrcrlf;chunked' => 'header end \n\r\r\n, chunked' ], [ INVALID, 'end-lfcrcrlf;gzip' => 'header end \n\r\r\n, gzip' ], [ INVALID, 'end-lfcrcrlfcrlf' => 'header end \n\r\r\n\r\n' ], [ INVALID, 'end-lfcrcrlfcrlf;chunked' => 'header end \n\r\r\n\r\n, chunked' ], [ INVALID, 'end-lfcrcrlfcrlf;gzip' => 'header end \n\r\r\n\r\n, gzip' ], [ INVALID, 'end-lf\040lf' => 'header end \n\040\n' ], [ UNCOMMON_INVALID, 'end-lf\040lflf' => 'header end \n\040\n\n' ], [ UNCOMMON_VALID, 'end-crlf\040crlfcrlf' => 'header end \r\n\040\r\n\r\n' ], [ INVALID, 'chunked;end-lf\040lf' => 'header end \n\040\n and chunked' ], [ UNCOMMON_INVALID, 'chunked;end-lf\040lflf' => 'header end \n\040\n\n and chunked' ], [ UNCOMMON_VALID, 'chunked;end-crlf\040crlfcrlf' => 'header end \r\n\040\r\n\r\n and chunked' ], [ INVALID, 'gzip;end-lf\040lf' => 'header end \n\040\n and gzip' ], [ UNCOMMON_INVALID, 'gzip;end-lf\040lflf' => 'header end \n\040\n\n and gzip' ], [ UNCOMMON_VALID, 'gzip;end-crlf\040crlfcrlf' => 'header end \r\n\040\r\n\r\n and gzip' ], [ INVALID, 'end-lf\040.lf' => 'header end \n\040.\n' ], [ INVALID, 'end-lf\011lf' => 'header end \n\011\n' ], [ UNCOMMON_INVALID, 'end-lf\011lflf' => 'header end \n\011\n\n' ], [ UNCOMMON_VALID, 'end-crlf\011crlfcrlf' => 'header end \r\n\011\r\n\r\n' ], [ INVALID, 'chunked;end-lf\011lf' => 'header end \n\011\n and chunked' ], [ UNCOMMON_INVALID, 'chunked;end-lf\011lflf' => 'header end \n\011\n\n and chunked' ], [ UNCOMMON_VALID, 'chunked;end-crlf\011crlfcrlf' => 'header end \r\n\011\r\n\r\n and chunked' ], [ INVALID, 'gzip;end-lf\011lf' => 'header end \n\011\n and gzip' ], [ UNCOMMON_INVALID, 'gzip;end-lf\011lflf' => 'header end \n\011\n\n and gzip' ], [ UNCOMMON_VALID, 'gzip;end-crlf\011crlfcrlf' => 'header end \r\n\011\r\n\r\n and gzip' ], [ INVALID, 'end-lf\013lf' => 'header end \n\013\n' ], [ UNCOMMON_INVALID, 'end-lf\013lflf' => 'header end \n\013\n\n' ], [ UNCOMMON_VALID, 'end-crlf\013crlfcrlf' => 'header end \r\n\013\r\n\r\n' ], [ INVALID, 'chunked;end-lf\013lf' => 'header end \n\013\n and chunked' ], [ UNCOMMON_INVALID, 'chunked;end-lf\013lflf' => 'header end \n\013\n\n and chunked' ], [ UNCOMMON_VALID, 'chunked;end-crlf\013crlfcrlf' => 'header end \r\n\013\r\n\r\n and chunked' ], [ INVALID, 'gzip;end-lf\013lf' => 'header end \n\013\n and gzip' ], [ UNCOMMON_INVALID, 'gzip;end-lf\013lflf' => 'header end \n\013\n\n and gzip' ], [ UNCOMMON_VALID, 'gzip;end-crlf\013crlfcrlf' => 'header end \r\n\013\r\n\r\n and gzip' ], [ 'INVALID: redirect without location' ], [ INVALID, '300' => 'code 300 without location header'], [ INVALID, '301' => 'code 301 without location header'], [ INVALID, '302' => 'code 302 without location header'], [ INVALID, '303' => 'code 303 without location header'], [ INVALID, '305' => 'code 305 without location header'], [ INVALID, '307' => 'code 307 without location header'], [ INVALID, '308' => 'code 308 without location header'], [ 'INVALID: other status codes with invalid behavior' ], [ INVALID, '100' => 'code 100 with body'], [ INVALID, '101' => 'code 101 with body'], [ INVALID, '102' => 'code 102 with body'], [ INVALID, '204' => 'code 204 with body'], [ INVALID, '205' => 'code 205 with body'], [ INVALID, '206' => 'code 206 with body'], [ INVALID, '304' => 'code 304 with body'], [ INVALID, '401' => 'code 401 with body and no authorization requested'], [ INVALID, '407' => 'code 407 with body and no authorization requested'], [ 'VALID: other status codes with valid behavior' ], [ UNCOMMON_VALID, '400' => 'code 400 with body'], [ UNCOMMON_VALID, '403' => 'code 403 with body'], [ UNCOMMON_VALID, '404' => 'code 404 with body'], [ UNCOMMON_VALID, '406' => 'code 406 with body'], [ UNCOMMON_VALID, '500' => 'code 500 with body'], [ UNCOMMON_VALID, '502' => 'code 502 with body'], [ UNCOMMON_INVALID, '100+' => 'code 100 followed by real response'], [ INVALID, '100+b' => 'code 100 with body followed by real response'], [ INVALID, '16-100+' => 'code -65436(100) followed by real response'], [ INVALID, '16-100+b' => 'code -65436(100) with body followed by real response'], [ 'VALID: non-existing status codes' ], [ INVALID, '000' => 'code 000 with body'], [ INVALID, '600' => 'code 600 with body'], [ INVALID, '700' => 'code 700 with body'], [ INVALID, '800' => 'code 800 with body'], [ INVALID, '900' => 'code 900 with body'], [ 'INVALID: malformed status codes' ], [ INVALID, '2xx' => 'invalid status code with non-digits (2xx)'], [ INVALID, '20x' => 'invalid status code with non-digits (20x)'], [ INVALID, '2' => 'invalid status code, only single digit (2)'], [ INVALID, '20' => 'invalid status code, two digits (20)'], [ INVALID, '2000' => 'invalid status code, too much digits (2000)'], [ INVALID, '0200' => 'invalid status code, numeric (0200)'], [ INVALID, 'space-200' => 'invalid status code, SPACE+200)'], [ INVALID, 'tab-200' => 'invalid status code, TAB+200)'], [ 'INVALID: more variations with status codes' ], [ UNCOMMON_VALID, '299' => 'code 299'], [ INVALID, '204;chunked' => 'code 204 with chunked body'], [ INVALID, '0204' => 'code 0204 with body'], [ INVALID, '2040' => 'code 2040 with body'], [ INVALID, '304;chunked' => 'code 304 with chunked body'], [ INVALID, '0304' => 'code 0304 with body'], [ INVALID, '3040' => 'code 3040 with body'], [ 'VALID: new lines before HTTP header' ], [ UNCOMMON_VALID, 'crlf-header;chunked' => 'single before header, chunked'], [ UNCOMMON_VALID, 'crlf-crlf-header;chunked' => 'double before header, chunked'], [ 'INVALID: other stuff before HTTP header' ], [ INVALID, 'space-crlf-header;chunked' => 'space followed by single before header, chunked'], [ INVALID, 'space-tab-crlf-header;chunked' => 'space+TAB followed by single before header, chunked'], [ INVALID, 'space-crlf-tab-crlf-header;chunked' => 'space+CRLF+TAB+CRLF before header, chunked'], [ INVALID, 'space-tab-cr-crlf-header;chunked' => 'space+TAB+CR followed by single before header, chunked'], [ INVALID, 'foobar-crlf-header;chunked' => 'junk followed by single before header, chunked'], [ INVALID, 'SIP/2.0-space-200-space-ok-crlf-header;chunked' => '"SIP/2.0 200 ok" followed by single before header, chunked'], [ INVALID, 'space-foobar-crlf-header;chunked' => 'space+junk followed by single before header, chunked'], [ INVALID, 'space-SIP/2.0-space-200-space-ok-crlf-header;chunked' => 'space+"SIP/2.0 200 ok" followed by single before header, chunked'], [ INVALID, 'cr-header;chunked' => 'single before header, chunked'], [ INVALID, 'cr-cr-header;chunked' => 'double before header, chunked'], [ INVALID, 'space-cr-header;chunked' => 'space followed by single before header, chunked'], [ INVALID, 'lf-header;chunked' => 'single before header, chunked'], [ INVALID, 'lf-lf-header;chunked' => 'double before header, chunked'], [ INVALID, 'space-lf-header;chunked' => 'space followed by single before header, chunked'], [ INVALID, 'lfcr-header;chunked' => 'single before header, chunked'], # chrome accepts up to 4 bytes garbage before HTTP/ [ INVALID, 'H-header;chunked' => '"H" before header, chunked'], [ INVALID, 'HT-header;chunked' => '"HT" before header, chunked'], [ INVALID, 'HTT-header;chunked' => '"HTT" before header, chunked'], [ INVALID, 'HTTX-header;chunked' => '"HTTX" before header, chunked'], [ INVALID, 'HTTXY-header;chunked' => '"HTTXY" before header, chunked'], ); sub make_response { my ($self,$page,$spec) = @_; return make_index_page() if $page eq ''; my ($cthdr,$data) = content($page,$self->ID."-".$spec) or die "unknown page $page"; my $version = '1.1'; my $te = 'clen'; my $only = 0; my $code = 200; my $prefix = ''; my $statusline; my @transform; my $hdr = "Connection: close\r\n"; my $hdrfirst; for (split(';',$spec)) { if ( $_ eq 'emptycont' ) { $hdr .= "Foo: bar\r\n \r\n" } elsif ( $_ eq '8bitkey' ) { $hdr .= "Löchriger-Häddar: foobar\r\n" } elsif ( $_ eq 'spacehdr' ) { $hdr .= "Foo Bar: foobar\r\n" } elsif ( $_ eq 'conthdr' ) { $hdr .= "FooBar: foobar\r\n barfoot\r\n" } elsif ( $_ eq 'colon' ) { $hdr .= ": foo\r\n" } elsif ( $_ eq 'space' ) { $hdr .= " " } elsif ( $_ eq 'tab' ) { $hdr .= "\t" } elsif ( $_ eq 'somehdr') { $hdr .= "X-Foo: bar\r\n"; } elsif ( $_ eq 'hdrfirst') { $hdrfirst = 1; } elsif ( $_ eq 'junkline' ) { $hdr .= "qutqzdafsdshadsdfdshsdd sddfd\r\n" } elsif ( m{^(?:(cr|lf|-)+)(only)?$} ) { s{cr}{\r}g; s{lf}{\n}g; s{-}{ }g; $hdr .= s{only$}{} ? $_ : "$_\r\n"; } elsif ( $_ eq 'chunked' ) { $te = 'chunked'; $hdr .= "Transfer-Encoding: chunked\r\n"; } elsif ( m{^do_(gzip|deflate)} ) { $data = zlib_compress($data,$1); } elsif ( m{^(gzip|deflate)$} ) { $data = zlib_compress($data,$1); $hdr .= "Content-Encoding: $1\r\n"; } elsif ( $_ eq 'do_clen') { $te = 'clen'; } elsif ( $_ eq 'do_chunked') { $te = 'chunked'; } elsif ( $_ eq 'do_close') { $te = 'close'; } elsif ( $_ eq '177' ) { $hdr .= "\177\r\n"; } elsif ( $_ eq 'only' ) { $only = 1; } elsif ( s{\Aprefix:((?:\w+|\\[0-7]{3})+)\z}{$1} ) { s{\\([0-7]{3})}{ chr(oct($1)) }eg; $prefix = $_; } elsif ( m{\A((?:[\w/.]+|\\[0-7]{3})*)http09\z} ) { my $prefix = $1 or return $data; $prefix =~s{\\([0-7]{3})}{ chr(oct($1)) }eg; return $prefix . $data; } elsif ( m{^(16-)?(\d+)\+(b?)\z}) { # 100+ my $code = $1 ? "-".(65536-$2):$2; $prefix = "HTTP/1.1 $code whatever\r\n"; if ($3) { my $body = "fooo"; $prefix .= "Content-length: ".length($body)."\r\n"; $prefix .= "\r\n"; $prefix .= $body; } else { $prefix .= "\r\n"; } } elsif ( m{^(space-|tab-)*(\d.*)$} ) { s{space-}{ }g; s{tab-}{\t}g; $code = $_; } elsif ( $_ eq 'code-only' ) { $statusline = "HTTP/$version $code\r\n"; } elsif ( $_ eq 'http-lower' ) { $statusline = "http/$version $code ok\r\n"; } elsif ( $_ =~ m{^((?:cr|lf|\\[0-7]{3})+)-no-crlf$} ) { my $w = $1; $w =~s{cr}{\r}g; $w =~s{lf}{\n}g; $w =~s{\\([0-7]{3})}{ chr(oct($1)) }eg; push @transform, sub { $_[0] =~ s{\r?\n}{$w}g } } elsif ( $_ =~ m{^end-((?:cr|lf|\\[0-7]{3})+)} ) { my $w = $1; $w =~s{cr}{\r}g; $w =~s{lf}{\n}g; $w =~s{\\([0-7]{3})}{ chr(oct($1)) }eg; push @transform, sub { $_[0] =~ s{[\r\n]+\z}{$w} or die } } elsif ( m{^proto:(.*)} ) { my $proto = $1; $proto =~s{cr|\\r}{\r}g; $proto =~s{tab|\\t}{\t}g; $proto =~s{lf|\\n}{\n}g; $proto =~s{space}{ }g; $proto =~s{\\([0-7]{3})}{ chr(oct($1)) }esg; $proto =~s{-}{}g; $statusline = "$proto $code ok\r\n"; } elsif ( m{^status:(.*)} ) { my $line = $1; $line =~s{\Q(cr)}{\r}g; $line =~s{\Q(tab)}{\t}g; $line =~s{\Q(lf)}{\n}g; $line =~s{\Q(space)}{ }g; $line =~s{\\([0-7]{3})}{ chr(oct($1)) }esg; $statusline = "$line\r\n"; } elsif ( $_ eq 'ok' ) { } elsif ( m{^(.*)-header$}) { $prefix = $1; $prefix =~s{cr}{\r}g; $prefix =~s{lf}{\n}g; $prefix =~s{space}{ }g; $prefix =~s{tab}{\t}g; $prefix =~s{-}{}g; } elsif (m{^te(.*:.*)}) { (my $d = $1) =~s{\\([0-7]{3})}{ chr(oct($1)) }esg; $hdr .= "Transfer-Encoding$d\r\n"; } elsif (m{^ce(.*:.*)}) { (my $d = $1) =~s{\\([0-7]{3})}{ chr(oct($1)) }esg; $hdr .= "Content-Encoding$d\r\n"; } elsif (s{^data:}{}) { s{\\([0-7]{3})}{ chr(oct($1)) }esg; $hdr .= $_; } else { die $_ } } $data = join('', map { sprintf("%x\r\n%s\r\n", length($_),$_) } ($data =~m{(.{1,4})}sg,'')) if $te eq 'chunked'; if (!$only) { $hdr .= "Yet-another-header: foo\r\n"; $hdr .= "Content-length: ".length($data)."\r\n" if $te eq 'clen'; } $statusline ||= "HTTP/$version $code ok\r\n"; if ($hdrfirst) { $hdr .= $cthdr } else { $hdr = $cthdr . $hdr } $hdr = "$prefix$statusline$hdr\r\n"; for(@transform) { $_->($hdr,$data); } return $hdr . $data; } 1;