Added random_real
[ail.git] / source / http.cpp
blob6c82363c56660f3f55c39121a410622335466c58
1 #include <iostream>
2 #include <istream>
3 #include <ostream>
5 #include <ail/http.hpp>
6 #include <ail/string.hpp>
7 #include <ail/zlib.hpp>
8 #include <ail/file.hpp>
9 #include <ail/net.hpp>
11 namespace ail
13 bool process_chunked_data(std::string const & data, std::string & content, std::size_t offset)
15 std::string const target = "\r\n";
16 while(offset < data.size())
18 std::size_t newline_offset = data.find(target, offset);
19 if(newline_offset == std::string::npos)
20 return false;
21 std::string number_string = data.substr(offset, newline_offset - offset);
22 long chunk_size;
23 if(!ail::string_to_number<long>(number_string, chunk_size, std::ios_base::hex))
24 return false;
25 std::size_t chunk_offset = newline_offset + target.length();
26 std::size_t end_of_chunk = chunk_offset + chunk_size;
27 std::string chunk = data.substr(chunk_offset, end_of_chunk - chunk_offset);
28 content += chunk;
29 offset = end_of_chunk + target.length();
31 return true;
34 http_client::http_client(boost::asio::io_service & io_service):
35 io_service(io_service),
36 resolver(io_service),
37 socket(io_service),
38 use_compression(true),
39 has_error_handler(false),
40 has_download_handler(false),
41 has_download_finished_handler(false),
42 user_agent(http_user_agent_firefox),
43 method("GET")
47 void http_client::use_post()
49 method = "POST";
52 void http_client::set_compression(bool new_use_compression)
54 use_compression = new_use_compression;
57 void http_client::add_post_data(std::string const & field, std::string const & value)
59 form_data[field] = value;
62 void http_client::start_download(std::string const & new_url)
64 std::string const
65 http_string = "http",
66 https_string = "https",
67 protocol_separator = "://";
69 std::size_t host_offset;
71 url = new_url;
73 std::size_t separator_offset = url.find(protocol_separator);
74 if(separator_offset == std::string::npos)
76 use_ssl = false;
77 host_offset = 0;
79 else
81 host_offset = separator_offset + protocol_separator.length();
82 std::string protocol = url.substr(0, separator_offset);
83 if(protocol == http_string)
84 use_ssl = false;
85 else if(protocol == https_string)
86 use_ssl = true;
87 else
88 throw ail::exception("Invalid protocol specified for HTTP client");
91 std::size_t path_offset = url.find('/', host_offset);
92 if(path_offset == std::string::npos)
94 host = url.substr(host_offset);
95 path = "/";
97 else
99 host = url.substr(host_offset, path_offset - host_offset);
100 path = url.substr(path_offset);
103 boost::asio::ip::tcp::resolver::query query(host, "http");
104 resolver.async_resolve(query, boost::bind(&http_client::dns_event, this, boost::asio::placeholders::error, boost::asio::placeholders::iterator));
105 io_service.stop();
108 bool http_client::start_download(std::string const & new_url, std::string const & file_name)
110 if(!file_output.open_create(file_name))
111 return false;
112 start_download(new_url);
113 return true;
116 void http_client::dns_event(boost::system::error_code const & error, boost::asio::ip::tcp::resolver::iterator endpoint_iterator)
118 //std::cout << "DNS event" << std::endl;
119 if(!error)
121 boost::asio::ip::tcp::endpoint endpoint = *endpoint_iterator;
122 endpoint_iterator++;
123 socket.async_connect(endpoint, boost::bind(&http_client::connect_event, this, boost::asio::placeholders::error, endpoint_iterator));
124 io_service.stop();
126 else
127 error_occured(http_error_dns);
130 void http_client::connect_event(boost::system::error_code const & error, boost::asio::ip::tcp::resolver::iterator endpoint_iterator)
132 if(!error)
134 std::ostream request_stream(&request);
135 request_stream << method << " " << path << " HTTP/1.1\r\n";
136 switch(user_agent)
138 case http_user_agent_firefox:
139 request_stream << "User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.0.8) Gecko/2009032609 Firefox/3.0.8 (.NET CLR 3.5.30729)\r\n";
140 break;
142 request_stream << "Host: " << host << "\r\n";
143 //request_stream << "Accept: */*\r\n";
144 request_stream << "Accept: text/html, application/xml;q=0.9, application/xhtml+xml, image/png, image/jpeg, image/gif, image/x-xbitmap, */*;q=0.1\r\n";
145 request_stream << "Accept-Language: en-us,en;q=0.5\r\n";
146 request_stream << "Accept-Charset: iso-8859-1, utf-8, utf-16, *;q=0.1\r\n";
147 request_stream << "Accept-Encoding: deflate, gzip, x-gzip, identity, *;q=0\r\n";
148 //request_stream << "Connection: close\r\n";
149 //request_stream << "Connection: Keep-Alive, TE\r\n";
150 request_stream << "Connection: close, TE\r\n";
151 request_stream << "TE: ";
152 if(use_compression)
153 request_stream << "deflate, gzip, ";
154 request_stream << "chunked, identity, trailers\r\n";
155 if(!referrer.empty())
156 request_stream << "Referer: " << referrer << "\r\n";
157 if(!cookies.empty())
159 request_stream << "Cookie: ";
160 bool first = true;
161 for(cookie_map::iterator i = cookies.begin(), end = cookies.end(); i != end; i++)
163 if(first)
164 first = false;
165 else
166 request_stream << "; ";
167 request_stream << i->first << "=" << i->second;
169 request_stream << "\r\n";
172 std::string post_data;
174 if(!form_data.empty())
176 std::string const boundary_tag = "------------PDq3XENV0Hx17uo1Qpe5qN";
177 for(form_map::iterator i = form_data.begin(), end = form_data.end(); i != end; i++)
178 post_data += boundary_tag + "\r\nContent-Disposition: form-data; name=\"" + i->first + "\"\r\n\r\n" + i->second + "\r\n";
179 post_data += boundary_tag + "--\r\n";
181 //std::cout << ail::consolify(post_data) << std::endl;
183 request_stream << "Content-Length: " << post_data.size() << "\r\n";
184 request_stream << "Content-Type: multipart/form-data; boundary=" << boundary_tag << "\r\n";
185 request_stream << "\r\n";
186 request_stream << post_data;
188 else
189 request_stream << "\r\n";
191 boost::asio::async_write(socket, request, boost::bind(&http_client::write_event, this, boost::asio::placeholders::error));
192 io_service.stop();
194 else if(endpoint_iterator != boost::asio::ip::tcp::resolver::iterator())
196 socket.close();
197 boost::asio::ip::tcp::endpoint endpoint = *endpoint_iterator;
198 endpoint_iterator++;
199 socket.async_connect(endpoint, boost::bind(&http_client::connect_event, this, boost::asio::placeholders::error, endpoint_iterator));
200 io_service.stop();
202 else
204 std::cout << "Connect: " << error.message() << std::endl;
205 error_occured(http_error_connect);
209 void http_client::write_event(boost::system::error_code const & error)
211 if(!error)
212 read_header_data();
213 else
214 error_occured(http_error_write);
217 void http_client::read_header_data()
219 boost::asio::async_read(socket, response, boost::asio::transfer_at_least(1), boost::bind(&http_client::read_header_event, this, boost::asio::placeholders::error));
220 io_service.stop();
223 void http_client::read_partial_data()
225 boost::asio::async_read(socket, response, boost::asio::transfer_at_least(1), boost::bind(&http_client::partial_read_event, this, boost::asio::placeholders::error));
226 io_service.stop();
229 void http_client::read_full_data()
231 boost::asio::async_read(socket, response, boost::asio::transfer_all(), boost::bind(&http_client::full_read_event, this, boost::asio::placeholders::error));
232 io_service.stop();
235 void http_client::read_header_event(boost::system::error_code const & error)
237 long const
238 ok_code = 200,
239 moved_code = 301,
240 found_code = 302;
242 bool download_finished = error == boost::asio::error::eof;
243 if(error && !download_finished)
245 error_occured(http_error_read);
246 return;
249 extract_data(response, buffer);
251 std::string const end_of_header_string = "\r\n\r\n";
253 std::size_t end_of_header = buffer.find(end_of_header_string);
254 if(end_of_header == std::string::npos)
256 if(download_finished)
257 error_occured(http_error_end_of_header);
258 else
259 read_header_data();
260 return;
262 std::string header = buffer.substr(0, end_of_header);
263 buffer.erase(0, end_of_header + end_of_header_string.size());
265 string_vector tokens = tokenise(header, "\r\n");
266 if(tokens.empty())
268 error_occured(http_error_header_lacks_lines);
269 return;
272 //std::cout << "Header: " << header << std::endl;
274 std::string const & status_line = tokens[0];
276 //std::cout << status_line << std::endl;
278 std::string http_code_string;
279 if(!extract_string(status_line, " ", " ", http_code_string))
281 error_occured(http_error_invalid_status_line);
282 return;
285 //std::cout << "Code: " << http_code_string << std::endl;
287 long http_code;
288 if(!string_to_number<long>(http_code_string, http_code))
290 error_occured(http_error_invalid_code_string);
291 return;
294 if(http_code != ok_code && http_code != moved_code && http_code != found_code)
296 error_occured(http_error_status_code_error);
297 std::cout << buffer << std::endl;
298 return;
301 encoding = http_encoding_none;
303 chunked = false;
305 for(std::size_t i = 1, end = tokens.size(); i < end; i++)
307 string_vector line_tokens = tokenise(tokens[i], ": ");
308 if(line_tokens.size() != 2)
310 error_occured(http_error_header_token_error);
311 return;
313 std::string const & field = line_tokens[0];
314 std::string const & value = line_tokens[1];
315 if(http_code == ok_code && field == "Content-Encoding")
317 if(value == "gzip")
318 encoding = http_encoding_gzip;
319 else if(value == "deflate")
320 encoding = http_encoding_deflate;
322 else if(http_code == ok_code && field == "Transfer-Encoding")
324 if(value == "chunked")
325 chunked = true;
327 else if((http_code == moved_code || http_code == found_code) && field == "Location")
329 socket.close();
331 std::string new_url;
333 if(!value.empty() && value[0] == '/')
334 new_url = (use_ssl ? "http" : "https") + std::string("://") + host + value;
335 else
336 new_url = value;
337 start_download(new_url);
338 return;
342 if(download_finished)
343 process_data();
344 else
346 if(encoding == http_encoding_none && has_download_handler)
348 process_partial_data();
349 read_partial_data();
351 else
352 read_full_data();
356 void http_client::full_read_event(boost::system::error_code const & error)
358 if(error != boost::asio::error::eof)
360 error_occured(http_error_read);
361 return;
364 extract_data(response, buffer);
365 process_data();
368 void http_client::process_partial_data()
370 if(file_output.open())
371 file_output.write(buffer);
373 if(has_download_handler)
374 download_handler(*this, buffer);
376 buffer.clear();
379 void http_client::partial_read_event(boost::system::error_code const & error)
381 bool download_finished = error == boost::asio::error::eof;
382 if(error && !download_finished)
384 error_occured(http_error_read);
385 return;
388 extract_data(response, buffer);
390 process_partial_data();
392 if(download_finished)
394 if(file_output.open())
395 file_output.close();
397 if(has_download_finished_handler)
398 download_finished_handler(*this, buffer);
400 else
401 read_partial_data();
404 void http_client::process_data()
406 std::string
407 content,
408 dechunked_data,
410 * input_pointer = &buffer;
412 if(chunked)
414 if(!process_chunked_data(buffer, dechunked_data, 0))
416 error_occured(http_error_chunk);
417 return;
419 input_pointer = &dechunked_data;
422 std::string & input = *input_pointer;
424 switch(encoding)
426 case http_encoding_none:
427 content = input;
428 break;
430 case http_encoding_gzip:
431 case http_encoding_deflate:
434 decompress_gzip(input, content);
436 catch(exception &)
438 error_occured(http_error_compression);
439 return;
441 break;
444 if(has_download_handler)
445 download_handler(*this, content);
447 if(has_download_finished_handler)
448 download_finished_handler(*this, content);
450 buffer.clear();
453 void http_client::error_occured(http_error_type the_error_code)
455 //std::cout << "http_client Error: " << the_error_code << std::endl;
456 if(has_error_handler)
457 error_handler(*this, the_error_code);
460 void http_client::set_error_handler(error_handler_type new_error_handler)
462 error_handler = new_error_handler;
463 has_error_handler = true;
466 void http_client::set_download_handler(download_handler_type new_download_handler)
468 download_handler = new_download_handler;
469 has_download_handler = true;
472 void http_client::set_download_finished_handler(download_finished_handler_type new_download_finished_handler)
474 download_finished_handler = new_download_finished_handler;
475 has_download_finished_handler = true;
478 void http_client::set_referrer(std::string const & new_referrer)
480 referrer = new_referrer;
483 std::string http_client::get_referrer() const
485 return referrer;
488 std::string http_client::get_url() const
490 return url;