I'm a relative newbie when it comes to the world of programming and have only recently taken up unix networking. I first created a program which downloaded files over the HTTP protocol to avail, and am now trying to make a file downloader that instead uses the Gemini protocol.
The issue, however, is that I can not apply my method of receiving uncorrupted data that I used with my HTTP file downloader as I used the content length header which Gemini lacks. I've experimented a little and came up with what I thought could be a solution, but does not work and I have yet to understand why.
Here's my code:
Code:
/*
* Simple program which downloads a file originating from the Gemini protocol
* using unix socket programming and the LibreSSL library.
*/
#include <netdb.h>
#include <libressl/openssl/ssl.h>
#include <cassert>
#include <iostream>
#include <fstream>
int
main()
{
/*
* Flag the to-be-created pointer to a linked-list pointer to allow
* both IPV4 and IPV6 IP addresses and use TCP.
*/
struct addrinfo flags = {0};
flags.ai_family = AF_UNSPEC;
flags.ai_socktype = SOCK_STREAM;
/*
* Return value for error handling.
*/
int rv = 0;
/*
* Stores given address, port, and above-defined flags into a pointer
* to a linked-list pointer which contains necessary connection
* information.
*/
struct addrinfo *conninfo;
rv = getaddrinfo("skyjake.fi", "1965", &flags, &conninfo);
assert(rv == 0);
/*
* Creates a communication endpoint and returns a file descriptor
* relating to that endpoint and assigns it to the "sockfd" int.
*/
int sockfd = socket(conninfo->ai_family, conninfo->ai_socktype,
conninfo->ai_protocol);
assert(sockfd != -1);
/*
* Connects to given address on the socket referred
* to by the "sockfd" int.
*/
rv = connect(sockfd, conninfo->ai_addr, conninfo->ai_addrlen);
assert(rv == 0);
SSL_library_init();
/*
* Creates an SSL structure containing TLS/SSL connection information
* including the SSL_CTX object which initializes the cipher list,
* session cache setting, callbacks, and the keys of
* certificates based off the connection method given, being the
* TLSv1.2 protocol.
*/
SSL *ssl = SSL_new(SSL_CTX_new(TLSv1_2_client_method()));
assert(ssl != nullptr);
/*
* Sets the above-defined SSL object's socket file descriptor to the
* one contained in the "sockfd" int.
*/
rv = SSL_set_fd(ssl, sockfd);
assert(rv == 1);
/*
* Initializes a TLS/SSL handshake with the connected server.
*/
rv = SSL_connect(ssl);
assert(rv == 1);
std::string req = "gemini://skyjake.fi/lagrange/lagrange_about.png"
"\r\n";
/*
* Sends a GET request to the connected server for the requested file.
*/
rv = SSL_write(ssl, req.data(), req.size());
assert(rv > 0);
std::string buffer = {0};
int read_size = 8192;
/*
* Inputs received data into a buffer while its available and with each
* iteration resize the buffer and read data by an addition of the read
* size, storing the new data in a new portion of the buffer to prevent
* any overwriting.... or so I thought
*/
while (rv != 0)
{
int old_size = buffer.size();
buffer.resize(old_size + read_size);
rv = SSL_read(ssl, buffer.data() + old_size, old_size + read_size);
}
/*
* Removes the Gemini header from the buffer.
*/
buffer.erase(0, buffer.find("\r\n") + 2);
std::ofstream file("image.png", std::ios::binary | std::ios::out
| std::ios::trunc);
SSL_shutdown(ssl);
shutdown(sockfd, 2);
return 0;
}