]> git.ipfire.org Git - thirdparty/pdns.git/blob - ext/yahttp/yahttp/reqresp.cpp
Merge pull request #11431 from jroessler-ox/docs-kskzskroll-update
[thirdparty/pdns.git] / ext / yahttp / yahttp / reqresp.cpp
1 #include "yahttp.hpp"
2
3 #include <limits>
4
5 namespace YaHTTP {
6
7 template class AsyncLoader<Request>;
8 template class AsyncLoader<Response>;
9
10 bool isspace(char c) {
11 return std::isspace(c) != 0;
12 }
13
14 bool isspace(char c, const std::locale& loc) {
15 return std::isspace(c, loc);
16 }
17
18 bool isxdigit(char c) {
19 return std::isxdigit(c) != 0;
20 }
21
22 bool isxdigit(char c, const std::locale& loc) {
23 return std::isxdigit(c, loc);
24 }
25
26 bool isdigit(char c) {
27 return std::isdigit(c) != 0;
28 }
29
30 bool isdigit(char c, const std::locale& loc) {
31 return std::isdigit(c, loc);
32 }
33
34 bool isalnum(char c) {
35 return std::isalnum(c) != 0;
36 }
37
38 bool isalnum(char c, const std::locale& loc) {
39 return std::isalnum(c, loc);
40 }
41
42 template <class T>
43 bool AsyncLoader<T>::feed(const std::string& somedata) {
44 buffer.append(somedata);
45 while(state < 2) {
46 int cr=0;
47 pos = buffer.find_first_of("\n");
48 // need to find CRLF in buffer
49 if (pos == std::string::npos) return false;
50 if (pos>0 && buffer[pos-1]=='\r')
51 cr=1;
52 std::string line(buffer.begin(), buffer.begin()+pos-cr); // exclude CRLF
53 buffer.erase(buffer.begin(), buffer.begin()+pos+1); // remove line from buffer including CRLF
54
55 if (state == 0) { // startup line
56 if (target->kind == YAHTTP_TYPE_REQUEST) {
57 std::string ver;
58 std::string tmpurl;
59 std::istringstream iss(line);
60 iss >> target->method >> tmpurl >> ver;
61 if (ver.size() == 0)
62 target->version = 9;
63 else if (ver.find("HTTP/0.9") == 0)
64 target->version = 9;
65 else if (ver.find("HTTP/1.0") == 0)
66 target->version = 10;
67 else if (ver.find("HTTP/1.1") == 0)
68 target->version = 11;
69 else
70 throw ParseError("HTTP version not supported");
71 // uppercase the target method
72 std::transform(target->method.begin(), target->method.end(), target->method.begin(), ::toupper);
73 target->url.parse(tmpurl);
74 target->getvars = Utility::parseUrlParameters(target->url.parameters);
75 state = 1;
76 } else if(target->kind == YAHTTP_TYPE_RESPONSE) {
77 std::string ver;
78 std::istringstream iss(line);
79 std::string::size_type pos1;
80 iss >> ver >> target->status;
81 std::getline(iss, target->statusText);
82 pos1=0;
83 while(pos1 < target->statusText.size() && YaHTTP::isspace(target->statusText.at(pos1))) pos1++;
84 target->statusText = target->statusText.substr(pos1);
85 if ((pos1 = target->statusText.find("\r")) != std::string::npos) {
86 target->statusText = target->statusText.substr(0, pos1-1);
87 }
88 if (ver.size() == 0) {
89 target->version = 9;
90 } else if (ver.find("HTTP/0.9") == 0)
91 target->version = 9;
92 else if (ver.find("HTTP/1.0") == 0)
93 target->version = 10;
94 else if (ver.find("HTTP/1.1") == 0)
95 target->version = 11;
96 else
97 throw ParseError("HTTP version not supported");
98 state = 1;
99 }
100 } else if (state == 1) {
101 std::string key,value;
102 size_t pos1;
103 if (line.empty()) {
104 chunked = (target->headers.find("transfer-encoding") != target->headers.end() && target->headers["transfer-encoding"] == "chunked");
105 state = 2;
106 break;
107 }
108 // split headers
109 if ((pos1 = line.find(":")) == std::string::npos) {
110 throw ParseError("Malformed header line");
111 }
112 key = line.substr(0, pos1);
113 value = line.substr(pos1 + 1);
114 for(std::string::iterator it=key.begin(); it != key.end(); it++)
115 if (YaHTTP::isspace(*it))
116 throw ParseError("Header key contains whitespace which is not allowed by RFC");
117
118 Utility::trim(value);
119 std::transform(key.begin(), key.end(), key.begin(), ::tolower);
120 // is it already defined
121
122 if (key == "set-cookie" && target->kind == YAHTTP_TYPE_RESPONSE) {
123 target->jar.parseSetCookieHeader(value);
124 } else if (key == "cookie" && target->kind == YAHTTP_TYPE_REQUEST) {
125 target->jar.parseCookieHeader(value);
126 } else {
127 if (key == "host" && target->kind == YAHTTP_TYPE_REQUEST) {
128 // maybe it contains port?
129 if ((pos1 = value.find(":")) == std::string::npos) {
130 target->url.host = value;
131 } else {
132 target->url.host = value.substr(0, pos1);
133 target->url.port = ::atoi(value.substr(pos1).c_str());
134 }
135 }
136 if (target->headers.find(key) != target->headers.end()) {
137 target->headers[key] = target->headers[key] + ";" + value;
138 } else {
139 target->headers[key] = std::move(value);
140 }
141 }
142 }
143 }
144
145 minbody = 0;
146 // check for expected body size
147 if (target->kind == YAHTTP_TYPE_REQUEST) maxbody = target->max_request_size;
148 else if (target->kind == YAHTTP_TYPE_RESPONSE) maxbody = target->max_response_size;
149 else maxbody = 0;
150
151 if (!chunked) {
152 if (target->headers.find("content-length") != target->headers.end()) {
153 std::istringstream maxbodyS(target->headers["content-length"]);
154 maxbodyS >> minbody;
155 maxbody = minbody;
156 }
157 if (minbody < 1) return true; // guess there isn't anything left.
158 if (target->kind == YAHTTP_TYPE_REQUEST && static_cast<ssize_t>(minbody) > target->max_request_size) throw ParseError("Max request body size exceeded");
159 else if (target->kind == YAHTTP_TYPE_RESPONSE && static_cast<ssize_t>(minbody) > target->max_response_size) throw ParseError("Max response body size exceeded");
160 }
161
162 if (maxbody == 0) hasBody = false;
163 else hasBody = true;
164
165 if (buffer.size() == 0) return ready();
166
167 while(buffer.size() > 0) {
168 if (chunked) {
169 if (chunk_size == 0) {
170 char buf[100];
171 // read chunk length
172 if ((pos = buffer.find('\n')) == std::string::npos) return false;
173 if (pos > 99)
174 throw ParseError("Impossible chunk_size");
175 buffer.copy(buf, pos);
176 buf[pos]=0; // just in case...
177 buffer.erase(buffer.begin(), buffer.begin()+pos+1); // remove line from buffer
178 if (sscanf(buf, "%x", &chunk_size) != 1) {
179 throw ParseError("Unable to parse chunk size");
180 }
181 if (chunk_size == 0) { state = 3; break; } // last chunk
182 if (chunk_size > (std::numeric_limits<decltype(chunk_size)>::max() - 2)) {
183 throw ParseError("Chunk is too large");
184 }
185 } else {
186 int crlf=1;
187 if (buffer.size() < static_cast<size_t>(chunk_size+1)) return false; // expect newline
188 if (buffer.at(chunk_size) == '\r') {
189 if (buffer.size() < static_cast<size_t>(chunk_size+2) || buffer.at(chunk_size+1) != '\n') return false; // expect newline after carriage return
190 crlf=2;
191 } else if (buffer.at(chunk_size) != '\n') return false;
192 std::string tmp = buffer.substr(0, chunk_size);
193 buffer.erase(buffer.begin(), buffer.begin()+chunk_size+crlf);
194 bodybuf << tmp;
195 chunk_size = 0;
196 if (buffer.size() == 0) break; // just in case
197 }
198 } else {
199 if (bodybuf.str().length() + buffer.length() > maxbody)
200 bodybuf << buffer.substr(0, maxbody - bodybuf.str().length());
201 else
202 bodybuf << buffer;
203 buffer = "";
204 }
205 }
206
207 if (chunk_size!=0) return false; // need more data
208
209 return ready();
210 };
211
212 void HTTPBase::write(std::ostream& os) const {
213 if (kind == YAHTTP_TYPE_REQUEST) {
214 std::ostringstream getparmbuf;
215 std::string getparms;
216 // prepare URL
217 for(strstr_map_t::const_iterator i = getvars.begin(); i != getvars.end(); i++) {
218 getparmbuf << Utility::encodeURL(i->first, false) << "=" << Utility::encodeURL(i->second, false) << "&";
219 }
220 if (getparmbuf.str().length() > 0) {
221 std::string buf = getparmbuf.str();
222 getparms = "?" + std::string(buf.begin(), buf.end() - 1);
223 }
224 else
225 getparms = "";
226 os << method << " " << url.path << getparms << " HTTP/" << versionStr(this->version);
227 } else if (kind == YAHTTP_TYPE_RESPONSE) {
228 os << "HTTP/" << versionStr(this->version) << " " << status << " ";
229 if (statusText.empty())
230 os << Utility::status2text(status);
231 else
232 os << statusText;
233 }
234 os << "\r\n";
235
236 bool cookieSent = false;
237 bool sendChunked = false;
238
239 if (this->version > 10) { // 1.1 or better
240 if (headers.find("content-length") == headers.end() && !this->is_multipart) {
241 // must use chunked on response
242 sendChunked = (kind == YAHTTP_TYPE_RESPONSE);
243 if ((headers.find("transfer-encoding") != headers.end() && headers.find("transfer-encoding")->second != "chunked")) {
244 throw YaHTTP::Error("Transfer-encoding must be chunked, or Content-Length defined");
245 }
246 if ((headers.find("transfer-encoding") == headers.end() && kind == YAHTTP_TYPE_RESPONSE)) {
247 sendChunked = true;
248 os << "Transfer-Encoding: chunked\r\n";
249 }
250 } else {
251 sendChunked = false;
252 }
253 }
254
255 // write headers
256 strstr_map_t::const_iterator iter = headers.begin();
257 while(iter != headers.end()) {
258 if (iter->first == "host" && (kind != YAHTTP_TYPE_REQUEST || version < 10)) { iter++; continue; }
259 if (iter->first == "transfer-encoding" && sendChunked) { iter++; continue; }
260 std::string header = Utility::camelizeHeader(iter->first);
261 if (header == "Cookie" || header == "Set-Cookie") cookieSent = true;
262 os << Utility::camelizeHeader(iter->first) << ": " << iter->second << "\r\n";
263 iter++;
264 }
265 if (version > 9 && !cookieSent && jar.cookies.size() > 0) { // write cookies
266 if (kind == YAHTTP_TYPE_REQUEST) {
267 bool first = true;
268 os << "Cookie: ";
269 for(strcookie_map_t::const_iterator i = jar.cookies.begin(); i != jar.cookies.end(); i++) {
270 if (first)
271 first = false;
272 else
273 os << "; ";
274 os << Utility::encodeURL(i->second.name) << "=" << Utility::encodeURL(i->second.value);
275 }
276 } else if (kind == YAHTTP_TYPE_RESPONSE) {
277 for(strcookie_map_t::const_iterator i = jar.cookies.begin(); i != jar.cookies.end(); i++) {
278 os << "Set-Cookie: ";
279 os << i->second.str() << "\r\n";
280 }
281 }
282 }
283 os << "\r\n";
284 #ifdef HAVE_CPP_FUNC_PTR
285 this->renderer(this, os, sendChunked);
286 #else
287 SendbodyRenderer r;
288 r(this, os, chunked)
289 #endif
290 };
291
292 std::ostream& operator<<(std::ostream& os, const Response &resp) {
293 resp.write(os);
294 return os;
295 };
296
297 std::istream& operator>>(std::istream& is, Response &resp) {
298 YaHTTP::AsyncResponseLoader arl;
299 arl.initialize(&resp);
300 while(is.good()) {
301 char buf[1024];
302 is.read(buf, 1024);
303 if (is.gcount()>0) { // did we actually read anything
304 is.clear();
305 if (arl.feed(std::string(buf, is.gcount())) == true) break; // completed
306 }
307 }
308 // throw unless ready
309 if (arl.ready() == false)
310 throw ParseError("Was not able to extract a valid Response from stream");
311 arl.finalize();
312 return is;
313 };
314
315 std::ostream& operator<<(std::ostream& os, const Request &req) {
316 req.write(os);
317 return os;
318 };
319
320 std::istream& operator>>(std::istream& is, Request &req) {
321 YaHTTP::AsyncRequestLoader arl;
322 arl.initialize(&req);
323 while(is.good()) {
324 char buf[1024];
325 is.read(buf, 1024);
326 if (is.gcount() > 0) { // did we actually read anything
327 is.clear();
328 if (arl.feed(std::string(buf, is.gcount())) == true) break; // completed
329 }
330 }
331 if (arl.ready() == false)
332 throw ParseError("Was not able to extract a valid Request from stream");
333 arl.finalize();
334 return is;
335 };
336 };