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