File Coverage

blib/lib/App/DubiousHTTP/Tests/Compressed.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1 1     1   3 use strict;
  1         1  
  1         24  
2 1     1   2 use warnings;
  1         1  
  1         24  
3             package App::DubiousHTTP::Tests::Compressed;
4 1     1   2 use App::DubiousHTTP::Tests::Common;
  1         1  
  1         119  
5 1     1   4 use Compress::Raw::Zlib;
  1         1  
  1         124  
6 1     1   773 use Compress::Raw::Lzma;
  0            
  0            
7              
8             SETUP(
9             'compressed',
10             "Variations on content compression",
11             <<'DESC',
12             Compression of Content is usueally done with a Content-Encoding header and a
13             value of 'gzip' (RFC1952) or 'deflate' (RFC1951). Most browsers additionally
14             accept RFC1950 compressed data (zlib) if 'deflate' is specified.
15             Some browsers also support compression with the Transfer-Encoding header,
16             which is actually specified in the HTTP RFC, but most browsers don't.
17             Some browsers just guess the encoding, e.g. accept gzip even if deflate is
18             specified.
19             And some browsers accept x-gzip and x-deflate specifications, and some even
20             specifications like "x gzip" or "gzip x".
21             Most browsers accept multiple content-encoding headers, even if it does not
22             make much sense to compress content twice with the same encoding.
23             DESC
24              
25             # ------------------------- Tests ----------------------------------------
26              
27             # these should be fine
28             [ 'VALID: correct compressed requests' ],
29             [ SHOULDBE_VALID, 'ce:gzip;gzip' => 'content-encoding gzip, served gzipped'],
30             [ UNCOMMON_VALID, 'ce:x-gzip;gzip' => 'content-encoding "x-gzip", served gzipped'], # not IE11
31             [ SHOULDBE_VALID, 'ce:deflate;deflate' => 'content-encoding deflate, served with deflate'],
32             [ VALID, 'ce:deFLaTe;deflate' => 'content-encoding deflate mixed case, served with deflate'],
33              
34             # various kinds of flush between compression parts
35             [ 'UNCOMMON_VALID: various kinds of flush between compression parts' ],
36             [ UNCOMMON_VALID, 'ce:gzip;gzip2p,partial' => 'content-encoding gzip, served gzipped with 2 compressed blocks with partial flush in between'],
37             [ UNCOMMON_VALID, 'ce:deflate;deflate2p,partial' => 'content-encoding deflate, served with deflate with 2 compressed blocks with partial flush in between'],
38             [ UNCOMMON_VALID, 'ce:gzip;gzip2p,block' => 'content-encoding gzip, served gzipped with 2 compressed blocks with block flush in between'],
39             [ UNCOMMON_VALID, 'ce:deflate;deflate2p,block' => 'content-encoding deflate, served with deflate with 2 compressed blocks with block flush in between'],
40             [ UNCOMMON_VALID, 'ce:gzip;gzip2p,sync' => 'content-encoding gzip, served gzipped with 2 compressed blocks with sync flush in between'],
41             [ UNCOMMON_VALID, 'ce:deflate;deflate2p,sync' => 'content-encoding deflate, served with deflate with 2 compressed blocks with sync flush in between'],
42             [ UNCOMMON_VALID, 'ce:gzip;gzip2p' => 'content-encoding gzip, served gzipped with 2 compressed blocks with full flush in between'],
43             [ UNCOMMON_VALID, 'ce:deflate;deflate2p' => 'content-encoding deflate, served with deflate with 2 compressed blocks with full flush in between'],
44             [ INVALID, 'ce:gzip;gzip2p,finish' => 'content-encoding gzip, served gzipped with 2 compressed blocks with finish in between'],
45             [ INVALID, 'ce:deflate;deflate2p,finish' => 'content-encoding deflate, served with deflate with 2 compressed blocks with finish in between'],
46              
47             [ 'INVALID: only part of data compressed, followed by uncompressed data' ],
48             [ INVALID, 'ce:gzip;gzip2s' => 'content-encoding gzip, first segment compressed with gzip, next uncompressed' ],
49             [ INVALID, 'ce:deflate;deflate2s' => 'content-encoding deflate, first segment compressed with deflate, next uncompressed' ],
50             [ INVALID, 'ce:deflate;zlib2s' => 'content-encoding deflate, first segment compressed with zlib, next uncompressed' ],
51             [ INVALID, 'ce:deflate;pkt:zlib+deflate' => 'content-encoding deflate, first segment compressed with zlib but ADLER32 removed, next with deflate in new TCP packet' ],
52             [ INVALID, 'ce:deflate;chk:zlib+deflate' => 'content-encoding deflate, first segment compressed with zlib but ADLER32 removed, next with deflate in new chunk with chunked encoding' ],
53             [ INVALID, 'ce:deflate;pkt:zlib+deflate+deflate' => 'content-encoding deflate, first segment compressed with zlib but ADLER32 removed, next two with deflate in new TCP packets' ],
54             [ INVALID, 'ce:deflate;chk:zlib+deflate+deflate' => 'content-encoding deflate, first segment compressed with zlib but ADLER32 removed, next two with deflate in new chunk with chunked encoding' ],
55              
56             [ 'VALID: lzma (supported by at least Opera)' ],
57             [ UNCOMMON_VALID, 'ce:lzma;lzma1' => 'content-encoding lzma, lzma1 (lzma_alone) encoded'],
58              
59             [ 'VALID: brotli (supported by at least Firefox when used with https)' ],
60             [ UNCOMMON_VALID, 'ce:br;brotli' => 'content-encoding br, encoded with brotli'],
61              
62             [ 'INVALID: gzip header combined with zlib (RFC1952) instead of deflate (RFC1951)' ],
63             [ INVALID, 'ce:gzip;gzip-zlib' => 'content-encoding gzip, encoded with zlib prefixed by gzip header'],
64              
65             # these might be strange/unsupported
66             [ 'VALID: less common but valid requests' ],
67             [ UNCOMMON_VALID, 'ce:deflate;zlib' => 'content-encoding deflate, served with RFC1950 style deflate (zlib)'],
68             [ UNCOMMON_VALID, 'ce:deflate;zlib2p' => 'content-encoding deflate, served with RFC1950 style deflate (zlib) with 2 compressed blocks'],
69             [ UNCOMMON_VALID, 'ce:nl-gzip;gzip' => 'content-encoding gzip but with continuation line, served gzipped'],
70             [ UNCOMMON_VALID, 'ce:nl-deflate;deflate' => 'content-encoding deflate but with continuation line, served with deflate'],
71             [ UNCOMMON_VALID, 'ce:nl-nl-deflate;deflate' => 'content-encoding deflate but with double continuation line, served with deflate'],
72             [ UNCOMMON_VALID, 'ce:deflate,;deflate' => 'content-encoding "deflate,", served with deflate'],
73             [ UNCOMMON_VALID, 'ce:deflate-nl-,;deflate' => 'content-encoding "deflate ,", served with deflate'],
74             [ UNCOMMON_VALID, 'ce:deflate-nl-,-nl-;deflate' => 'content-encoding "deflate , ", served with deflate'],
75              
76             # These should be fine according to RFC, but are not supported in the browsers
77             # Thus treat is as problem if they get supported.
78             [ 'INVALID: transfer-encoding with compression should not be supported' ],
79             [ INVALID, 'te:gzip;gzip' => 'transfer-encoding gzip, served gzipped'],
80             [ INVALID, 'te:deflate;deflate' => 'transfer-encoding deflate, served with deflate'],
81             [ INVALID, 'te:gzip;ce:gzip;gzip;gzip' => 'transfer-encoding and content-encoding gzip, gzipped twice'],
82              
83             # double encodings
84             [ 'VALID: double encodings' ],
85             [ UNCOMMON_VALID, 'ce:gzip;ce:gzip;gzip;gzip' => 'double content-encoding header gzip, served twice gzipped'],
86             [ UNCOMMON_VALID, 'ce:gzip,gzip;gzip;gzip' => 'single content-encoding header "gzip,gzip", served twice gzipped'],
87             [ UNCOMMON_VALID, 'ce:deflate;ce:deflate;deflate;deflate' => 'double content-encoding header deflate, compressed twice with deflate'],
88             [ UNCOMMON_VALID, 'ce:deflate,deflate;deflate;deflate' => 'single content-encoding header "deflate,deflate", compressed twice with deflate'],
89             [ UNCOMMON_VALID, 'ce:deflate-nl-,-nl-deflate;deflate;deflate' => 'single content-encoding header "deflate , deflate", compressed twice with deflate'],
90             [ UNCOMMON_VALID, 'ce:deflate-nl-,-nl-deflate-nl-;deflate;deflate' => 'single content-encoding header "deflate , deflate ", compressed twice with deflate'],
91              
92             [ UNCOMMON_VALID, 'ce:gzip;ce:deflate;gzip;deflate' => 'content-encoding header for gzip and deflate, content compressed in this order'],
93             [ UNCOMMON_VALID, 'ce:gzip,deflate;gzip;deflate' => 'single content-encoding "gzip,deflate", content compressed in this order'],
94             [ UNCOMMON_VALID, 'ce:deflate;ce:gzip;deflate;gzip' => 'content-encoding header for deflate and gzip, content compressed in this order'],
95             [ UNCOMMON_VALID, 'ce:deflate,gzip;deflate;gzip' => 'single content-encoding header "deflate,gzip", content compressed in this order'],
96              
97             # according to RFC2616 identity SHOULD only be used in Accept-Encoding, not Content-Encoding
98             [ 'INVALID: using "content-encoding: identity"' ],
99             [ UNCOMMON_INVALID, 'ce:identity', '"content-encoding:identity", served without encoding' ],
100             [ UNCOMMON_INVALID, 'ce:identity;ce:identity', 'twice "content-encoding:identity", served without encoding' ],
101             [ UNCOMMON_INVALID, 'ce:identity,identity', '"content-encoding:identity,identity", served without encoding' ],
102             [ UNCOMMON_INVALID, 'ce:identity;ce:gzip;gzip' => 'content-encoding header for identity and gzip, compressed with gzip'],
103             [ UNCOMMON_INVALID, 'ce:identity,gzip;gzip' => 'single content-encoding "identity,gzip", compressed with gzip'],
104             [ UNCOMMON_INVALID, 'ce:gzip;ce:identity;gzip' => 'content-encoding header for gzip and identity, compressed with gzip'],
105             [ UNCOMMON_INVALID, 'ce:gzip,identity;gzip' => 'single content-encoding header "gzip,identity", compressed with gzip'],
106              
107             [ UNCOMMON_INVALID, 'ce:identity;ce:deflate;deflate' => 'content-encoding header for identity and deflate, compressed with deflate'],
108             [ UNCOMMON_INVALID, 'ce:identity,deflate;deflate' => 'single content-encoding "identity,deflate", compressed with deflate'],
109             [ UNCOMMON_INVALID, 'ce:deflate;ce:identity;deflate' => 'content-encoding header for deflate and identity, compressed with deflate'],
110             [ UNCOMMON_INVALID, 'ce:deflate,identity;deflate' => 'single content-encoding header "deflate,identity", compressed with deflate'],
111              
112             # triple encodings
113             [ 'VALID: triple encodings' ],
114             [ UNCOMMON_VALID, 'ce:gzip;ce:deflate;ce:gzip;gzip;deflate;gzip' => 'served gzip + deflate + gzip, separate content-encoding header'],
115             [ UNCOMMON_VALID, 'ce:gzip,deflate,gzip;gzip;deflate;gzip' => 'served gzip + deflate + gzip, single content-encoding header'],
116             [ UNCOMMON_VALID, 'ce:gzip,deflate;ce:gzip;gzip;deflate;gzip' => 'served gzip + deflate + gzip, two content-encoding headers'],
117             [ UNCOMMON_VALID, 'ce:deflate;ce:gzip;ce:deflate;deflate;gzip;deflate' => 'served deflate + gzip + gzip, separate content-encoding header'],
118             [ UNCOMMON_VALID, 'ce:deflate,gzip,deflate;deflate;gzip;deflate' => 'served deflate + gzip + deflate, single content-encoding header'],
119             [ UNCOMMON_VALID, 'ce:deflate,gzip;ce:deflate;deflate;gzip;deflate' => 'served deflate + gzip + deflate, two content-encoding headers'],
120              
121             [ 'INVALID: specified double encodings, but content not or only once encoded or in the wrong order' ],
122             [ INVALID, 'ce:gzip;ce:gzip;gzip' => 'double content-encoding header gzip, but served with single gzip'],
123             [ INVALID, 'ce:gzip;ce:gzip' => 'double content-encoding header gzip, but served without encoding'],
124             [ INVALID, 'ce:deflate;ce:deflate;deflate' => 'double content-encoding header deflate, but served with single deflate'],
125             [ INVALID, 'ce:deflate;ce:deflate' => 'double content-encoding header deflate, but server without encoding'],
126              
127             [ INVALID, 'ce:gzip;ce:deflate;deflate;gzip' => 'content-encoding header for gzip and deflate, compressed in opposite order'],
128             [ INVALID, 'ce:gzip;ce:deflate;deflate' => 'content-encoding header for gzip and deflate, but served only with single deflate'],
129             [ INVALID, 'ce:gzip;ce:deflate;gzip' => 'content-encoding header for gzip and deflate, but server only with single gzip'],
130             [ INVALID, 'ce:gzip;ce:deflate' => 'content-encoding header for gzip and deflate, server without encoding'],
131             [ INVALID, 'ce:gzip,deflate;deflate;gzip' => 'single content-encoding header for "gzip,deflate", compressed in opposite order'],
132             [ INVALID, 'ce:gzip,deflate;deflate' => 'single content-encoding header for "gzip,deflate", but served with single deflate'],
133             [ INVALID, 'ce:gzip,deflate;gzip' => 'single content-encoding header for "gzip,deflate", but served with single gzip'],
134             [ INVALID, 'ce:gzip,deflate' => 'single content-encoding header for "gzip,deflate", but served without encoding'],
135              
136             [ INVALID, 'ce:deflate;ce:gzip;gzip;deflate' => 'content-encoding header for deflate and gzip, compressed in opposite order'],
137             [ INVALID, 'ce:deflate;ce:gzip;gzip' => 'content-encoding header for deflate and gzip, but served only with single gzip'],
138             [ INVALID, 'ce:deflate;ce:gzip;deflate' => 'content-encoding header for deflate and gzip, but server only with single deflate'],
139             [ INVALID, 'ce:deflate;ce:gzip' => 'content-encoding header for deflate and gzip, server without encoding'],
140             [ INVALID, 'ce:deflate,gzip;gzip;deflate' => 'single content-encoding header for "deflate,gzip", compressed in opposite order'],
141             [ INVALID, 'ce:deflate,gzip;gzip' => 'single content-encoding header for "deflate,gzip", but served with single gzip'],
142             [ INVALID, 'ce:deflate,gzip;deflate' => 'single content-encoding header for "deflate,gzip", but served with single deflate'],
143             [ INVALID, 'ce:deflate,gzip' => 'single content-encoding header for "deflate,gzip", but served without encoding'],
144              
145             # and the bad ones
146             [ 'INVALID: incorrect compressed response, should not succeed' ],
147             [ INVALID, 'ce:x-deflate;deflate' => 'content-encoding x-deflate, served with deflate'],
148             [ INVALID, 'ce:x-deflate;zlib' => 'content-encoding x-deflate, served with RFC1950 style deflate (zlib)'],
149             [ INVALID, 'ce:gzipx;gzip' => 'content-encoding "gzipx", served with gzip' ],
150             [ INVALID, 'ce:xgzip;gzip' => 'content-encoding "xgzip", served with gzip' ],
151             [ INVALID, 'ce:gzip_x;gzip' => 'content-encoding "gzip x", served with gzip' ],
152             [ INVALID, 'ce:x_gzip;gzip' => 'content-encoding "x gzip", served with gzip' ],
153             [ INVALID, 'ce:deflate;gzip' => 'content-encoding deflate but served with gzip'],
154             [ INVALID, 'ce:gzip;deflate' => 'content-encoding gzip but served with decode'],
155             [ INVALID, 'ce:deflate' => 'content-encoding "deflate", not encoded'],
156             [ INVALID, 'ce:deflate,' => 'content-encoding "deflate,", not encoded'],
157             [ INVALID, 'ce:deflate-nl-,' => 'content-encoding "deflate ,", not encoded'],
158             [ INVALID, 'ce:deflate-nl-,-nl-' => 'content-encoding "deflate , ", not encoded'],
159              
160             [ 'INVALID: invalid content-encodings should not be ignored' ],
161             [ INVALID, 'ce:gzip_x' => 'content-encoding "gzip x", but not encoded' ],
162             [ INVALID, 'ce:deflate;ce:gzip_x;deflate' => 'content-encoding deflate + "gzip x", but only deflated' ],
163             [ INVALID, 'ce:gzip_x;ce:deflate;deflate' => 'content-encoding "gzip x" + deflate, but only deflated' ],
164             [ INVALID, 'ce:foo', '"content-encoding:foo" and no encoding' ],
165             [ INVALID, 'ce:rfc2047-deflate', '"content-encoding:rfc2047(deflate)" and no encoding' ],
166             [ INVALID, 'ce:rfc2047-deflate;deflate', '"content-encoding:rfc2047(deflate)" with encoding' ],
167              
168             [ 'VALID: transfer-encoding should be ignored for compression' ],
169             [ UNCOMMON_VALID,'te:gzip' => 'transfer-encoding gzip but not compressed'],
170              
171             [ 'INVALID: "Hiding the Content-encoding header"' ],
172             [ INVALID, 'ce-space-colon-deflate;deflate' => '"Content-Encoding: deflate", served with deflate' ],
173             [ UNCOMMON_INVALID, 'ce-space-colon-deflate' => '"Content-Encoding: deflate", served not with deflate' ],
174             [ INVALID, 'ce-space-colon-gzip;gzip' => '"Content-Encoding: gzip", served with gzip' ],
175             [ UNCOMMON_INVALID, 'ce-space-colon-gzip' => '"Content-Encoding: gzip", served not with gzip' ],
176              
177             [ INVALID, 'ce-colon-colon-deflate;deflate' => '"Content-Encoding:: deflate", served with deflate' ],
178             [ UNCOMMON_INVALID, 'ce-colon-colon-deflate' => '"Content-Encoding:: deflate", served not with deflate' ],
179             [ INVALID, 'ce-colon-colon-gzip;gzip' => '"Content-Encoding:: gzip", served with gzip' ],
180             [ UNCOMMON_INVALID, 'ce-colon-colon-gzip' => '"Content-Encoding:: gzip", served not with gzip' ],
181              
182             [ INVALID, 'cronly-deflate;deflate' => 'Content-Encoding with only as line delimiter before, served deflate' ],
183             [ INVALID, 'crxonly-deflate;deflate' => 'Only as line delimiter followed by "xContent-Encoding", served deflate' ],
184             [ UNCOMMON_INVALID, 'cronly-deflate' => 'Content-Encoding with only as line delimiter before, not served deflate' ],
185             [ INVALID, 'cronly-gzip;gzip' => 'Content-Encoding with only as line delimiter before, served gzip' ],
186             [ INVALID, 'crxonly-gzip;gzip' => 'Only as line delimiter followed by "xContent-Encoding", served gzip' ],
187             [ UNCOMMON_INVALID, 'cronly-gzip' => 'Content-Encoding with only as line delimiter before, not served gzip' ],
188              
189             [ UNCOMMON_INVALID, 'lfonly-deflate;deflate' => 'Content-Encoding with only as line delimiter before, served deflate' ],
190             [ INVALID, 'lfonly-deflate' => 'Content-Encoding with only as line delimiter before, not served deflate' ],
191             [ UNCOMMON_INVALID, 'lfonly-gzip;gzip' => 'Content-Encoding with only as line delimiter before, served gzip' ],
192             [ INVALID, 'lfonly-gzip' => 'Content-Encoding with only as line delimiter before, not served gzip' ],
193              
194             [ INVALID, 'ce:crdeflate;deflate' => 'Content-Encoding:deflate, served with deflate' ],
195             [ INVALID, 'ce:crdeflate' => 'Content-Encoding:deflate, not served with deflate' ],
196             [ INVALID, 'ce:cr-deflate;deflate' => 'Content-Encoding:deflate, served with deflate' ],
197             [ INVALID, 'ce:cr-deflate' => 'Content-Encoding:deflate, not served with deflate' ],
198             [ INVALID, 'ce:crgzip;gzip' => 'Content-Encoding:gzip, served with gzip' ],
199             [ INVALID, 'ce:crgzip' => 'Content-Encoding:gzip, not served with gzip' ],
200             [ INVALID, 'ce:cr-gzip;gzip' => 'Content-Encoding:gzip, served with gzip' ],
201             [ INVALID, 'ce:cr-gzip' => 'Content-Encoding:gzip, not served with gzip' ],
202              
203             [ 'INVALID: slightly invalid gzip encodings' ],
204             [ INVALID,'ce:gzip;gzip;replace:0,2=1f8c', 'wrong gzip magic header'],
205             [ INVALID,'ce:gzip;gzip;replace:2,1=88', 'wrong compression method 88 instead of 08'],
206             [ UNCOMMON_VALID,'ce:gzip;gzip;replace:3,1|01', 'set flag FTEXT'],
207             [ INVALID,'ce:gzip;gzip;replace:3,1|02', 'set flag FHCRC without having CRC'],
208             [ INVALID,'ce:gzip;gzip;replace:3,1|02;replace:10,0=0000', 'set flag FHCRC and add CRC with 0'],
209             [ UNCOMMON_VALID,'ce:gzip;gzip;replace:3,1|04;replace:10,0=0000', 'set flag FEXTRA and extra part with XLEN 0'],
210             [ UNCOMMON_VALID,'ce:gzip;gzip;replace:3,1|04;replace:10,0=05004170010000', 'set flag FEXTRA and extra part with XLEN 5'],
211             [ INVALID,'ce:gzip;gzip;replace:3,1|04;replace:10,0=0500', 'set flag FEXTRA and XLEN 5 but no extra part'],
212             [ INVALID,'ce:gzip;gzip-payload-as-extra', 'gzip, but hide the real (deflate) payload inside the EXTRA part'],
213             [ UNCOMMON_VALID,'ce:gzip;gzip;replace:3,1|08;replace:10,0=2000', 'set flag FNAME and add short file name'],
214             [ UNCOMMON_VALID,'ce:gzip;gzip;replace:3,1|10;replace:10,0=2000', 'set flag FCOMMENT and add short comment'],
215             [ INVALID,'ce:gzip;gzip;replace:3,1|20', 'set flag reserved bit 5'],
216             [ INVALID,'ce:gzip;gzip;replace:3,1|40', 'set flag reserved bit 6'],
217             [ INVALID,'ce:gzip;gzip;replace:3,1|80', 'set flag reserved bit 7'],
218             [ INVALID,'ce:gzip;gzip;replace:-8,4^ffffffff', 'invalidate final checksum'],
219             [ INVALID,'ce:gzip;gzip;replace:-4,1^ff', 'invalidate length'],
220             [ INVALID,'ce:gzip;gzip;replace:-4,4=', 'remove length'],
221             [ INVALID,'ce:gzip;gzip;replace:-8,8=', 'remove checksum and length'],
222             [ INVALID,'ce:gzip;gzip;replace:-4,4=;clen+4', 'remove length but set content-length header to original size'],
223             [ INVALID,'ce:gzip;gzip;replace:-8,8=;clen+8', 'remove checksum and length but set content-length header to original size'],
224             [ INVALID,'ce:gzip;gzip;replace:-4,4=;noclen', 'remove length and close with eof without sending length'],
225             [ INVALID,'ce:gzip;gzip;replace:-8,8=;noclen', 'remove checksum and and close with eof without sending length'],
226             # and now hide the 'gzip' behind a \r so that some firewalls will use the
227             # heuristics of the antivirus which might be different from the the proxy
228             [ INVALID,'ce:cr-gzip;gzip;replace:3,1|01', 'set flag FTEXT (hide gzip with "content-encoding:\r gzip")'],
229             [ INVALID,'ce:cr-gzip;gzip;replace:3,1|02;replace:10,0=0000', 'set flag FHCRC and add CRC with 0 (hide gzip with "content-encoding:\r gzip")'],
230             [ INVALID,'ce:cr-gzip;gzip;replace:3,1|08;replace:10,0=2000', 'set flag FNAME and add short file name (hide gzip with "content-encoding:\r gzip")'],
231             [ INVALID,'ce:cr-gzip;gzip;replace:3,1|10;replace:10,0=2000', 'set flag FCOMMENT and add short comment (hide gzip with "content-encoding:\r gzip")'],
232             [ INVALID,'ce:cr-gzip;gzip;replace:3,1|04;replace:10,0=0000', 'set flag FEXTRA and extra part with XLEN 0 (hide gzip with "content-encoding:\r gzip")'],
233             [ INVALID,'ce:cr-gzip;gzip;replace:3,1|04;replace:10,0=05004170010000', 'set flag FEXTRA and extra part with XLEN 5 (hide gzip with "content-encoding:\r gzip")'],
234             [ INVALID,'ce:cr-gzip;gzip;replace:3,1|20', 'set flag reserved bit 5 (hide gzip with "content-encoding:\r gzip")'],
235             [ INVALID,'ce:cr-gzip;gzip;replace:3,1|40', 'set flag reserved bit 6 (hide gzip with "content-encoding:\r gzip")'],
236             [ INVALID,'ce:cr-gzip;gzip;replace:3,1|80', 'set flag reserved bit 7 (hide gzip with "content-encoding:\r gzip")'],
237             [ INVALID,'ce:cr-gzip;gzip;replace:-8,4^ffffffff', 'invalidate final checksum (hide gzip with "content-encoding:\r gzip")'],
238             [ INVALID,'ce:cr-gzip;gzip;replace:-4,1^ff', 'invalidate length (hide gzip with "content-encoding:\r gzip")'],
239             [ INVALID,'ce:cr-gzip;gzip;replace:-4,4=', 'remove length (hide gzip with "content-encoding:\r gzip")'],
240             [ INVALID,'ce:cr-gzip;gzip;replace:-8,8=', 'remove checksum and length (hide gzip with "content-encoding:\r gzip")'],
241             # same game, but with Content-Encoding: for other firewalls
242             [ INVALID,'ce-space-colon-gzip;gzip;replace:3,1|01', 'set flag FTEXT (hide gzip with "content-encoding : gzip")'],
243             [ INVALID,'ce-space-colon-gzip;gzip;replace:3,1|02;replace:10,0=0000', 'set flag FHCRC and add CRC with 0 (hide gzip with "content-encoding : gzip")'],
244             [ INVALID,'ce-space-colon-gzip;gzip;replace:3,1|08;replace:10,0=2000', 'set flag FNAME and add short file name (hide gzip with "content-encoding : gzip")'],
245             [ INVALID,'ce-space-colon-gzip;gzip;replace:3,1|10;replace:10,0=2000', 'set flag FCOMMENT and add short comment (hide gzip with "content-encoding : gzip")'],
246             [ INVALID,'ce-space-colon-gzip;gzip;replace:3,1|04;replace:10,0=0000', 'set flag FEXTRA and extra part with XLEN 0 (hide gzip with "content-encoding : gzip")'],
247             [ INVALID,'ce-space-colon-gzip;gzip;replace:3,1|04;replace:10,0=05004170010000', 'set flag FEXTRA and extra part with XLEN 5 (hide gzip with "content-encoding : gzip")'],
248             [ INVALID,'ce-space-colon-gzip;gzip;replace:3,1|20', 'set flag reserved bit 5 (hide gzip with "content-encoding : gzip")'],
249             [ INVALID,'ce-space-colon-gzip;gzip;replace:3,1|40', 'set flag reserved bit 6 (hide gzip with "content-encoding : gzip")'],
250             [ INVALID,'ce-space-colon-gzip;gzip;replace:3,1|80', 'set flag reserved bit 7 (hide gzip with "content-encoding : gzip")'],
251             [ INVALID,'ce-space-colon-gzip;gzip;replace:-8,4^ffffffff', 'invalidate final checksum (hide gzip with "content-encoding : gzip")'],
252             [ INVALID,'ce-space-colon-gzip;gzip;replace:-4,1^ff', 'invalidate length (hide gzip with "content-encoding : gzip")'],
253             [ INVALID,'ce-space-colon-gzip;gzip;replace:-4,4=', 'remove length (hide gzip with "content-encoding : gzip")'],
254             [ INVALID,'ce-space-colon-gzip;gzip;replace:-8,8=', 'remove checksum and length (hide gzip with "content-encoding : gzip")'],
255              
256             # data before gzip
257             [ INVALID,'ce:gzip;gzip;\012-before-body','new line at start of gzip body' ],
258             );
259              
260             sub make_response {
261             my ($self,$page,$spec) = @_;
262             return make_index_page() if $page eq '';
263             my ($hdr,$data) = content($page,$self->ID."-".$spec) or die "unknown page $page";
264             my $version = '1.1';
265             my $clen_extend;
266             my $body_prefix = '';
267             my $te = 'clen';
268             my @data; # preferred against $data if given
269             for (split(';',$spec)) {
270             if ($_ eq 'ce:rfc2047-deflate') {
271             $hdr .= "Content-Encoding: =?UTF-8?B?ZGVmbGF0ZQo=?=\r\n";
272             } elsif ( my ($field,$v) = m{^(ce|te):(.*)$}i ) {
273             my $changed;
274             $changed++ if $v =~s{(?<=cr|lf|nl)-}{ }g;
275             $changed++ if $v =~s{cr}{\r}g;
276             $changed++ if $v =~s{lf}{\n}g;
277             $changed++ if $v =~s{nl}{\r\n}g;
278             $changed++ if $v =~s{_}{ }g;
279             $v =~s{(?
280             $hdr .= "Connection: close\r\n" if $changed;
281             $hdr .= $field eq 'ce' ? 'Content-Encoding:':'Transfer-Encoding:';
282             $hdr .= "$v\r\n";
283             } elsif ( m{^(pkt|chk):zlib\+deflate(\+deflate)?\z} ) {
284             # [zlib-header][deflate][more-deflate]....
285             # zlib will return with Z_DATA_ERROR when trying to process
286             # more-deflate because it actually expected the correct ADLER32
287             # checksum there. Browsers will then assume that this should be
288             # raw-deflate instead and retry with an edded zlib header.
289             # With some browsers this process can be repeated.
290              
291             my ($enc,$nchunks) = ($1, $2? 3:2);
292             my $size = int(length($data)/$nchunks);
293             @data = ();
294             for(my $i=0;$i<$nchunks;$i++) {
295             push @data, substr($data,0,$size,'');
296             }
297             $data[-1] .= $data; # in case something left
298             $_ = zlib_compress($_,'deflate') for(@data);
299             $data[0] = "\x78\x9c".$data[0];
300             if ($enc eq 'chk') {
301             $te = 'chunked';
302             $data = join("", map {
303             sprintf("%x\r\n%s\r\n",length($_),$_)
304             } (@data,''));
305             @data = ();
306             }
307             } elsif ( m{^(?:(gzip)|deflate|(zlib))(?:(\d+)([ps]))?(?:,(sync|partial|block|full|finish))?$} ) {
308             my $zlib = Compress::Raw::Zlib::Deflate->new(
309             -WindowBits => $1 ? WANT_GZIP : $2 ? +MAX_WBITS() : -MAX_WBITS(),
310             -AppendOutput => 1,
311             );
312             my $size = int(length($data)/($3||1)) || 1;
313             my @chunks;
314             while ($data ne '') {
315             push @chunks,substr($data,0,$size,'')
316             }
317             my $plain_chunk = '';
318             $plain_chunk = join('',splice(@chunks,1)) if $4 && $4 eq 's';
319              
320             my $flush =
321             ! $5 ? Z_FULL_FLUSH :
322             $5 eq 'partial' ? Z_PARTIAL_FLUSH :
323             $5 eq 'sync' ? Z_SYNC_FLUSH :
324             $5 eq 'full' ? Z_FULL_FLUSH :
325             $5 eq 'block' ? Z_BLOCK :
326             $5 eq 'finish' ? Z_FINISH :
327             die $5;
328             my $newdata = '';
329             while (@chunks) {
330             $zlib->deflate( shift(@chunks), $newdata);
331             if (defined $flush && @chunks) {
332             $zlib->flush($newdata,$flush);
333             $zlib->deflateReset if $flush == Z_FINISH;
334             }
335             }
336             $zlib->flush($newdata,Z_FINISH);
337             $data = $newdata . $plain_chunk;
338             } elsif (m{^(lzma[12]|xz)$}) {
339             my ($lzma,$status) =
340             $_ eq 'xz' ? Compress::Raw::Lzma::EasyEncoder->new(AppendOutput => 1) :
341             $_ eq 'lzma1' ? Compress::Raw::Lzma::AloneEncoder->new(AppendOutput => 1) :
342             Compress::Raw::Lzma::RawEncoder->new(AppendOutput =>1);
343             $status == LZMA_OK or die "failed to create LZMA object";
344             my $newdata = '';
345             $lzma->code($data,$newdata) == LZMA_OK or die "failed to lzma encode data";
346             $lzma->flush($newdata,LZMA_FINISH) == LZMA_STREAM_END or die "failed to close lzma stream";
347             $data = $newdata;
348              
349             } elsif ($_ eq 'brotli') {
350             $data = bro_compress($data) or do {
351             # no brotli for this content
352             return "HTTP/$version 500 no brotli\r\nContent-length:0\r\n\r\n";
353             };
354             } elsif ($_ eq 'gzip-zlib') {
355             my $zlib = Compress::Raw::Zlib::Deflate->new(
356             -WindowBits => +MAX_WBITS(),
357             -AppendOutput => 1,
358             );
359             my $gzip_hdr = pack("CCCCVCC",0x1f,0x8b,0x8,0,0,2,0);
360             my $gzip_trailer = pack("VV",Compress::Raw::Zlib::crc32($data),length($data));
361             my $newdata = '';
362             $zlib->deflate($data,$newdata);
363             $zlib->flush($newdata,Z_FINISH);
364             $data = $gzip_hdr.$newdata.$gzip_trailer;
365              
366             } elsif (m{^ce-space-colon-(.*)}) {
367             $hdr .= "Content-Encoding : $1\r\n";
368             } elsif (m{^ce-colon-colon-(.*)}) {
369             $hdr .= "Content-Encoding:: $1\r\n";
370             } elsif ( my ($crlf,$encoding) = m{^((?:lf|cr|x)+)only-(.*)}) {
371             $hdr = "X-Foo: bar" if $hdr !~s{\r\n\z}{};
372             $crlf =~s{cr}{\r}g;
373             $crlf =~s{lf}{\n}g;
374             $hdr .= $crlf . "Content-Encoding: $encoding\r\n";
375             } elsif ( my ($off,$len,$op,$replacement) = m{replace:(-?\d+),(\d+)([=|^])(.*)}) {
376             $replacement = pack('C*',map { hex($_) } $replacement=~m{(..)}g);
377             if ($op eq '=') {
378             substr($data,$off,$len,$replacement);
379             } elsif ($op eq '|') {
380             die "'$_' flags are already set" if (substr($data,$off,$len) & $replacement) eq $replacement;
381             substr($data,$off,$len) |= $replacement;
382             } elsif ($op eq '^') {
383             substr($data,$off,$len) ^= $replacement;
384             } else {
385             die "bad op=$op in '$_'"
386             }
387              
388             } elsif ( $_ eq 'gzip-payload-as-extra') {
389              
390             # header with FEXTRA set
391             my $gzip = pack("CCCCVCC",0x1f,0x8b,0x8,0b100,0,2,0);
392              
393             # add XLEN + payload with deflate data
394             my $zlib = Compress::Raw::Zlib::Deflate->new(
395             -WindowBits => -MAX_WBITS(),
396             -AppendOutput => 1,
397             );
398             my $newdata = '';
399             $zlib->deflate($data,$newdata);
400             $zlib->flush($newdata,Z_FINISH);
401             $gzip .= pack("v/a*",$newdata);
402              
403             # then add some innocent compressed data
404             $data = 'This is not what you are looking for.';
405             $zlib = Compress::Raw::Zlib::Deflate->new(
406             -WindowBits => -MAX_WBITS(),
407             -AppendOutput => 1,
408             );
409             $newdata = '';
410             $zlib->deflate($data,$newdata);
411             $zlib->flush($newdata,Z_FINISH);
412             $gzip .= $newdata;
413              
414             # and add the trailer with CRC and length based on the innocent data
415             $gzip .= pack("VV",Compress::Raw::Zlib::crc32($data),length($data));
416             $data = $gzip;
417              
418             } elsif (m{^clen\+(\d+)$}) {
419             $clen_extend = $1
420             } elsif ($_ eq 'noclen') {
421             $clen_extend = -1;
422             } elsif (m{(.+)-before-body$}) {
423             ( my $d = $1 ) =~s{\\([0-7]{3})}{ chr(oct($1)) }esg;
424             $body_prefix .= $d;
425             } else {
426             die $_
427             }
428             }
429              
430             $data = $body_prefix . $data;
431             if (! @data) {
432             @data = $data;
433             } else {
434             $data = join('',@data);
435             }
436             my $len = length($data);
437             if ($te eq 'chunked') {
438             $hdr .= "Transfer-Encoding: chunked\r\n"
439             } elsif (!$clen_extend) {
440             $hdr = "Content-length: ".length($data)."\r\n".$hdr;
441             } elsif ($clen_extend<0) {
442             $hdr = "Connection: close\r\n$hdr";
443             } else {
444             $hdr = "Connection: close\r\nContent-length: ".(length($data)+$clen_extend)."\r\n".$hdr;
445             }
446             return (
447             "HTTP/$version 200 ok\r\n$hdr\r\n$data[0]",
448             (@data>1) ? (@data[1..$#data]):(),
449             );
450             }
451              
452             1;