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 <arpa/inet.h>
#include <netdb.h> #include <netdb.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <stdio.h> #include <cstdio>
#include <sys/socket.h> #include <sys/socket.h>
#include <iostream> #include <iostream>
@ -16,13 +16,93 @@
#include <sstream> #include <sstream>
#include <string> #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) { // split url to parts
// https://stackoverflow.com/questions/25896916/parse-http-headers-in-c 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; std::smatch matches;
if (std::regex_search(url, matches, rgx)) { if (std::regex_search(url, matches, rgx)) {
@ -55,67 +135,14 @@ std::pair<int, std::string> HttpClient::doGetRequest(const std::string &url, con
} else { } else {
std::cerr << "Match not found" << std::endl; // TODO better message std::cerr << "Match not found" << std::endl; // TODO better message
} }
}
std::string headers_string = ""; std::string HttpClient::inetAddress(const std::string &hostname) {
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) {
hostent *record = gethostbyname(hostname.c_str()); hostent *record = gethostbyname(hostname.c_str());
if (record == NULL) { if (record == NULL) {
throw std::runtime_error(hostname + " network unavailable."); 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); std::string ip_address = inet_ntoa(*address);
return ip_address; return ip_address;
@ -145,16 +172,15 @@ int HttpClient::sslRecvPacket() {
return -1; return -1;
} }
return ssl_read_packet.length(); return (int) ssl_read_packet.length();
} }
int HttpClient::sslSendPacket(std::string buf) { int HttpClient::sslSendPacket(const std::string &buf) {
int len = SSL_write(ssl, buf.c_str(), strlen(buf.c_str())); int len = SSL_write(ssl, buf.c_str(), (int)strlen(buf.c_str()));
if (len < 0) { if (len < 0) {
int err = SSL_get_error(ssl, len); int err = SSL_get_error(ssl, len);
switch (err) { switch (err) {
case SSL_ERROR_WANT_WRITE: case SSL_ERROR_WANT_WRITE:
return 0;
case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_READ:
return 0; return 0;
case SSL_ERROR_ZERO_RETURN: case SSL_ERROR_ZERO_RETURN:
@ -164,8 +190,8 @@ int HttpClient::sslSendPacket(std::string buf) {
return -1; 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) { 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; return -1;
} }
// socket address // socket address
std::string server_ip = inetAddress(server_name); std::string server_ip = inetAddress(server_name);
struct sockaddr_in sa; struct sockaddr_in sa;
@ -201,7 +226,7 @@ int HttpClient::sslRequest(const std::string &server_name, const std::string &re
ssl = SSL_new(ctx); ssl = SSL_new(ctx);
if (!ssl) { if (!ssl) {
printf("sslRequest, error creating SSL.\n"); printf("sslRequest, error creating SSL.\n");
log_ssl(); logSSL();
return -1; return -1;
} }
sock = SSL_get_fd(ssl); 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); int err = SSL_connect(ssl);
if (err <= 0) { if (err <= 0) {
printf("sslRequest, error creating SSL connection. err=%x\n", err); printf("sslRequest, error creating SSL connection. err=%x\n", err);
log_ssl(); logSSL();
fflush(stdout); fflush(stdout);
return -1; return -1;
} }
// log cipher // log cipher
printf ("SSL connection using %s\n", SSL_get_cipher (ssl)); // printf ("SSL connection using %s\n", SSL_get_cipher (ssl));
ShowCerts(ssl); // showCerts(ssl);
// send request // send request
// std::out << request << std::endl; // printf ("SSL sending request %s\n", request.c_str());
int written_bytes = sslSendPacket(request); int written_bytes = sslSendPacket(request);
if (written_bytes != request.length()) {
printf("sslRequest, error sending request\n");
return -1;
}
// read response and return its length // read response and return its length
return sslRecvPacket(); return sslRecvPacket();
} }
void HttpClient::log_ssl() { void HttpClient::logSSL() {
int err; unsigned long err;
while ((err = ERR_get_error())) { while ((err = ERR_get_error())) {
char *str = ERR_error_string(err, 0); char *str = ERR_error_string(err, 0);
if (!str) if (!str)
@ -239,24 +270,21 @@ void HttpClient::log_ssl() {
} }
} }
void HttpClient::showCerts(SSL* ssl) {
X509 *cert;
void HttpClient::ShowCerts(SSL* ssl)
{ X509 *cert;
char *line; char *line;
cert = SSL_get_peer_certificate(ssl); /* get the server's certificate */ cert = SSL_get_peer_certificate(ssl); /* get the server's certificate */
if ( cert != NULL ) if ( cert != NULL ) {
{ printf("Server certificates:\n");
printf("Server certificates:\n"); line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0); printf("Subject: %s\n", line);
printf("Subject: %s\n", line); free(line); /* free the malloc'ed string */
free(line); /* free the malloc'ed string */ line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);
line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0); printf("Issuer: %s\n", line);
printf("Issuer: %s\n", line); free(line); /* free the malloc'ed string */
free(line); /* free the malloc'ed string */ X509_free(cert); /* free the malloc'ed certificate copy */
X509_free(cert); /* free the malloc'ed certificate copy */ } else {
} printf("No certificates.\n");
else }
printf("No certificates.\n");
} }

View File

@ -5,6 +5,7 @@
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
#include <map>
class HttpClient { class HttpClient {
@ -16,23 +17,30 @@ private:
std::string full_url, proto, server, port, uri, params, href; std::string full_url, proto, server, port, uri, params, href;
std::basic_string<char> ssl_read_packet; 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: public:
HttpClient(); HttpClient() = default;
std::pair<int, std::string> 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);
doGetRequest(const std::string &url, const std::unordered_map<std::string, std::string> &headers);
private: private:
std::string inetAddress(std::string hostname); static std::string inetAddress(const std::string &hostname);
int sslRecvPacket(); int sslRecvPacket();
int sslSendPacket(std::string buf); int sslSendPacket(const std::string &buf);
int sslRequest(const std::string &server_name, const std::string &request); int sslRequest(const std::string &server_name, const std::string &request);
void log_ssl(); static void logSSL();
void ShowCerts(SSL* ssl); 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 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))`| |`(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|| |`(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|| |`(system-cmd command_str)`|Execute system command||
|`(ls-dir dir)`|List a dir|List of directory entries| |`(ls-dir dir)`|List a dir|List of directory entries|
|`(is-file? filename)`|Returns true if passed filename is a file|| |`(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); eval_args(args, env);
// PERF optimize it for memory usage and performance // 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); throw MlError(MlValue("read_url", read_url), env, args.empty() ? TOO_FEW_ARGS : TOO_MANY_ARGS);
std::unordered_map<std::string, std::string> headers = {}; std::string url = args[0].as_string();
HttpClient client; 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()) { for (const auto &hdr_val_pair: args[1].as_list()) {
// TODO check its 2 string elements list // TODO check its 2 string elements list
const auto &pair = hdr_val_pair.as_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)}; return std::vector<MlValue>{MlValue(result.first), MlValue::string(result.second)};
} }