ssl client bit of work

This commit is contained in:
VaclavT 2022-01-14 01:00:03 +01:00
parent cd930f6b7b
commit bab67f70f9
4 changed files with 154 additions and 106 deletions

View File

@ -8,7 +8,7 @@
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdio.h>
#include <cstdio>
#include <sys/socket.h>
#include <iostream>
@ -16,13 +16,93 @@
#include <sstream>
#include <string>
// https://stackoverflow.com/questions/25896916/parse-http-headers-in-c
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET
HttpClient::HttpClient() {};
std::pair<int, std::string> HttpClient::doRequest(const std::string &method, const std::string &url, const std::map<std::string, std::string> &headers, const std::string &request_body) {
std::pair<int, std::string> HttpClient::doGetRequest(const std::string &url, const std::unordered_map<std::string, std::string> &headers) {
// https://stackoverflow.com/questions/25896916/parse-http-headers-in-c
// split url to parts
parseURL(url);
if (proto != "https")
throw std::runtime_error("Unsupported protocol");
std::regex rgx{R"(^(?:((?:https?|s?ftp):)//)([^:/\s]+)(?::(\d*))?(?:/([^\s?#]+)?([?][^?#]*)?(#.*)?)?)"};
// create headers
const std::string headers_string = createRequestHeaders(headers);
// create request
std::string request = method + " " + full_url + " HTTP/1.0\r\nHost: " + server + headers_string + "\r\n\r\n";
if (method == "POST")
request.append(request_body);
// read response
int bytes_read = sslRequest(server, request); // TODO memory leaks ???
if (bytes_read <= 0) {
std::cerr << "no data read" << std::endl;
return std::make_pair(403, "");
}
// get headers
std::string::size_type position = ssl_read_packet.find("\r\n\r\n");
if (position == std::string::npos) {
std::cerr << "end of headers not found" << std::endl;
// TODO invalid packet, throw exception
}
const std::string hdr = ssl_read_packet.substr(0, position);
auto status_pos = hdr.find("\r\n");
const std::string status_str = hdr.substr(0, status_pos);
const std::string response_headers = hdr.substr(status_pos + 2, hdr.length() - 2 - status_pos);
// parse status code
const int resp_status = responseStatusCode(status_str);
// parse headers
responseHeaders(response_headers);
// get body
const std::string body = ssl_read_packet.substr(position + 4, ssl_read_packet.length() - 4 - position);
return std::make_pair(resp_status, body);
}
void HttpClient::responseHeaders(const std::string &hdr) {
std::istringstream resp(hdr);
std::string header;
std::string::size_type index;
while (std::getline(resp, header) && header != "\r") {
index = header.find(": ", 0);
if (index != std::string::npos)
headers_map.insert(std::make_pair(header.substr(0, index), header.substr(index + 1)));
}
}
int HttpClient::responseStatusCode(const std::string &status_str) const {
int resp_status = 200; // default is OK
std::regex status_rgx{"^HTTP/\\d\\.\\d (\\d{3}) .+$"};
std::smatch status_matches;
if (std::regex_search(status_str, status_matches, status_rgx)) {
if (status_matches.size() > 1)
resp_status = std::stoi(status_matches[1].str());
}
return resp_status;
}
std::string HttpClient::createRequestHeaders(const std::map<std::string, std::string> &headers) {
std::string headers_string;
for (const auto & header : headers) {
headers_string.append("\r\n" + header.first + ": " + header.second);
// std::cerr << "KEY: `" << it->first << "`, VALUE: `" << it->second << '`' << std::endl;
}
return headers_string;
}
void HttpClient::parseURL(const std::string &url) {
std::regex rgx{R"(^(?:((?:https?|s?ftp))://)([^:/\s]+)(?::(\d*))?(?:/([^\s?#]+)?([?][^?#]*)?(#.*)?)?)"};
std::smatch matches;
if (std::regex_search(url, matches, rgx)) {
@ -55,67 +135,14 @@ std::pair<int, std::string> HttpClient::doGetRequest(const std::string &url, con
} else {
std::cerr << "Match not found" << std::endl; // TODO better message
}
}
std::string headers_string = "";
for (auto it = headers.begin(); it != headers.end(); ++it) {
headers_string.append("\r\n" + it->first + ": " + it->second);
// std::cerr << "KEY: `" << it->first << "`, VALUE: `" << it->second << '`' << std::endl;
}
std::string request = "GET " + full_url + " HTTP/1.0\r\nHost: " + server + headers_string + "\r\n\r\n";
// TODO memory leaks ???
int bytes_read = sslRequest(server, request);
if (bytes_read <= 0) {
std::cerr << "no data read" << std::endl;
return std::make_pair(403, "");
}
std::string::size_type position = ssl_read_packet.find("\r\n\r\n");
if (position == std::string::npos) {
std::cerr << "substring not found" << std::endl; // TODO invalid packet
}
std::string hdr = ssl_read_packet.substr(0, position);
auto status_pos = hdr.find("\r\n");
std::string status_str = hdr.substr(0, status_pos);
hdr = hdr.substr(status_pos + 2, hdr.length() - 2 - status_pos);
// TODO parse status code
std::regex status_rgx{"^HTTP/\\d\\.\\d (\\d{3}) .+$"};
std::smatch status_matches;
if (std::regex_search(status_str, status_matches, status_rgx)) {
if (status_matches.size() > 1) {
auto sta = status_matches[1].str(); // string "200"
// std::cout << "status: " << sta << std::endl;
}
}
std::string body = ssl_read_packet.substr(position + 4, ssl_read_packet.length() - 4 - position);
std::istringstream resp(hdr);
std::string header;
std::string::size_type index;
while (std::getline(resp, header) && header != "\r") {
index = header.find(": ", 0);
if (index != std::string::npos) {
headers_map.insert(std::make_pair(header.substr(0, index), header.substr(index + 1)));
}
}
// TODO if error return error desc in string
return std::make_pair(200, body);
};
std::string HttpClient::inetAddress(std::string hostname) {
std::string HttpClient::inetAddress(const std::string &hostname) {
hostent *record = gethostbyname(hostname.c_str());
if (record == NULL) {
throw std::runtime_error(hostname + " network unavailable.");
}
in_addr *address = (in_addr *) record->h_addr;
auto *address = (in_addr *) record->h_addr;
std::string ip_address = inet_ntoa(*address);
return ip_address;
@ -145,16 +172,15 @@ int HttpClient::sslRecvPacket() {
return -1;
}
return ssl_read_packet.length();
return (int) ssl_read_packet.length();
}
int HttpClient::sslSendPacket(std::string buf) {
int len = SSL_write(ssl, buf.c_str(), strlen(buf.c_str()));
int HttpClient::sslSendPacket(const std::string &buf) {
int len = SSL_write(ssl, buf.c_str(), (int)strlen(buf.c_str()));
if (len < 0) {
int err = SSL_get_error(ssl, len);
switch (err) {
case SSL_ERROR_WANT_WRITE:
return 0;
case SSL_ERROR_WANT_READ:
return 0;
case SSL_ERROR_ZERO_RETURN:
@ -164,8 +190,8 @@ int HttpClient::sslSendPacket(std::string buf) {
return -1;
}
}
int errr = SSL_get_error(ssl, len);
return buf.length();
return len;
}
int HttpClient::sslRequest(const std::string &server_name, const std::string &request) {
@ -177,7 +203,6 @@ int HttpClient::sslRequest(const std::string &server_name, const std::string &re
return -1;
}
// socket address
std::string server_ip = inetAddress(server_name);
struct sockaddr_in sa;
@ -201,7 +226,7 @@ int HttpClient::sslRequest(const std::string &server_name, const std::string &re
ssl = SSL_new(ctx);
if (!ssl) {
printf("sslRequest, error creating SSL.\n");
log_ssl();
logSSL();
return -1;
}
sock = SSL_get_fd(ssl);
@ -212,25 +237,31 @@ int HttpClient::sslRequest(const std::string &server_name, const std::string &re
int err = SSL_connect(ssl);
if (err <= 0) {
printf("sslRequest, error creating SSL connection. err=%x\n", err);
log_ssl();
logSSL();
fflush(stdout);
return -1;
}
// log cipher
printf ("SSL connection using %s\n", SSL_get_cipher (ssl));
ShowCerts(ssl);
// printf ("SSL connection using %s\n", SSL_get_cipher (ssl));
// showCerts(ssl);
// send request
// std::out << request << std::endl;
// printf ("SSL sending request %s\n", request.c_str());
int written_bytes = sslSendPacket(request);
if (written_bytes != request.length()) {
printf("sslRequest, error sending request\n");
return -1;
}
// read response and return its length
return sslRecvPacket();
}
void HttpClient::log_ssl() {
int err;
void HttpClient::logSSL() {
unsigned long err;
while ((err = ERR_get_error())) {
char *str = ERR_error_string(err, 0);
if (!str)
@ -239,15 +270,12 @@ void HttpClient::log_ssl() {
}
}
void HttpClient::ShowCerts(SSL* ssl)
{ X509 *cert;
void HttpClient::showCerts(SSL* ssl) {
X509 *cert;
char *line;
cert = SSL_get_peer_certificate(ssl); /* get the server's certificate */
if ( cert != NULL )
{
if ( cert != NULL ) {
printf("Server certificates:\n");
line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
printf("Subject: %s\n", line);
@ -256,7 +284,7 @@ void HttpClient::ShowCerts(SSL* ssl)
printf("Issuer: %s\n", line);
free(line); /* free the malloc'ed string */
X509_free(cert); /* free the malloc'ed certificate copy */
}
else
} else {
printf("No certificates.\n");
}
}

View File

@ -5,6 +5,7 @@
#include <string>
#include <unordered_map>
#include <map>
class HttpClient {
@ -16,23 +17,30 @@ private:
std::string full_url, proto, server, port, uri, params, href;
std::basic_string<char> ssl_read_packet;
std::unordered_map<std::string, std::string> headers_map;
std::map<std::string, std::string> headers_map;
public:
HttpClient();
HttpClient() = default;
std::pair<int, std::string>
doGetRequest(const std::string &url, const std::unordered_map<std::string, std::string> &headers);
std::pair<int, std::string> doRequest(const std::string &method, const std::string &url, const std::map<std::string, std::string> &headers, const std::string &request_body);
private:
std::string inetAddress(std::string hostname);
static std::string inetAddress(const std::string &hostname);
int sslRecvPacket();
int sslSendPacket(std::string buf);
int sslSendPacket(const std::string &buf);
int sslRequest(const std::string &server_name, const std::string &request);
void log_ssl();
void ShowCerts(SSL* ssl);
static void logSSL();
static void showCerts(SSL* ssl);
void parseURL(const std::string &url);
[[nodiscard]] static std::string createRequestHeaders(const std::map<std::string, std::string> &headers) ;
int responseStatusCode(const std::string &status_str) const;
void responseHeaders(const std::string &hdr);
};

View File

@ -79,7 +79,7 @@
|`(read-file filename)`|Get the contents of a file||
|`(read-file-lines filename lambda)`|Reads file and for each line call lambda with passing the line as a parameter|`(read-file-lines "/tmp/f.txt" (lambda (ln) (print ln))`|
|`(write-file filename content-str)`|Write a string to a file||
|`(read-url url [headers])`|Reads URL|Returns list (status-code content)|
|`(read-url url [headers] [body] [method])`|Reads URL|Returns list (status-code content)|
|`(system-cmd command_str)`|Execute system command||
|`(ls-dir dir)`|List a dir|List of directory entries|
|`(is-file? filename)`|Returns true if passed filename is a file||

22
ml.cpp
View File

@ -1202,13 +1202,16 @@ MlValue read_url(std::vector<MlValue> args, MlEnvironment &env) {
eval_args(args, env);
// PERF optimize it for memory usage and performance
if (args.empty() || args.size() > 2)
if (args.empty() || args.size() > 4)
throw MlError(MlValue("read_url", read_url), env, args.empty() ? TOO_FEW_ARGS : TOO_MANY_ARGS);
std::unordered_map<std::string, std::string> headers = {};
HttpClient client;
std::string url = args[0].as_string();
std::map<std::string, std::string> headers = {};
std::string method = "GET";
std::string body;
if (args.size() == 2) {
// headers
if (args.size() > 1) {
for (const auto &hdr_val_pair: args[1].as_list()) {
// TODO check its 2 string elements list
const auto &pair = hdr_val_pair.as_list();
@ -1216,7 +1219,16 @@ MlValue read_url(std::vector<MlValue> args, MlEnvironment &env) {
}
}
std::pair<long, std::string> result = client.doGetRequest(args[0].as_string(), headers);
// body
if (args.size() > 2)
body = args[2].as_string();
// method
if (args.size() > 3)
method = args[3].as_string();
std::pair<long, std::string> result = HttpClient{}.doRequest(method, url, headers, body);
return std::vector<MlValue>{MlValue(result.first), MlValue::string(result.second)};
}