libpolluxd/px_gemini_msg.c

368 lines
9.8 KiB
C

#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <px_common.h>
#include <px_gemini_msg.h>
#include <px_log.h>
#include <px_msg_buf.h>
#include <openssl/err.h>
px_gemini_request_t* px_gemini_request_new() {
px_gemini_request_t* req = PX_NEW_UNINITIALIZED(px_gemini_request_t);
if (!req)
return req;
px_gemini_request_init(req);
return req;
}
void px_gemini_request_init(px_gemini_request_t* req) {
memset(req, 0, sizeof(*req));
req->bufp = &req->bufarr[0];
// req->err = gemini_error_none;
}
void px_gemini_request_destroy(px_gemini_request_t* req) {
if (!req)
return;
px_url_delete(req->url);
req->url = NULL;
}
void px_gemini_request_delete(px_gemini_request_t* req) {
px_gemini_request_destroy(req);
free(req);
}
px_gemini_request_t* px_gemini_request_from_connection(px_connection_t* conn) {
px_gemini_request_t* req = px_gemini_request_new();
// we rely on the following for making data from px_connection_read to later code
req->bufp = &req->bufarr[0];
req->bufp_sz = 0;
int r = px_connection_read(conn, &req->bufarr[0], sizeof(req->bufarr) - 1 /* 1026 */, &req->bufp_sz);
req->bufp[sizeof(req->bufarr) - 1] = '\0'; // ensure we're null terminated
if (r == 1) {
if (req->bufp_sz < 2) { // need cr+lf for proper request
px_log_error("request too short");
req->err = gemini_error_truncated_req;
} else {
// trim the request to the first \r\n
// TODO ensure that this sequence is present, since it's specified by the spec?
// how strict should we be?
char* rn = strstr(req->bufp, "\r\n");
if (rn != NULL) {
*rn = '\0';
req->bufp_sz = rn - &req->bufarr[0];
}
}
} else {
unsigned long err;
while ((err = ERR_get_error()) != 0)
px_log_error("could not read from SSL: (%lu) %s", err, ERR_reason_error_string(err));
px_gemini_request_delete(req);
return NULL;
}
// trim beginning whitespace
while (req->bufp_sz > 0 && isspace(*req->bufp)) {
++req->bufp;
--req->bufp_sz;
}
// trim ending whitespace
if (req->bufp_sz > 0) { // avoid underflows on unsigned buflen
char* bufp_last = &req->bufp[req->bufp_sz - 1];
while (req->bufp_sz > 0 && isspace(*bufp_last)) {
*bufp_last = '\0';
--bufp_last;
--req->bufp_sz;
}
}
px_url_t* url = px_url_parse(req->bufp, req->bufp_sz);
if (url) {
px_log_debug("got request: %.*s", (int)req->bufp_sz, req->bufp);
} else {
px_log_debug("invalid request: %.*s", (int)req->bufp_sz, req->bufp);
}
// TODO additional checking for gemini compliance, e.g. userinfo or fragments
// present means failure, etc
req->url = url;
return req;
}
int px_gemini_valid_url(px_url_t* url) {
return (url->host || url->path) && !url->userinfo && !url->fragment;
}
px_gemini_response_t* px_gemini_response_new() {
px_gemini_response_t* req = PX_NEW_UNINITIALIZED(px_gemini_response_t);
if (!req)
return req;
px_gemini_response_init(req);
return req;
}
void px_gemini_response_init(px_gemini_response_t* resp) {
memset(resp, 0, sizeof(*resp));
//px_msg_buf_init(&resp->msg_buf_);
//pthread_mutex_init(&resp->mtx, NULL);
//resp->refcount = 1;
}
void px_gemini_response_destroy(px_gemini_response_t* resp) {
if (!resp)
return;
//px_msg_buf_destroy(&resp->msg_buf_);
if (resp->header.data)
free(resp->header.data);
if (resp->cb_cleanup)
resp->cb_cleanup(resp);
px_gemini_response_init(resp);
}
void px_gemini_response_delete(px_gemini_response_t* resp) {
px_gemini_response_destroy(resp);
free(resp);
}
#if 0
void px_gemini_response_acquire(px_gemini_response_t* resp) {
if (!resp)
return;
pthread_mutex_lock(&resp->mtx);
px_log_assert((resp->refcount > 0), "reference count on acquire should never be zero!");
++resp->refcount;
pthread_mutex_unlock(&resp->mtx);
}
void px_gemini_response_release(px_gemini_response_t* resp) {
if (!resp)
return;
pthread_mutex_lock(&resp->mtx);
px_log_assert((resp->refcount > 0), "reference count on release should never drop below zero!");
--resp->refcount;
if (resp->refcount == 0) {
pthread_mutex_unlock(&resp->mtx);
px_gemini_response_delete(resp);
} else {
pthread_mutex_unlock(&resp->mtx);
}
}
#endif // 0
static inline int px_gemini_check_status(int status) {
return status >= 10 && status <= 69;
}
int px_gemini_response_set_header(px_gemini_response_t* resp, int status, char const* meta_str) {
int r;
if (!px_gemini_check_status(status)) {
px_log_error("could not set header, bad status code %d\n", status);
return -1;
}
if (resp->header.data)
free(resp->header.data);
resp->header.status = status;
resp->header.data = meta_str ? strdup(meta_str) : NULL;
return 0;
}
int px_gemini_response_set_cb_ondatatx(px_gemini_response_t* resp, px_gemini_cb_ondatatx_f fxn, void* d) {
resp->cb_ondatatx = fxn;
return 0;
}
int px_gemini_response_send(px_gemini_response_t* resp, px_connection_t* conn) {
int r;
size_t bufsz, ck;
px_log_info("sending response");
if (!px_gemini_check_status(resp->header.status)) {
px_log_error("bad gemini response status %d, not sending response", resp->header.status);
return -1;
}
if (!resp->header.data) {
if (!px_gemini_response_set_header(resp, resp->header.status, "octet/stream")) {
px_log_error("could not set header, not sending");
return -1;
}
}
px_msg_buf_t mbuf;
px_msg_buf_init(&mbuf);
bufsz = 3 /* {digit}{digit}{space} */ + strlen(resp->header.data) + 3 /* \r\n\0 */;
ck = px_msg_buf_resize(&mbuf, bufsz);
if (ck < bufsz) {
px_log_error("could not resize message buffer");
px_msg_buf_destroy(&mbuf);
return -1;
}
char* buf = px_msg_buf_get_buffer_writable(&mbuf);
if (!buf) {
px_log_error("could not get writable buffer");
px_msg_buf_destroy(&mbuf);
return -1;
}
r = snprintf(buf, mbuf.buf_sz, "%02d %s\r\n", resp->header.status, resp->header.data);
if (r < 0) {
int e = errno;
px_log_error("could not construct header: %s", strerror(e));
errno = e;
px_msg_buf_destroy(&mbuf);
return -1;
}
if ((size_t)r > bufsz) {
px_log_error("truncated header buffer, wrote %lu/%lu required bytes", bufsz, (size_t)r);
px_msg_buf_destroy(&mbuf);
return -1;
}
px_msg_buf_pend_buffer(&mbuf);
if (mbuf.pending_sz > 0 && mbuf.pending[mbuf.pending_sz - 1] == '\0')
--mbuf.pending_sz; // strip null byte
while (mbuf.pending && mbuf.pending_sz > 0) {
size_t iosz = 0;
r = px_connection_write(conn, mbuf.pending, mbuf.pending_sz, &iosz);
if (r != 1) {
px_log_error("could not write %lu bytes", mbuf.pending_sz);
break;
}
px_msg_buf_advance_pending(&mbuf, iosz);
if (mbuf.pending_sz == 0 && resp->cb_ondatatx) {
r = (resp->cb_ondatatx)(resp, &mbuf);
if (r == PX_GEM_RESP_DATA_EOF)
break;
else if (r < 0)
return -1;
}
}
return 0;
}
struct px_gem_static_buffer_ {
int flags;
char const* buf;
size_t buf_sz;
};
static int serve_buffer_data_cb(px_gemini_response_t* resp, px_msg_buf_t* msg) {
// do not call again
px_log_assert(resp, "sanity check");
px_log_assert(resp->cb_ondatatx == serve_buffer_data_cb, "sanity check failure");
resp->cb_ondatatx = NULL;
// copy and free the buffer info
struct px_gem_static_buffer_* info = resp->cb_data ? (struct px_gem_static_buffer_*)resp->cb_data : NULL;
if (!info)
return PX_GEM_RESP_DATA_EOF;
// load up the response with our data
if (info->buf && info->buf_sz > 0) {
px_msg_buf_use_external_buf(msg, info->buf, info->buf_sz);
msg->pending = msg->buf;
msg->pending_sz = msg->buf_sz;
return PX_GEM_RESP_DATA_MORE;
}
return PX_GEM_RESP_DATA_EOF;
}
static void serve_buffer_cleanup_cb(px_gemini_response_t* resp) {
if (resp && resp->cb_data) {
struct px_gem_static_buffer_* info = (struct px_gem_static_buffer_*)resp->cb_data;
if (info->flags & PX_GEM_RESP_BUFFER_OWNED) {
if (info->flags & PX_GEM_RESP_BUFFER_MAPPED) {
munmap((void*)info->buf, info->buf_sz);
} else {
free((void*)info->buf);
}
}
free(resp->cb_data);
resp->cb_data = NULL;
}
}
px_gemini_response_t* px_gemini_response_from_buffer(char const* content, size_t content_sz, int flags) {
px_gemini_response_t* resp = px_gemini_response_new();
if (!resp)
return resp;
struct px_gem_static_buffer_* info = PX_NEW(struct px_gem_static_buffer_);
if (!info) {
px_gemini_response_delete(resp);
return NULL;
}
resp->cb_data = info;
resp->cb_cleanup = serve_buffer_cleanup_cb;
info->buf = content;
info->buf_sz = content_sz;
resp->cb_ondatatx = serve_buffer_data_cb;
return resp;
}
px_gemini_response_t* px_gemini_response_from_file(char const* filename) {
if (!filename)
return NULL;
int f = open(filename, O_RDONLY);
if (f < 0) {
int e = errno;
px_log_error("could not open %s: %s", filename, strerror(e));
errno = e;
return NULL;
}
struct stat s;
if (fstat(f, &s) < 0) {
int e = errno;
px_log_error("could not stat %s: %s", filename, strerror(e));
close(f);
errno = e;
return NULL;
}
size_t buf_sz = s.st_size;
void* m = mmap(NULL, buf_sz, PROT_READ, MAP_SHARED, f, 0);
if (m == MAP_FAILED) {
int e = errno;
px_log_error("could not map %s: %s", filename, strerror(e));
close(f);
errno = e;
return NULL;
}
close(f);
px_gemini_response_t* resp = px_gemini_response_from_buffer(
m,
buf_sz,
PX_GEM_RESP_BUFFER_OWNED | PX_GEM_RESP_BUFFER_MAPPED);
if (!resp)
munmap(m, buf_sz);
return resp;
}