| /* |
| * Copyright 2015 Twitter, Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // Implements the HTTPRequest and HTTPResponse functions and some utility |
| // functions |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| #include "network/httputils.h" |
| #include <stdlib.h> |
| #include <sys/queue.h> |
| #include <evhttp.h> |
| #include <string> |
| #include <vector> |
| #include "glog/logging.h" |
| |
| std::unordered_map<char, sp_string> PopulateEncodeMap() { |
| std::unordered_map<char, sp_string> retval; |
| retval[' '] = "%20"; |
| retval['<'] = "%3C"; |
| retval['>'] = "%3E"; |
| retval['#'] = "%23"; |
| retval['%'] = "%25"; |
| retval['{'] = "%7B"; |
| retval['}'] = "%7D"; |
| retval['|'] = "%7C"; |
| retval['\\'] = "%5C"; |
| retval['^'] = "%5E"; |
| retval['~'] = "%7E"; |
| retval['['] = "%5B"; |
| retval[']'] = "%5D"; |
| retval['`'] = "%60"; |
| retval[';'] = "%3B"; |
| retval['/'] = "%2F"; |
| retval['?'] = "%3F"; |
| retval[':'] = "%3A"; |
| retval['@'] = "%40"; |
| retval['='] = "%3D"; |
| retval['&'] = "%26"; |
| retval['$'] = "%24"; |
| retval['"'] = "%22"; |
| return retval; |
| } |
| |
| std::unordered_map<char, sp_string> BaseHTTPRequest::httpencodemap_ = PopulateEncodeMap(); |
| |
| static sp_string ExtractHostAndPort(const sp_string& _host, sp_int32& _port) { |
| size_t sz = _host.find(":"); |
| if (sz == std::string::npos) { |
| _port = 80; |
| return _host; |
| } else { |
| sp_string retval = std::string(_host, 0, sz); |
| if (_host.size() == sz + 1) { |
| _port = 80; |
| } else { |
| _port = atoi(_host.c_str() + sz + 1); |
| } |
| return retval; |
| } |
| } |
| |
| BaseHTTPRequest::BaseHTTPRequest(HTTPRequestType _type) { type_ = _type; } |
| |
| BaseHTTPRequest::~BaseHTTPRequest() {} |
| |
| BaseHTTPRequest::HTTPRequestType GetRequestType(evhttp_cmd_type _type) { |
| switch (_type) { |
| case EVHTTP_REQ_GET: |
| return BaseHTTPRequest::GET; |
| case EVHTTP_REQ_POST: |
| return BaseHTTPRequest::POST; |
| case EVHTTP_REQ_HEAD: |
| return BaseHTTPRequest::HEAD; |
| case EVHTTP_REQ_PUT: |
| return BaseHTTPRequest::PUT; |
| case EVHTTP_REQ_DELETE: |
| return BaseHTTPRequest::DELETE; |
| default: |
| return BaseHTTPRequest::UNKNOWN; |
| } |
| } |
| |
| // decode a possibly encoded value passed via http. Based loosely on |
| // evhttp_decode_uri from libevent but is modified because of several |
| // bugs in the libevent code. |
| sp_string BaseHTTPRequest::http_decode(const sp_string& value) { |
| char c; |
| sp_string retval; |
| |
| for (size_t i = 0; i < value.size(); ++i) { |
| c = value[i]; |
| if (c == '+') { |
| retval.push_back(' '); |
| } else if (c == '%') { |
| if ((i + 2) < value.size() && isxdigit((unsigned char)value[i + 1]) && |
| isxdigit((unsigned char)value[i + 2])) { |
| char tmp[] = {value[i + 1], value[i + 2], '\0'}; |
| retval.push_back(static_cast<char>(strtol(tmp, NULL, 16))); |
| i += 2; |
| } else { |
| // if we got a '%' but there is not enough chars left in the string |
| // or it is not a encoded value |
| retval.push_back(c); |
| } |
| } else { |
| retval.push_back(c); |
| } |
| } |
| // retval.push_back('\0'); |
| return std::string(retval); |
| } |
| |
| sp_string BaseHTTPRequest::http_encode(const sp_string& _value) { |
| sp_string retval; |
| for (size_t i = 0; i < _value.size(); ++i) { |
| if (httpencodemap_.find(_value[i]) == httpencodemap_.end()) { |
| retval.push_back(_value[i]); |
| } else { |
| retval.append(httpencodemap_[_value[i]]); |
| } |
| } |
| return retval; |
| } |
| |
| // the evhttp code has a bug in it where it decodes the URI string before it |
| // checks for invalid line feeds... which stops us from handling 'textarea' |
| // form entries that are multi-line. So we will use our own. Based on the |
| // code in libevent/http.c but customised. |
| sp_string BaseHTTPRequest::parse_query(const char* _uri) { |
| std::string parsed_uri; |
| if (strchr(_uri, '?') == NULL) |
| parsed_uri = http_decode(std::string(_uri)); |
| else |
| parsed_uri = http_decode(std::string(_uri, strchr(_uri, '?') - _uri)); |
| return parsed_uri; |
| } |
| |
| void BaseHTTPRequest::parse_keyvalues(HTTPKeyValuePairs& _params, const char* _data) { |
| const char* argument = _data; |
| |
| while (argument != NULL && *argument != '\0') { |
| const char* value = strchr(argument, '='); |
| if (!value) return; |
| sp_string key = std::string(argument, value - argument); |
| key = http_decode(key); |
| value++; // move past = |
| if (!value || *value == '\0') { |
| _params.push_back(make_pair(key, "")); |
| return; |
| } |
| const char* next = strchr(value, '&'); |
| if (!next) { |
| sp_string val = http_decode(value); |
| _params.push_back(make_pair(key, val)); |
| return; |
| } |
| sp_string val = http_decode(std::string(value, next - value)); |
| _params.push_back(make_pair(key, val)); |
| argument = next + 1; |
| } |
| return; |
| } |
| |
| sp_string BaseHTTPRequest::encode_keyvalues(std::unordered_map<sp_string, sp_string>& _kv) { |
| std::ostringstream encoded; |
| auto kvpair = _kv.begin(); |
| encoded << http_encode(kvpair->first) << "=" << http_encode(kvpair->second); |
| |
| for (; kvpair != _kv.end(); kvpair++) { |
| encoded << "&" << http_encode(kvpair->first) << "=" << http_encode(kvpair->second); |
| } |
| |
| return encoded.str(); |
| } |
| |
| bool BaseHTTPRequest::ExtractHostNameAndQuery(const sp_string& _url, sp_string& _host, |
| sp_int32& _port, sp_string& _uri) { |
| const sp_string prefix = "http://"; |
| size_t sz = _url.find(prefix); |
| if (sz != 0) return false; |
| sz += prefix.size(); |
| sz = _url.find("/", sz); |
| if (sz == std::string::npos) { |
| _host = std::string(_url, prefix.size(), _url.size() - prefix.size()); |
| _uri = "/"; |
| _host = ExtractHostAndPort(_host, _port); |
| if (_host.empty()) return false; |
| return true; |
| } else { |
| _host = std::string(_url, prefix.size(), sz - prefix.size()); |
| _uri = std::string(_url, sz, _url.size() - sz); |
| _host = ExtractHostAndPort(_host, _port); |
| if (_host.empty()) return false; |
| return true; |
| } |
| } |
| |
| IncomingHTTPRequest::IncomingHTTPRequest(struct evhttp_request* _request) |
| : BaseHTTPRequest(GetRequestType(_request->type)) { |
| request_ = _request; |
| query_ = parse_query(request_->uri); |
| if (request_->type == EVHTTP_REQ_POST || request_->type == EVHTTP_REQ_PUT) { |
| const char* content_type = evhttp_find_header(_request->input_headers, "Content-Type"); |
| if (!content_type || strcmp(content_type, "application/x-www-form-urlencoded") == 0) { |
| char* bufdata = reinterpret_cast<char*>(EVBUFFER_DATA(request_->input_buffer)); |
| size_t buflen = EVBUFFER_LENGTH(request_->input_buffer); |
| std::string body = std::string(bufdata, buflen); |
| parse_keyvalues(kv_, body.c_str()); |
| } else { |
| // Unlike most HTTP Servers, we still will parse post's parameters |
| if (strchr(request_->uri, '?') != NULL) { |
| parse_keyvalues(kv_, strchr(request_->uri, '?') + 1); |
| } |
| } |
| } else { |
| if (strchr(request_->uri, '?') != NULL) { |
| parse_keyvalues(kv_, strchr(request_->uri, '?') + 1); |
| } |
| } |
| } |
| |
| IncomingHTTPRequest::~IncomingHTTPRequest() {} |
| |
| const sp_string& IncomingHTTPRequest::GetQuery() const { return query_; } |
| |
| const sp_string& IncomingHTTPRequest::GetValue(const sp_string& _key) const { |
| for (size_t i = 0; i < kv_.size(); ++i) { |
| if (kv_[i].first == _key) { |
| return kv_[i].second; |
| } |
| } |
| return EMPTY_STRING; |
| } |
| |
| bool IncomingHTTPRequest::GetAllValues(const sp_string& _key, |
| std::vector<sp_string>& _values) const { |
| bool retval = false; |
| for (size_t i = 0; i < kv_.size(); ++i) { |
| if (kv_[i].first == _key) { |
| _values.push_back(kv_[i].second); |
| retval = true; |
| } |
| } |
| return retval; |
| } |
| |
| void IncomingHTTPRequest::AddValue(const sp_string& _key, const sp_string& _value) { |
| kv_.push_back(make_pair(_key, _value)); |
| } |
| |
| // returns the value of the given header attribute |
| const sp_string IncomingHTTPRequest::GetHeader(const sp_string& _key) { |
| const char* header = evhttp_find_header(request_->input_headers, _key.c_str()); |
| if (header) { |
| return header; |
| } else { |
| return EMPTY_STRING; |
| } |
| } |
| |
| unsigned char* IncomingHTTPRequest::ExtractFromPostData(sp_int32 _start, sp_uint32 _len) { |
| unsigned char* contig_buffer = evbuffer_pullup(request_->input_buffer, _start + _len); |
| return contig_buffer + _start; |
| } |
| |
| // IP address of the source endpoint of this request |
| sp_string IncomingHTTPRequest::GetRemoteHost() { |
| sp_string x_forwarded_for = GetHeader("X-Forwarded-For"); |
| if (x_forwarded_for.empty()) { |
| return request_->remote_host; |
| } else { |
| if (x_forwarded_for.find(',') == std::string::npos) { |
| return x_forwarded_for; |
| } else { |
| return std::string(x_forwarded_for, 0, x_forwarded_for.find(',')); |
| } |
| } |
| } |
| |
| // Port of the source endpoint of this request |
| sp_int32 IncomingHTTPRequest::GetRemotePort() { return request_->remote_port; } |
| |
| // Calculate the total payload size for the request |
| sp_int32 IncomingHTTPRequest::GetRequestSize() { |
| std::string uri = request_->uri; |
| sp_int32 uri_size = uri.length() * sizeof(char); |
| |
| evkeyval* p; |
| p = TAILQ_FIRST(request_->input_headers); |
| sp_int32 headers_size = 0; |
| while (p) { |
| std::string hkey = p->key; |
| std::string hval = p->value; |
| headers_size += (hkey.length() * sizeof(char)) + (hval.length() * sizeof(char)); |
| p = TAILQ_NEXT(p, next); |
| } |
| |
| return uri_size + headers_size + EVBUFFER_LENGTH(request_->input_buffer); |
| } |
| |
| sp_int32 IncomingHTTPRequest::GetPayloadSize() { return EVBUFFER_LENGTH(request_->input_buffer); } |
| |
| OutgoingHTTPRequest::OutgoingHTTPRequest(const sp_string& _host, sp_int32 _port, |
| const sp_string& _uri, |
| BaseHTTPRequest::HTTPRequestType _type, |
| const HTTPKeyValuePairs& _kvs) |
| : BaseHTTPRequest(_type) { |
| host_ = _host; |
| port_ = _port; |
| query_ = _uri; |
| kv_ = _kvs; |
| } |
| |
| OutgoingHTTPRequest::~OutgoingHTTPRequest() {} |
| |
| void OutgoingHTTPRequest::AddValue(const sp_string& _key, const sp_string& _value) { |
| kv_.push_back(make_pair(_key, _value)); |
| } |
| |
| void OutgoingHTTPRequest::SetHeader(const sp_string& _key, const sp_string& _value) { |
| header_[_key] = _value; |
| } |
| |
| void OutgoingHTTPRequest::ExtendQuery(const sp_string& _body) { query_ += "?" + _body; } |
| |
| OutgoingHTTPResponse::OutgoingHTTPResponse(IncomingHTTPRequest* _request) |
| : response_(_request->request_) {} |
| |
| OutgoingHTTPResponse::~OutgoingHTTPResponse() {} |
| |
| void OutgoingHTTPResponse::AddResponse(const sp_string& _str) { |
| CHECK_GE(evbuffer_add_printf(response_->output_buffer, "%s", _str.c_str()), 0); |
| } |
| |
| void OutgoingHTTPResponse::AddHeader(const sp_string& _key, const sp_string& _value) { |
| CHECK_EQ(evhttp_add_header(response_->output_headers, _key.c_str(), _value.c_str()), 0); |
| } |
| |
| IncomingHTTPResponse::IncomingHTTPResponse(struct evhttp_request* _response) { |
| response_code_ = -1; |
| if (_response) { |
| response_code_ = _response->response_code; |
| char* bufdata = reinterpret_cast<char*>(EVBUFFER_DATA(_response->input_buffer)); |
| size_t buflen = EVBUFFER_LENGTH(_response->input_buffer); |
| body_ = std::string(bufdata, buflen); |
| struct evkeyval* header; |
| TAILQ_FOREACH(header, _response->input_headers, next) { |
| sp_string hdr = header->key; |
| for (size_t i = 0; i < hdr.size(); ++i) { |
| hdr[i] = tolower(hdr[i]); |
| } |
| headers_[hdr] = header->value; |
| } |
| } |
| } |
| |
| IncomingHTTPResponse::~IncomingHTTPResponse() {} |
| |
| sp_string IncomingHTTPResponse::header(const sp_string& _key) { |
| sp_string key; |
| for (size_t i = 0; i < _key.size(); ++i) { |
| key.push_back(tolower(_key[i])); |
| } |
| if (headers_.find(key) == headers_.end()) return EMPTY_STRING; |
| return headers_[key]; |
| } |