2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
17 #include "hphp/runtime/server/http_protocol.h"
18 #include "hphp/runtime/base/hphp_system.h"
19 #include "hphp/runtime/base/zend-url.h"
20 #include "hphp/runtime/base/zend-string.h"
21 #include "hphp/runtime/base/program-functions.h"
22 #include "hphp/runtime/base/runtime-option.h"
23 #include "hphp/runtime/server/source_root_info.h"
24 #include "hphp/runtime/server/request_uri.h"
25 #include "hphp/runtime/server/transport.h"
26 #include "hphp/util/logger.h"
27 #include "hphp/util/util.h"
28 #include "hphp/runtime/server/upload.h"
29 #include "hphp/runtime/server/replay_transport.h"
30 #include "hphp/runtime/server/virtual_host.h"
31 #include "hphp/runtime/base/http_client.h"
33 #define DEFAULT_POST_CONTENT_TYPE "application/x-www-form-urlencoded"
38 ///////////////////////////////////////////////////////////////////////////////
41 static bool read_all_post_data(Transport
*transport
,
42 const void *&data
, int &size
) {
43 if (transport
->hasMorePostData()) {
44 data
= Util::buffer_duplicate(data
, size
);
47 const void *extra
= transport
->getMorePostData(delta
);
48 if (size
+ delta
< VirtualHost::GetMaxPostSize()) {
49 data
= Util::buffer_append(data
, size
, extra
, delta
);
52 } while (transport
->hasMorePostData());
58 ///////////////////////////////////////////////////////////////////////////////
60 const VirtualHost
*HttpProtocol::GetVirtualHost(Transport
*transport
) {
61 if (!RuntimeOption::VirtualHosts
.empty()) {
62 string host
= transport
->getHeader("Host");
63 for (unsigned int i
= 0; i
< RuntimeOption::VirtualHosts
.size(); i
++) {
64 VirtualHostPtr vhost
= RuntimeOption::VirtualHosts
[i
];
65 if (vhost
->match(host
)) {
66 VirtualHost::SetCurrent(vhost
.get());
71 VirtualHost::SetCurrent(nullptr);
72 return VirtualHost::GetCurrent();
76 s_REQUEST_START_TIME("REQUEST_START_TIME"),
79 s_HHVM_JIT("HHVM_JIT"),
80 s_HPHP_SERVER("HPHP_SERVER"),
81 s_HPHP_HOTPROFILER("HPHP_HOTPROFILER"),
82 s_HTTP_HOST("HTTP_HOST"),
83 s_CONTENT_TYPE("CONTENT_TYPE"),
84 s_CONTENT_LENGTH("CONTENT_LENGTH"),
85 s_PHP_AUTH_USER("PHP_AUTH_USER"),
86 s_PHP_AUTH_PW("PHP_AUTH_PW"),
87 s_REQUEST_URI("REQUEST_URI"),
88 s_SCRIPT_URL("SCRIPT_URL"),
89 s_SCRIPT_URI("SCRIPT_URI"),
90 s_SCRIPT_NAME("SCRIPT_NAME"),
91 s_PHP_SELF("PHP_SELF"),
92 s_SCRIPT_FILENAME("SCRIPT_FILENAME"),
93 s_PATH_TRANSLATED("PATH_TRANSLATED"),
94 s_PATH_INFO("PATH_INFO"),
100 s__REQUEST("_REQUEST"),
102 s__COOKIE("_COOKIE"),
103 s_HTTP_RAW_POST_DATA("HTTP_RAW_POST_DATA"),
105 s_GATEWAY_INTERFACE("GATEWAY_INTERFACE"),
106 s_CGI_1_1("CGI/1.1"),
107 s_SERVER_ADDR("SERVER_ADDR"),
108 s_SERVER_NAME("SERVER_NAME"),
109 s_SERVER_PORT("SERVER_PORT"),
110 s_SERVER_SOFTWARE("SERVER_SOFTWARE"),
111 s_SERVER_PROTOCOL("SERVER_PROTOCOL"),
112 s_SERVER_ADMIN("SERVER_ADMIN"),
113 s_SERVER_SIGNATURE("SERVER_SIGNATURE"),
114 s_REQUEST_METHOD("REQUEST_METHOD"),
120 s_REQUEST_TIME("REQUEST_TIME"),
121 s_REQUEST_TIME_FLOAT("REQUEST_TIME_FLOAT"),
122 s_QUERY_STRING("QUERY_STRING"),
123 s_REMOTE_ADDR("REMOTE_ADDR"),
124 s_REMOTE_HOST("REMOTE_HOST"),
125 s_REMOTE_PORT("REMOTE_PORT"),
126 s_DOCUMENT_ROOT("DOCUMENT_ROOT"),
127 s_THREAD_TYPE("THREAD_TYPE");
130 * PHP has "EGPCS" processing order of these global variables, and this
131 * order is important in preparing $_REQUEST that needs to know which to
132 * overwrite what when name happens to be the same.
134 void HttpProtocol::PrepareSystemVariables(Transport
*transport
,
136 const SourceRootInfo
&sri
) {
137 GlobalVariables
*g
= get_global_variables();
138 const VirtualHost
*vhost
= VirtualHost::GetCurrent();
140 Variant
& server
= g
->getRef(s__SERVER
);
141 server
.set(s_REQUEST_START_TIME
, time(nullptr));
144 Variant
& env
= g
->getRef(s__ENV
);
145 process_env_variables(env
);
148 if (RuntimeOption::EvalJit
) {
149 env
.set(s_HHVM_JIT
, 1);
152 bool isServer
= RuntimeOption::ServerExecutionMode();
154 env
.set(s_HPHP_SERVER
, 1);
156 env
.set(s_HPHP_HOTPROFILER
, 1);
160 Variant
&request
= g
->getRef(s__REQUEST
);
162 // $_GET and $_REQUEST
163 if (!r
.queryString().empty()) {
164 Variant
&get
= g
->getRef(s__GET
);
165 DecodeParameters(get
, r
.queryString().data(),
166 r
.queryString().size());
167 CopyParams(request
, get
);
170 string contentType
= transport
->getHeader("Content-Type");
171 string contentLength
= transport
->getHeader("Content-Length");
172 // $_POST and $_REQUEST
173 if (transport
->getMethod() == Transport::Method::POST
) {
174 bool needDelete
= false;
176 const void *data
= transport
->getPostData(size
);
178 assert(((char*)data
)[size
] == 0); // we need a NULL terminated string
180 int content_length
= atoi(contentLength
.c_str());
181 bool rfc1867Post
= IsRfc1867(contentType
, boundary
);
184 if (content_length
> VirtualHost::GetMaxPostSize()) {
185 // $_POST and $_FILES are empty
186 Logger::Warning("POST Content-Length of %d bytes exceeds "
187 "the limit of %" PRId64
" bytes",
188 content_length
, VirtualHost::GetMaxPostSize());
189 while (transport
->hasMorePostData()) {
191 transport
->getMorePostData(delta
);
194 if (transport
->hasMorePostData()) {
196 data
= Util::buffer_duplicate(data
, size
);
198 DecodeRfc1867(transport
, g
->getRef(s__POST
), g
->getRef(s__FILES
),
199 content_length
, data
, size
, boundary
);
201 assert(!transport
->getFiles(files
));
203 needDelete
= read_all_post_data(transport
, data
, size
);
205 bool decodeData
= strncasecmp(contentType
.c_str(),
206 DEFAULT_POST_CONTENT_TYPE
,
207 sizeof(DEFAULT_POST_CONTENT_TYPE
)-1) == 0;
208 // Always decode data for now. (macvicar)
212 DecodeParameters(g
->getRef(s__POST
), (const char*)data
, size
, true);
215 bool ret
= transport
->getFiles(files
);
217 g
->getRef(s__FILES
) = unserialize_from_string(files
);
220 CopyParams(request
, g
->getRef(s__POST
));
222 if (RuntimeOption::AlwaysPopulateRawPostData
&&
223 uint32_t(size
) <= StringData::MaxSize
) {
224 g
->getRef(s_HTTP_RAW_POST_DATA
) =
225 String((char*)data
, size
, AttachString
);
230 // For literal we disregard RuntimeOption::AlwaysPopulateRawPostData
231 if (uint32_t(size
) <= StringData::MaxSize
) {
232 g
->getRef(s_HTTP_RAW_POST_DATA
) =
233 String((char*)data
, size
, CopyString
);
240 string cookie_data
= transport
->getHeader("Cookie");
241 if (!cookie_data
.empty()) {
243 sb
.append(cookie_data
);
244 DecodeCookies(g
->getRef(s__COOKIE
), (char*)sb
.data());
245 CopyParams(request
, g
->getRef(s__COOKIE
));
250 // HTTP_ headers -- we don't exclude headers we handle elsewhere (e.g.,
251 // Content-Type, Authorization), since the CGI "spec" merely says the server
252 // "may" exclude them; this is not what APE does, but it's harmless.
254 transport
->getHeaders(headers
);
256 static int bad_request_count
= -1;
257 for (HeaderMap::const_iterator iter
= headers
.begin();
258 iter
!= headers
.end(); ++iter
) {
259 const vector
<string
> &values
= iter
->second
;
261 // Detect suspicious headers. We are about to modify header names
262 // for the SERVER variable. This means that it is possible to
263 // deliberately cause a header collision, which an attacker could
264 // use to sneak a header past a proxy that would either overwrite
265 // or filter it otherwise. Client code should use
266 // apache_request_headers() to retrieve the original headers if
267 // they are security-critical.
268 if (RuntimeOption::LogHeaderMangle
!= 0) {
269 String key
= "HTTP_";
270 key
+= StringUtil::ToUpper(iter
->first
).replace("-","_");
271 if (server
.asArrRef().exists(key
)) {
272 if (!(++bad_request_count
% RuntimeOption::LogHeaderMangle
)) {
274 "HeaderMangle warning: "
275 "The header %s overwrote another header which mapped to the same "
276 "key. This happens because PHP normalises - to _, ie AN_EXAMPLE "
277 "and AN-EXAMPLE are equivalent. You should treat this as "
279 iter
->first
.c_str());
284 for (unsigned int i
= 0; i
< values
.size(); i
++) {
285 String key
= "HTTP_";
286 key
+= StringUtil::ToUpper(iter
->first
).replace("-", "_");
287 server
.set(key
, String(values
[i
]));
291 string host
= transport
->getHeader("Host");
292 String
hostName(VirtualHost::GetCurrent()->serverName(host
));
293 string
hostHeader(host
);
294 if (hostHeader
.empty()) {
295 server
.set(s_HTTP_HOST
, hostName
);
296 StackTraceNoHeap::AddExtraLogging("Server", hostName
.data());
298 StackTraceNoHeap::AddExtraLogging("Server", hostHeader
.c_str());
300 if (hostName
.empty() || RuntimeOption::ForceServerNameToHeader
) {
301 hostName
= hostHeader
;
302 // _SERVER['SERVER_NAME'] shouldn't contain the port number
303 int colonPos
= hostName
.find(':');
304 if (colonPos
!= String::npos
) {
305 hostName
= hostName
.substr(0, colonPos
);
309 // APE sets CONTENT_TYPE and CONTENT_LENGTH without HTTP_
310 if (!contentType
.empty()) {
311 server
.set(s_CONTENT_TYPE
, String(contentType
));
313 if (!contentLength
.empty()) {
314 server
.set(s_CONTENT_LENGTH
, String(contentLength
));
317 // APE processes Authorization: Basic into PHP_AUTH_USER and PHP_AUTH_PW
318 string authorization
= transport
->getHeader("Authorization");
319 if (!authorization
.empty()) {
320 if (strncmp(authorization
.c_str(), "Basic ", 6) == 0) {
321 // it's safe to pass this as a string literal since authorization
322 // outlives decodedAuth; this saves us a superfluous copy.
324 StringUtil::Base64Decode(String(authorization
.c_str() + 6));
325 int colonPos
= decodedAuth
.find(':');
326 if (colonPos
!= String::npos
) {
327 server
.set(s_PHP_AUTH_USER
, decodedAuth
.substr(0, colonPos
));
328 server
.set(s_PHP_AUTH_PW
, decodedAuth
.substr(colonPos
+ 1));
333 server
.set(s_REQUEST_URI
, String(transport
->getUrl(), CopyString
));
334 server
.set(s_SCRIPT_URL
, r
.originalURL());
335 String
prefix(transport
->isSSL() ? "https://" : "http://");
336 String
port_suffix("");
338 // Need to append port
339 if (!transport
->isSSL() && RuntimeOption::ServerPort
!= 80) {
340 port_suffix
= folly::format(":{}", RuntimeOption::ServerPort
).str();
342 server
.set(s_SCRIPT_URI
,
343 String(prefix
+ (hostHeader
.empty() ? hostName
+ port_suffix
:
344 String(hostHeader
)) + r
.originalURL()));
347 // when URL is rewritten, PHP decided to put original URL as SCRIPT_NAME
348 String name
= r
.originalURL();
349 if (!r
.pathInfo().empty()) {
350 int pos
= name
.find(r
.pathInfo());
352 name
= name
.substr(0, pos
);
355 if (r
.defaultDoc()) {
356 if (!name
.empty() && name
[name
.length() - 1] != '/') {
359 name
+= String(RuntimeOption::DefaultDocument
);
361 server
.set(s_SCRIPT_NAME
, name
);
363 server
.set(s_SCRIPT_NAME
, r
.resolvedURL());
366 server
.set(s_PHP_SELF
, r
.originalURL());
368 server
.set(s_PHP_SELF
, r
.resolvedURL() + r
.origPathInfo());
371 server
.set(s_SCRIPT_FILENAME
, r
.absolutePath());
372 if (r
.pathInfo().empty()) {
373 server
.set(s_PATH_TRANSLATED
, r
.absolutePath());
375 server
.set(s_PATH_TRANSLATED
,
376 String(vhost
->getDocumentRoot() + r
.pathInfo().data()));
377 server
.set(s_PATH_INFO
, r
.pathInfo());
380 server
.set(s_argv
, CREATE_VECTOR1(r
.queryString()));
381 server
.set(s_argc
, 1);
382 server
.set(s_GATEWAY_INTERFACE
, s_CGI_1_1
);
383 server
.set(s_SERVER_ADDR
, String(RuntimeOption::ServerPrimaryIP
));
384 server
.set(s_SERVER_NAME
, hostName
);
385 server
.set(s_SERVER_PORT
, RuntimeOption::ServerPort
);
386 server
.set(s_SERVER_SOFTWARE
, s_HPHP
);
387 server
.set(s_SERVER_PROTOCOL
, "HTTP/" + transport
->getHTTPVersion());
388 server
.set(s_SERVER_ADMIN
, empty_string
);
389 server
.set(s_SERVER_SIGNATURE
, empty_string
);
390 switch (transport
->getMethod()) {
391 case Transport::Method::GET
: server
.set(s_REQUEST_METHOD
, s_GET
); break;
392 case Transport::Method::HEAD
: server
.set(s_REQUEST_METHOD
, s_HEAD
); break;
393 case Transport::Method::POST
:
394 if (transport
->getExtendedMethod() == nullptr) {
395 server
.set(s_REQUEST_METHOD
, s_POST
);
397 server
.set(s_REQUEST_METHOD
, transport
->getExtendedMethod());
400 default: server
.set(s_REQUEST_METHOD
, empty_string
); break;
403 struct timeval tp
= {0};
405 if (!gettimeofday(&tp
, nullptr)) {
406 now_double
= (double)(tp
.tv_sec
+ tp
.tv_usec
/ 1000000.00);
410 now_double
= (double)now
;
412 server
.set(s_HTTPS
, transport
->isSSL() ? s_1
: empty_string
);
413 server
.set(s_REQUEST_TIME
, now
);
414 server
.set(s_REQUEST_TIME_FLOAT
, now_double
);
415 server
.set(s_QUERY_STRING
, r
.queryString());
417 server
.set(s_REMOTE_ADDR
, String(transport
->getRemoteHost(), CopyString
));
418 server
.set(s_REMOTE_HOST
, empty_string
); // I don't think we need to nslookup
419 server
.set(s_REMOTE_PORT
, transport
->getRemotePort());
421 server
.set(s_DOCUMENT_ROOT
, String(vhost
->getDocumentRoot()));
423 for (map
<string
, string
>::const_iterator iter
=
424 RuntimeOption::ServerVariables
.begin();
425 iter
!= RuntimeOption::ServerVariables
.end(); ++iter
) {
426 server
.set(String(iter
->first
), String(iter
->second
));
428 const map
<string
, string
> &vServerVars
= vhost
->getServerVars();
429 for (map
<string
, string
>::const_iterator iter
=
431 iter
!= vServerVars
.end(); ++iter
) {
432 server
.set(String(iter
->first
), String(iter
->second
));
434 sri
.setServerVariables(server
);
436 const char *threadType
= transport
->getThreadTypeName();
437 server
.set(s_THREAD_TYPE
, threadType
);
438 StackTraceNoHeap::AddExtraLogging("ThreadType", threadType
);
441 std::string
HttpProtocol::RecordRequest(Transport
*transport
) {
442 char tmpfile
[PATH_MAX
+ 1];
443 if (RuntimeOption::RecordInput
) {
444 strcpy(tmpfile
, "/tmp/hphp_request_XXXXXX");
445 close(mkstemp(tmpfile
));
448 rt
.recordInput(transport
, tmpfile
);
449 Logger::Info("request recorded in %s", tmpfile
);
455 void HttpProtocol::ClearRecord(bool success
, const std::string
&tmpfile
) {
456 if (success
&& RuntimeOption::ClearInputOnSuccess
&& !tmpfile
.empty()) {
457 unlink(tmpfile
.c_str());
458 Logger::Info("request %s deleted", tmpfile
.c_str());
462 ///////////////////////////////////////////////////////////////////////////////
464 void HttpProtocol::CopyParams(Variant
&dest
, Variant
&src
) {
466 Array srcArray
= src
.toArray();
467 for (ArrayIter
iter(srcArray
); iter
; ++iter
) {
468 dest
.set(iter
.first(), iter
.second());
473 void HttpProtocol::DecodeParameters(Variant
&variables
, const char *data
,
474 int size
, bool post
/* = false */) {
475 if (data
== nullptr || size
== 0) {
479 const char *s
= data
;
480 const char *e
= s
+ size
;
483 while (s
< e
&& (p
= (const char *)memchr(s
, '&', (e
- s
)))) {
485 if ((val
= (const char *)memchr(s
, '=', (p
- s
)))) {
487 char *name
= url_decode(s
, len
);
488 String
sname(name
, len
, AttachString
);
492 char *value
= url_decode(val
, len
);
493 if (RuntimeOption::EnableMagicQuotesGpc
) {
494 char *slashedvalue
= string_addslashes(value
, len
);
496 value
= slashedvalue
;
498 String
svalue(value
, len
, AttachString
);
500 register_variable(variables
, (char*)sname
.data(), svalue
);
503 char *name
= url_decode(s
, len
);
504 String
sname(name
, len
, AttachString
);
505 register_variable(variables
, (char*)sname
.data(), "");
515 void HttpProtocol::DecodeCookies(Variant
&variables
, char *data
) {
516 assert(data
&& *data
);
518 char *strtok_buf
= nullptr;
519 char *var
= strtok_r(data
, ";", &strtok_buf
);
521 char *val
= strchr(var
, '=');
523 // Remove leading spaces from cookie names, needed for multi-cookie
524 // header where ; can be followed by a space */
525 while (isspace(*var
)) {
529 if (var
!= val
&& *var
!= '\0') {
530 if (val
) { /* have a value */
532 char *name
= url_decode(var
, len
);
533 String
sname(name
, len
, AttachString
);
537 char *value
= url_decode(val
, len
);
538 if (RuntimeOption::EnableMagicQuotesGpc
) {
539 char *slashedvalue
= string_addslashes(value
, len
);
541 value
= slashedvalue
;
543 String
svalue(value
, len
, AttachString
);
545 register_variable(variables
, (char*)sname
.data(), svalue
, false);
547 int len
= strlen(var
);
548 char *name
= url_decode(var
, len
);
549 String
sname(name
, len
, AttachString
);
551 register_variable(variables
, (char*)sname
.data(), "", false);
555 var
= strtok_r(nullptr, ";", &strtok_buf
);
559 bool HttpProtocol::IsRfc1867(const string contentType
, string
&boundary
) {
560 if (contentType
.empty()) return false;
561 const char *ctstr
= contentType
.c_str();
564 for (s
= (char*)ctstr
; *s
&& !(*s
== ';' || *s
== ',' || *s
== ' '); s
++) ;
565 if (strncasecmp(ctstr
, MULTIPART_CONTENT_TYPE
, s
- ctstr
)) {
568 s
= strstr(s
, "boundary");
569 if (!s
|| !(s
=strchr(s
, '='))) {
570 Logger::Warning("Missing boundary in multipart/form-data POST data");
578 Logger::Warning("Invalid boundary in multipart/form-data POST data");
582 /* search for the end of the boundary */
592 void HttpProtocol::DecodeRfc1867(Transport
*transport
,
593 Variant
&post
, Variant
&files
,
594 int contentLength
, const void *&data
,
595 int &size
, string boundary
) {
596 rfc1867PostHandler(transport
, post
, files
, contentLength
,
597 data
, size
, boundary
);
600 const char *HttpProtocol::GetReasonString(int code
) {
602 case 100: return "Continue";
603 case 101: return "Switching Protocols";
604 case 200: return "OK";
605 case 201: return "Created";
606 case 202: return "Accepted";
607 case 203: return "Non-Authoritative Information";
608 case 204: return "No Content";
609 case 205: return "Reset Content";
610 case 206: return "Partial Content";
611 case 207: return "Multi-Status";
612 case 300: return "Multiple Choices";
613 case 301: return "Moved Permanently";
614 case 302: return "Found";
615 case 303: return "See Other";
616 case 304: return "Not Modified";
617 case 305: return "Use Proxy";
618 case 307: return "Temporary Redirect";
619 case 400: return "Bad Request";
620 case 401: return "Unauthorized";
621 case 402: return "Payment Required";
622 case 403: return "Forbidden";
623 case 404: return "Not Found";
624 case 405: return "Method Not Allowed";
625 case 406: return "Not Acceptable";
626 case 407: return "Proxy Authentication Required";
627 case 408: return "Request Time-out";
628 case 409: return "Conflict";
629 case 410: return "Gone";
630 case 411: return "Length Required";
631 case 412: return "Precondition Failed";
632 case 413: return "Request Entity Too Large";
633 case 414: return "Request-URI Too Large";
634 case 415: return "Unsupported Media Type";
635 case 416: return "Requested range not satisfiable";
636 case 417: return "Expectation Failed";
637 case 500: return "Internal Server Error";
638 case 501: return "Not Implemented";
639 case 502: return "Bad Gateway";
640 case 503: return "Service Unavailable";
641 case 504: return "Gateway Time-out";
642 case 505: return "HTTP Version not supported";
644 return "Bad Response";
647 ///////////////////////////////////////////////////////////////////////////////
649 bool HttpProtocol::ProxyRequest(Transport
*transport
, bool force
,
650 const std::string
&url
,
651 int &code
, std::string
&error
,
652 StringBuffer
&response
,
653 HeaderMap
*extraHeaders
/* = NULL */) {
655 if (transport
->headersSent()) {
656 raise_warning("Cannot proxy request - headers already sent");
660 HeaderMap requestHeaders
;
661 transport
->getHeaders(requestHeaders
);
663 for (HeaderMap::const_iterator iter
= extraHeaders
->begin();
664 iter
!= extraHeaders
->end(); ++iter
) {
665 vector
<string
> &values
= requestHeaders
[iter
->first
];
666 values
.insert(values
.end(), iter
->second
.begin(), iter
->second
.end());
671 const char *data
= nullptr;
672 if (transport
->getMethod() == Transport::Method::POST
) {
673 data
= (const char *)transport
->getPostData(size
);
676 code
= 0; // HTTP status of curl or 0 for "no server response code"
677 vector
<String
> responseHeaders
;
680 code
= http
.post(url
.c_str(), data
, size
, response
, &requestHeaders
,
683 code
= http
.get(url
.c_str(), response
, &requestHeaders
, &responseHeaders
);
686 if (!force
) return false; // so we can retry
687 Logger::Error("Unable to proxy %s: %s", url
.c_str(),
688 http
.getLastError().c_str());
689 error
= http
.getLastError();
693 for (unsigned int i
= 0; i
< responseHeaders
.size(); i
++) {
694 String
&header
= responseHeaders
[i
];
695 if (header
.find(":") != String::npos
&&
696 header
.find("Content-Length: ") != 0 &&
697 header
.find("Client-Transfer-Encoding: ") != 0 &&
698 header
.find("Transfer-Encoding: ") != 0 &&
699 header
.find("Connection: ") != 0) {
700 transport
->addHeader(header
.data());
703 const char* respData
= response
.data();
707 Logger::Verbose("Response code was %d when proxying %s", code
, url
.c_str());
711 ///////////////////////////////////////////////////////////////////////////////