368 lines
9.8 KiB
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;
|
|
}
|