ssl client bit of work
This commit is contained in:
parent
cd930f6b7b
commit
bab67f70f9
|
|
@ -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>
|
||||
|
||||
|
||||
HttpClient::HttpClient() {};
|
||||
|
||||
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
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET
|
||||
|
||||
std::regex rgx{R"(^(?:((?:https?|s?ftp):)//)([^:/\s]+)(?::(\d*))?(?:/([^\s?#]+)?([?][^?#]*)?(#.*)?)?)"};
|
||||
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) {
|
||||
|
||||
// split url to parts
|
||||
parseURL(url);
|
||||
if (proto != "https")
|
||||
throw std::runtime_error("Unsupported protocol");
|
||||
|
||||
// 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");
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
22
ml.cpp
|
|
@ -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)};
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue