Botan
1.11.15
|
00001 /* 00002 * Sketchy HTTP client 00003 * (C) 2013 Jack Lloyd 00004 * 00005 * Botan is released under the Simplified BSD License (see license.txt) 00006 */ 00007 00008 #include <botan/http_util.h> 00009 #include <botan/parsing.h> 00010 #include <botan/hex.h> 00011 #include <botan/internal/stl_util.h> 00012 #include <sstream> 00013 00014 #if defined(BOTAN_HAS_BOOST_ASIO) 00015 #include <boost/asio.hpp> 00016 #endif 00017 00018 namespace Botan { 00019 00020 namespace HTTP { 00021 00022 #if defined(BOTAN_HAS_BOOST_ASIO) 00023 std::string http_transact_asio(const std::string& hostname, 00024 const std::string& message) 00025 { 00026 using namespace boost::asio::ip; 00027 00028 boost::asio::ip::tcp::iostream tcp; 00029 00030 tcp.connect(hostname, "http"); 00031 00032 if(!tcp) 00033 throw std::runtime_error("HTTP connection to " + hostname + " failed"); 00034 00035 tcp << message; 00036 tcp.flush(); 00037 00038 std::ostringstream oss; 00039 oss << tcp.rdbuf(); 00040 00041 return oss.str(); 00042 } 00043 #endif 00044 00045 std::string http_transact_fail(const std::string& hostname, 00046 const std::string&) 00047 { 00048 throw std::runtime_error("Cannot connect to " + hostname + 00049 ": network code disabled in build"); 00050 } 00051 00052 std::string url_encode(const std::string& in) 00053 { 00054 std::ostringstream out; 00055 00056 for(auto c : in) 00057 { 00058 if(c >= 'A' && c <= 'Z') 00059 out << c; 00060 else if(c >= 'a' && c <= 'z') 00061 out << c; 00062 else if(c >= '0' && c <= '9') 00063 out << c; 00064 else if(c == '-' || c == '_' || c == '.' || c == '~') 00065 out << c; 00066 else 00067 out << '%' << hex_encode(reinterpret_cast<byte*>(&c), 1); 00068 } 00069 00070 return out.str(); 00071 } 00072 00073 std::ostream& operator<<(std::ostream& o, const Response& resp) 00074 { 00075 o << "HTTP " << resp.status_code() << " " << resp.status_message() << "\n"; 00076 for(auto h : resp.headers()) 00077 o << "Header '" << h.first << "' = '" << h.second << "'\n"; 00078 o << "Body " << std::to_string(resp.body().size()) << " bytes:\n"; 00079 o.write(reinterpret_cast<const char*>(&resp.body()[0]), resp.body().size()); 00080 return o; 00081 } 00082 00083 Response http_sync(http_exch_fn http_transact, 00084 const std::string& verb, 00085 const std::string& url, 00086 const std::string& content_type, 00087 const std::vector<byte>& body, 00088 size_t allowable_redirects) 00089 { 00090 const auto protocol_host_sep = url.find("://"); 00091 if(protocol_host_sep == std::string::npos) 00092 throw std::runtime_error("Invalid URL " + url); 00093 const std::string protocol = url.substr(0, protocol_host_sep); 00094 00095 const auto host_loc_sep = url.find('/', protocol_host_sep + 3); 00096 00097 std::string hostname, loc; 00098 00099 if(host_loc_sep == std::string::npos) 00100 { 00101 hostname = url.substr(protocol_host_sep + 3, std::string::npos); 00102 loc = "/"; 00103 } 00104 else 00105 { 00106 hostname = url.substr(protocol_host_sep + 3, host_loc_sep-protocol_host_sep-3); 00107 loc = url.substr(host_loc_sep, std::string::npos); 00108 } 00109 00110 std::ostringstream outbuf; 00111 00112 outbuf << verb << " " << loc << " HTTP/1.0\r\n"; 00113 outbuf << "Host: " << hostname << "\r\n"; 00114 00115 if(verb == "GET") 00116 { 00117 outbuf << "Accept: */*\r\n"; 00118 outbuf << "Cache-Control: no-cache\r\n"; 00119 } 00120 else if(verb == "POST") 00121 outbuf << "Content-Length: " << body.size() << "\r\n"; 00122 00123 if(content_type != "") 00124 outbuf << "Content-Type: " << content_type << "\r\n"; 00125 outbuf << "Connection: close\r\n\r\n"; 00126 outbuf.write(reinterpret_cast<const char*>(&body[0]), body.size()); 00127 00128 std::istringstream io(http_transact(hostname, outbuf.str())); 00129 00130 std::string line1; 00131 std::getline(io, line1); 00132 if(!io || line1.empty()) 00133 throw std::runtime_error("No response"); 00134 00135 std::stringstream response_stream(line1); 00136 std::string http_version; 00137 unsigned int status_code; 00138 std::string status_message; 00139 00140 response_stream >> http_version >> status_code; 00141 00142 std::getline(response_stream, status_message); 00143 00144 if(!response_stream || http_version.substr(0,5) != "HTTP/") 00145 throw std::runtime_error("Not an HTTP response"); 00146 00147 std::map<std::string, std::string> headers; 00148 std::string header_line; 00149 while (std::getline(io, header_line) && header_line != "\r") 00150 { 00151 auto sep = header_line.find(": "); 00152 if(sep == std::string::npos || sep > header_line.size() - 2) 00153 throw std::runtime_error("Invalid HTTP header " + header_line); 00154 const std::string key = header_line.substr(0, sep); 00155 00156 if(sep + 2 < header_line.size() - 1) 00157 { 00158 const std::string val = header_line.substr(sep + 2, (header_line.size() - 1) - (sep + 2)); 00159 headers[key] = val; 00160 } 00161 } 00162 00163 if(status_code == 301 && headers.count("Location")) 00164 { 00165 if(allowable_redirects == 0) 00166 throw std::runtime_error("HTTP redirection count exceeded"); 00167 return GET_sync(headers["Location"], allowable_redirects - 1); 00168 } 00169 00170 std::vector<byte> resp_body; 00171 std::vector<byte> buf(4096); 00172 while(io.good()) 00173 { 00174 io.read(reinterpret_cast<char*>(&buf[0]), buf.size()); 00175 resp_body.insert(resp_body.end(), &buf[0], &buf[io.gcount()]); 00176 } 00177 00178 const std::string header_size = search_map(headers, std::string("Content-Length")); 00179 00180 if(header_size != "") 00181 { 00182 if(resp_body.size() != to_u32bit(header_size)) 00183 throw std::runtime_error("Content-Length disagreement, header says " + 00184 header_size + " got " + std::to_string(resp_body.size())); 00185 } 00186 00187 return Response(status_code, status_message, resp_body, headers); 00188 } 00189 00190 Response http_sync(const std::string& verb, 00191 const std::string& url, 00192 const std::string& content_type, 00193 const std::vector<byte>& body, 00194 size_t allowable_redirects) 00195 { 00196 return http_sync( 00197 #if defined(BOTAN_HAS_BOOST_ASIO) 00198 http_transact_asio, 00199 #else 00200 http_transact_fail, 00201 #endif 00202 verb, 00203 url, 00204 content_type, 00205 body, 00206 allowable_redirects); 00207 } 00208 00209 Response GET_sync(const std::string& url, size_t allowable_redirects) 00210 { 00211 return http_sync("GET", url, "", std::vector<byte>(), allowable_redirects); 00212 } 00213 00214 Response POST_sync(const std::string& url, 00215 const std::string& content_type, 00216 const std::vector<byte>& body, 00217 size_t allowable_redirects) 00218 { 00219 return http_sync("POST", url, content_type, body, allowable_redirects); 00220 } 00221 00222 std::future<Response> GET_async(const std::string& url, size_t allowable_redirects) 00223 { 00224 return std::async(std::launch::async, GET_sync, url, allowable_redirects); 00225 } 00226 00227 } 00228 00229 }