]>
git.ipfire.org Git - thirdparty/pdns.git/blob - ext/yahttp/yahttp/reqresp.cpp
7 template class AsyncLoader
<Request
>;
8 template class AsyncLoader
<Response
>;
10 bool isspace(char c
) {
11 return std::isspace(c
) != 0;
14 bool isspace(char c
, const std::locale
& loc
) {
15 return std::isspace(c
, loc
);
18 bool isxdigit(char c
) {
19 return std::isxdigit(c
) != 0;
22 bool isxdigit(char c
, const std::locale
& loc
) {
23 return std::isxdigit(c
, loc
);
26 bool isdigit(char c
) {
27 return std::isdigit(c
) != 0;
30 bool isdigit(char c
, const std::locale
& loc
) {
31 return std::isdigit(c
, loc
);
34 bool isalnum(char c
) {
35 return std::isalnum(c
) != 0;
38 bool isalnum(char c
, const std::locale
& loc
) {
39 return std::isalnum(c
, loc
);
43 bool AsyncLoader
<T
>::feed(const std::string
& somedata
) {
44 buffer
.append(somedata
);
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')
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
55 if (state
== 0) { // startup line
56 if (target
->kind
== YAHTTP_TYPE_REQUEST
) {
59 std::istringstream
iss(line
);
60 iss
>> target
->method
>> tmpurl
>> ver
;
63 else if (ver
.find("HTTP/0.9") == 0)
65 else if (ver
.find("HTTP/1.0") == 0)
67 else if (ver
.find("HTTP/1.1") == 0)
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
);
76 } else if(target
->kind
== YAHTTP_TYPE_RESPONSE
) {
78 std::istringstream
iss(line
);
79 std::string::size_type pos1
;
80 iss
>> ver
>> target
->status
;
81 std::getline(iss
, target
->statusText
);
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);
88 if (ver
.size() == 0) {
90 } else if (ver
.find("HTTP/0.9") == 0)
92 else if (ver
.find("HTTP/1.0") == 0)
94 else if (ver
.find("HTTP/1.1") == 0)
97 throw ParseError("HTTP version not supported");
100 } else if (state
== 1) {
101 std::string key
,value
;
104 chunked
= (target
->headers
.find("transfer-encoding") != target
->headers
.end() && target
->headers
["transfer-encoding"] == "chunked");
109 if ((pos1
= line
.find(":")) == std::string::npos
) {
110 throw ParseError("Malformed header line");
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");
118 Utility::trim(value
);
119 std::transform(key
.begin(), key
.end(), key
.begin(), ::tolower
);
120 // is it already defined
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
);
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
;
132 target
->url
.host
= value
.substr(0, pos1
);
133 target
->url
.port
= ::atoi(value
.substr(pos1
).c_str());
136 if (target
->headers
.find(key
) != target
->headers
.end()) {
137 target
->headers
[key
] = target
->headers
[key
] + ";" + value
;
139 target
->headers
[key
] = std::move(value
);
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
;
152 if (target
->headers
.find("content-length") != target
->headers
.end()) {
153 std::istringstream
maxbodyS(target
->headers
["content-length"]);
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");
162 if (maxbody
== 0) hasBody
= false;
165 if (buffer
.size() == 0) return ready();
167 while(buffer
.size() > 0) {
169 if (chunk_size
== 0) {
172 if ((pos
= buffer
.find('\n')) == std::string::npos
) return false;
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");
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");
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
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
);
196 if (buffer
.size() == 0) break; // just in case
199 if (bodybuf
.str().length() + buffer
.length() > maxbody
)
200 bodybuf
<< buffer
.substr(0, maxbody
- bodybuf
.str().length());
207 if (chunk_size
!=0) return false; // need more data
212 void HTTPBase::write(std::ostream
& os
) const {
213 if (kind
== YAHTTP_TYPE_REQUEST
) {
214 std::ostringstream getparmbuf
;
215 std::string getparms
;
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) << "&";
220 if (getparmbuf
.str().length() > 0) {
221 std::string buf
= getparmbuf
.str();
222 getparms
= "?" + std::string(buf
.begin(), buf
.end() - 1);
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
);
236 bool cookieSent
= false;
237 bool sendChunked
= false;
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");
246 if ((headers
.find("transfer-encoding") == headers
.end() && kind
== YAHTTP_TYPE_RESPONSE
)) {
248 os
<< "Transfer-Encoding: chunked\r\n";
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";
265 if (version
> 9 && !cookieSent
&& jar
.cookies
.size() > 0) { // write cookies
266 if (kind
== YAHTTP_TYPE_REQUEST
) {
269 for(strcookie_map_t::const_iterator i
= jar
.cookies
.begin(); i
!= jar
.cookies
.end(); i
++) {
274 os
<< Utility::encodeURL(i
->second
.name
) << "=" << Utility::encodeURL(i
->second
.value
);
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";
284 #ifdef HAVE_CPP_FUNC_PTR
285 this->renderer(this, os
, sendChunked
);
292 std::ostream
& operator<<(std::ostream
& os
, const Response
&resp
) {
297 std::istream
& operator>>(std::istream
& is
, Response
&resp
) {
298 YaHTTP::AsyncResponseLoader arl
;
299 arl
.initialize(&resp
);
303 if (is
.gcount()>0) { // did we actually read anything
305 if (arl
.feed(std::string(buf
, is
.gcount())) == true) break; // completed
308 // throw unless ready
309 if (arl
.ready() == false)
310 throw ParseError("Was not able to extract a valid Response from stream");
315 std::ostream
& operator<<(std::ostream
& os
, const Request
&req
) {
320 std::istream
& operator>>(std::istream
& is
, Request
&req
) {
321 YaHTTP::AsyncRequestLoader arl
;
322 arl
.initialize(&req
);
326 if (is
.gcount() > 0) { // did we actually read anything
328 if (arl
.feed(std::string(buf
, is
.gcount())) == true) break; // completed
331 if (arl
.ready() == false)
332 throw ParseError("Was not able to extract a valid Request from stream");