]>
Commit | Line | Data |
---|---|---|
bbc27441 | 1 | /* |
4ac4a490 | 2 | * Copyright (C) 1996-2017 The Squid Software Foundation and contributors |
bbc27441 AJ |
3 | * |
4 | * Squid software is distributed under GPLv2+ license and includes | |
5 | * contributions from numerous individuals and organizations. | |
6 | * Please see the COPYING and CONTRIBUTORS files for details. | |
7 | */ | |
8 | ||
582c2af2 | 9 | #include "squid.h" |
3d93a84d | 10 | #include "base/TextException.h" |
602d9612 | 11 | #include "Debug.h" |
db1720f8 | 12 | #include "http/one/TeChunkedParser.h" |
cb4c8047 | 13 | #include "http/one/Tokenizer.h" |
350ec67a | 14 | #include "http/ProtocolVersion.h" |
774c051c | 15 | #include "MemBuf.h" |
602d9612 | 16 | #include "Parsing.h" |
9a4b5048 | 17 | #include "SquidConfig.h" |
774c051c | 18 | |
db1720f8 | 19 | Http::One::TeChunkedParser::TeChunkedParser() |
774c051c | 20 | { |
350ec67a AJ |
21 | // chunked encoding only exists in HTTP/1.1 |
22 | Http1::Parser::msgProtocol_ = Http::ProtocolVersion(1,1); | |
23 | ||
24 | clear(); | |
774c051c | 25 | } |
26 | ||
51c3e7f0 | 27 | void |
db1720f8 | 28 | Http::One::TeChunkedParser::clear() |
774c051c | 29 | { |
350ec67a | 30 | parsingStage_ = Http1::HTTP_PARSE_NONE; |
be29ee33 | 31 | buf_.clear(); |
774c051c | 32 | theChunkSize = theLeftBodySize = 0; |
be29ee33 | 33 | theOut = NULL; |
83c51da9 | 34 | useOriginBody = -1; |
774c051c | 35 | } |
36 | ||
51c3e7f0 | 37 | bool |
db1720f8 | 38 | Http::One::TeChunkedParser::parse(const SBuf &aBuf) |
774c051c | 39 | { |
74faa994 AJ |
40 | buf_ = aBuf; // sync buffers first so calls to remaining() work properly if nothing done. |
41 | ||
42 | if (buf_.isEmpty()) // nothing to do (yet) | |
43 | return false; | |
44 | ||
be29ee33 | 45 | debugs(74, DBG_DATA, "Parse buf={length=" << aBuf.length() << ", data='" << aBuf << "'}"); |
74faa994 | 46 | |
be29ee33 | 47 | Must(!buf_.isEmpty() && theOut); |
774c051c | 48 | |
350ec67a AJ |
49 | if (parsingStage_ == Http1::HTTP_PARSE_NONE) |
50 | parsingStage_ = Http1::HTTP_PARSE_CHUNK_SZ; | |
774c051c | 51 | |
cb4c8047 | 52 | Http1::Tokenizer tok(buf_); |
be29ee33 | 53 | |
350ec67a AJ |
54 | // loop for as many chunks as we can |
55 | // use do-while instead of while so that we can incrementally | |
56 | // restart in the middle of a chunk/frame | |
57 | do { | |
774c051c | 58 | |
be29ee33 AJ |
59 | if (parsingStage_ == Http1::HTTP_PARSE_CHUNK_EXT && !parseChunkExtension(tok, theChunkSize)) |
60 | return false; | |
774c051c | 61 | |
be29ee33 | 62 | if (parsingStage_ == Http1::HTTP_PARSE_CHUNK && !parseChunkBody(tok)) |
350ec67a AJ |
63 | return false; |
64 | ||
be29ee33 | 65 | if (parsingStage_ == Http1::HTTP_PARSE_MIME && !grabMimeBlock("Trailers", 64*1024 /* 64KB max */)) |
350ec67a AJ |
66 | return false; |
67 | ||
68 | // loop for as many chunks as we can | |
be29ee33 | 69 | } while (parsingStage_ == Http1::HTTP_PARSE_CHUNK_SZ && parseChunkSize(tok)); |
350ec67a AJ |
70 | |
71 | return !needsMoreData() && !needsMoreSpace(); | |
774c051c | 72 | } |
73 | ||
51c3e7f0 | 74 | bool |
db1720f8 | 75 | Http::One::TeChunkedParser::needsMoreSpace() const |
774c051c | 76 | { |
77 | assert(theOut); | |
350ec67a | 78 | return parsingStage_ == Http1::HTTP_PARSE_CHUNK && !theOut->hasPotentialSpace(); |
774c051c | 79 | } |
80 | ||
be29ee33 | 81 | /// RFC 7230 section 4.1 chunk-size |
51c3e7f0 | 82 | bool |
cb4c8047 | 83 | Http::One::TeChunkedParser::parseChunkSize(Http1::Tokenizer &tok) |
774c051c | 84 | { |
85 | Must(theChunkSize <= 0); // Should(), really | |
86 | ||
5c550f5f | 87 | int64_t size = -1; |
be29ee33 | 88 | if (tok.int64(size, 16, false) && !tok.atEnd()) { |
5c550f5f AR |
89 | if (size < 0) |
90 | throw TexcHere("negative chunk size"); | |
91 | ||
92 | theChunkSize = theLeftBodySize = size; | |
93 | debugs(94,7, "found chunk: " << theChunkSize); | |
be29ee33 | 94 | buf_ = tok.remaining(); // parse checkpoint |
350ec67a AJ |
95 | parsingStage_ = Http1::HTTP_PARSE_CHUNK_EXT; |
96 | return true; | |
350ec67a | 97 | |
be29ee33 AJ |
98 | } else if (tok.atEnd()) { |
99 | return false; // need more data | |
774c051c | 100 | } |
101 | ||
be29ee33 AJ |
102 | // else error |
103 | throw TexcHere("corrupted chunk size"); | |
104 | return false; // should not be reachable | |
774c051c | 105 | } |
106 | ||
350ec67a | 107 | /** |
be29ee33 AJ |
108 | * Parses a set of RFC 7230 section 4.1.1 chunk-ext |
109 | * http://tools.ietf.org/html/rfc7230#section-4.1.1 | |
350ec67a | 110 | * |
be29ee33 AJ |
111 | * chunk-ext = *( ";" chunk-ext-name [ "=" chunk-ext-val ] ) |
112 | * chunk-ext-name = token | |
113 | * chunk-ext-val = token / quoted-string | |
350ec67a | 114 | * |
be29ee33 | 115 | * ICAP 'use-original-body=N' extension is supported. |
350ec67a AJ |
116 | */ |
117 | bool | |
cb4c8047 | 118 | Http::One::TeChunkedParser::parseChunkExtension(Http1::Tokenizer &tok, bool skipKnown) |
774c051c | 119 | { |
aad871e3 DS |
120 | // Bug 4492: IBM_HTTP_Server sends SP padding |
121 | if (auto n = tok.skipAll(CharacterSet::SP)) { | |
122 | debugs(94, 3, "skipping " << n << " spurious whitespace at start of chunk extension"); | |
123 | } | |
124 | ||
be29ee33 | 125 | SBuf ext; |
cb4c8047 | 126 | SBuf value; |
be29ee33 | 127 | while (tok.skip(';') && tok.prefix(ext, CharacterSet::TCHAR)) { |
350ec67a | 128 | |
be29ee33 AJ |
129 | // whole value part is optional. if no '=' expect next chunk-ext |
130 | if (tok.skip('=')) { | |
774c051c | 131 | |
be29ee33 AJ |
132 | if (!skipKnown) { |
133 | if (ext.cmp("use-original-body",17) == 0 && tok.int64(useOriginBody, 10)) { | |
134 | debugs(94, 3, "Found chunk extension " << ext << "=" << useOriginBody); | |
135 | buf_ = tok.remaining(); // parse checkpoint | |
136 | continue; | |
137 | } | |
138 | } | |
774c051c | 139 | |
be29ee33 | 140 | debugs(94, 5, "skipping unknown chunk extension " << ext); |
774c051c | 141 | |
cb4c8047 AJ |
142 | // unknown might have a value token or quoted-string |
143 | if (tok.quotedStringOrToken(value) && !tok.atEnd()) { | |
be29ee33 AJ |
144 | buf_ = tok.remaining(); // parse checkpoint |
145 | continue; | |
146 | } | |
774c051c | 147 | |
be29ee33 AJ |
148 | // otherwise need more data OR corrupt syntax |
149 | break; | |
350ec67a | 150 | } |
774c051c | 151 | |
be29ee33 AJ |
152 | if (!tok.atEnd()) |
153 | buf_ = tok.remaining(); // parse checkpoint (unless there might be more token name) | |
774c051c | 154 | } |
155 | ||
be29ee33 AJ |
156 | if (skipLineTerminator(tok)) { |
157 | buf_ = tok.remaining(); // checkpoint | |
158 | // non-0 chunk means data, 0-size means optional Trailer follows | |
159 | parsingStage_ = theChunkSize ? Http1::HTTP_PARSE_CHUNK : Http1::HTTP_PARSE_MIME; | |
160 | return true; | |
774c051c | 161 | } |
162 | ||
163 | return false; | |
164 | } | |
165 | ||
be29ee33 | 166 | bool |
cb4c8047 | 167 | Http::One::TeChunkedParser::parseChunkBody(Http1::Tokenizer &tok) |
83c51da9 | 168 | { |
8103fa66 AR |
169 | if (theLeftBodySize > 0) { |
170 | buf_ = tok.remaining(); // sync buffers before buf_ use | |
5c550f5f | 171 | |
8103fa66 AR |
172 | // TODO fix type mismatches and casting for these |
173 | const size_t availSize = min(theLeftBodySize, (uint64_t)buf_.length()); | |
174 | const size_t safeSize = min(availSize, (size_t)theOut->potentialSpaceSize()); | |
5c550f5f | 175 | |
8103fa66 AR |
176 | theOut->append(buf_.rawContent(), safeSize); |
177 | buf_.consume(safeSize); | |
178 | theLeftBodySize -= safeSize; | |
5c550f5f | 179 | |
8103fa66 AR |
180 | tok.reset(buf_); // sync buffers after consume() |
181 | } | |
7ddcfbab | 182 | |
be29ee33 AJ |
183 | if (theLeftBodySize == 0) |
184 | return parseChunkEnd(tok); | |
185 | else | |
186 | Must(needsMoreData() || needsMoreSpace()); | |
7ddcfbab | 187 | |
be29ee33 AJ |
188 | return true; |
189 | } | |
7ddcfbab | 190 | |
be29ee33 | 191 | bool |
cb4c8047 | 192 | Http::One::TeChunkedParser::parseChunkEnd(Http1::Tokenizer &tok) |
be29ee33 AJ |
193 | { |
194 | Must(theLeftBodySize == 0); // Should(), really | |
83c51da9 | 195 | |
be29ee33 AJ |
196 | if (skipLineTerminator(tok)) { |
197 | buf_ = tok.remaining(); // parse checkpoint | |
198 | theChunkSize = 0; // done with the current chunk | |
199 | parsingStage_ = Http1::HTTP_PARSE_CHUNK_SZ; | |
200 | return true; | |
83c51da9 | 201 | } |
5c550f5f | 202 | |
be29ee33 | 203 | return false; |
83c51da9 | 204 | } |
f53969cc | 205 |