aboutsummaryrefslogtreecommitdiff
path: root/src/net/http_client.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/net/http_client.cc')
-rw-r--r--src/net/http_client.cc135
1 files changed, 117 insertions, 18 deletions
diff --git a/src/net/http_client.cc b/src/net/http_client.cc
index b31073c..b31976f 100644
--- a/src/net/http_client.cc
+++ b/src/net/http_client.cc
@@ -17,6 +17,7 @@
*/
#include "ricochet/net/http_client.hh"
+#include "ricochet/net/ssl_socket.hh"
#include "ricochet/net/sys/address.hh"
#include "ricochet/net/sys/socket.hh"
@@ -37,13 +38,17 @@ struct ParsedUrl
std::expected<ParsedUrl, std::string> parse_url( std::string_view url )
{
- const std::string_view http_prefix = "http://";
+ std::string_view prefix = "http://";
- if ( !url.starts_with( http_prefix ) ) {
- return std::unexpected( "Only 'http://' URLs are supported in MVP. (e.g., http://example.com)" );
+ if ( url.starts_with( "https://" ) ) {
+ prefix = "https://";
+ } else if ( url.starts_with( "http://" ) ) {
+ prefix = "http://";
+ } else {
+ return std::unexpected( "Only 'http://' or 'https://' URLs are supported." );
}
- url.remove_prefix( http_prefix.length() );
+ url.remove_prefix( prefix.length() );
auto slash_pos = url.find( '/' );
ParsedUrl result;
@@ -57,6 +62,77 @@ std::expected<ParsedUrl, std::string> parse_url( std::string_view url )
return result;
}
+
+std::string dechunk( std::string_view body ) // NOLINT(readability-convert-member-functions-to-static)
+{
+ std::string result;
+ std::size_t pos = 0;
+ while ( pos < body.size() ) {
+ // find hex length end with \r\n
+ auto line_end = body.find( "\r\n", pos );
+ if ( line_end == std::string_view::npos ) {
+ break;
+ }
+
+ // parse hex length
+ const std::string len_str( body.substr( pos, line_end - pos ) );
+ unsigned int chunk_size = 0;
+ try {
+ chunk_size = std::stoul( len_str, nullptr, 16 );
+ } catch ( ... ) {
+ break;
+ }
+
+ if ( chunk_size == 0 ) {
+ break; // end of stream
+ }
+
+ // extract data block (skip \r\n)
+ pos = line_end + 2;
+ if ( pos + chunk_size > body.size() ) {
+ break;
+ }
+ result.append( body.substr( pos, chunk_size ) );
+
+ // skip data block's \r\n
+ pos += chunk_size + 2;
+ }
+ return result;
+}
+
+std::string remove_html_comments( std::string html ) // NOLINT(readability-convert-member-functions-to-static)
+{
+ std::size_t start_pos = 0;
+ while ( ( start_pos = html.find( "<!--", start_pos ) ) != std::string::npos ) {
+ auto end_pos = html.find( "-->", start_pos );
+ if ( end_pos != std::string::npos ) {
+ html.erase( start_pos, end_pos - start_pos + 3 );
+ } else {
+ html.erase( start_pos );
+ break;
+ }
+ }
+ return html;
+}
+
+std::string remove_tag_blocks( std::string html, const std::string& tag_name ) // NOLINT
+{
+ const std::string open_tag = "<" + tag_name;
+ const std::string close_tag = "</" + tag_name + ">";
+ std::size_t start_pos = 0;
+
+ while ( ( start_pos = html.find( open_tag, start_pos ) ) != std::string::npos ) {
+ const std::size_t end_pos = html.find( close_tag, start_pos );
+ if ( end_pos != std::string::npos ) {
+ html.erase( start_pos, end_pos - start_pos + close_tag.length() );
+ } else {
+ html.erase( start_pos );
+ break;
+ }
+ }
+ return html;
+}
+
} // namespace
std::expected<HttpResponse, std::string>
@@ -69,30 +145,46 @@ HttpClient::fetch( // NOLINT(readability-convert-member-functions-to-static)
}
auto [host, path] = parsed_url.value();
+ const bool is_https = url.starts_with( "https://" );
+ const std::string service = is_https ? "https" : "http";
try {
- const Address server_addr( host, "http" );
-
- TCPSocket socket;
- socket.connect( server_addr );
+ const Address server_addr( host, service );
const std::string request = std::format( "GET {} HTTP/1.1\r\n"
"Host: {}\r\n"
- "User-Agent: Ricochet/0.1.0\r\n"
+ "User-Agent: Mozilla/5.0 (Ricochet/0.1.0)\r\n"
+ "Accept: text/html\r\n"
+ "Accept-Encoding: identity\r\n"
"Connection: close\r\n\r\n",
path,
host );
- size_t bytes_written = 0;
- while ( bytes_written < request.size() ) {
- bytes_written += socket.write( request.substr( bytes_written ) );
- }
-
std::string raw_response;
- while ( !socket.eof() ) {
- std::string buffer;
- socket.read( buffer );
- raw_response += buffer;
+ if ( is_https ) {
+ SSLSocket ssl_socket;
+ ssl_socket.connect( server_addr, host );
+ size_t bytes_written = 0;
+ while ( bytes_written < request.size() ) {
+ bytes_written += ssl_socket.write( request.substr( bytes_written ) );
+ }
+ while ( !ssl_socket.eof() ) {
+ std::string buffer;
+ ssl_socket.read( buffer );
+ raw_response += buffer;
+ }
+ } else {
+ TCPSocket tcp_socket;
+ tcp_socket.connect( server_addr );
+ size_t bytes_written = 0;
+ while ( bytes_written < request.size() ) {
+ bytes_written += tcp_socket.write( request.substr( bytes_written ) );
+ }
+ while ( !tcp_socket.eof() ) {
+ std::string buffer;
+ tcp_socket.read( buffer );
+ raw_response += buffer;
+ }
}
HttpResponse response;
@@ -111,6 +203,13 @@ HttpClient::fetch( // NOLINT(readability-convert-member-functions-to-static)
response.body = raw_response;
}
+ if ( raw_response.contains( "Transfer-Encoding: chunked" ) ) {
+ response.body = dechunk( response.body );
+ }
+
+ response.body = remove_html_comments( response.body );
+ response.body = remove_tag_blocks( response.body, "script" );
+ response.body = remove_tag_blocks( response.body, "style" );
return response;
} catch ( const std::exception& e ) {
return std::unexpected( std::string( "Network Error: " ) + e.what() );