ssl client bit of work
This commit is contained in:
parent
cd930f6b7b
commit
bab67f70f9
|
|
@ -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");
|
|
||||||
}
|
}
|
||||||
|
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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
22
ml.cpp
|
|
@ -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)};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue