vessel/old/response.c
2025-06-13 17:58:13 +03:00

366 lines
9 KiB
C

#include "include/conf.h"
#include "include/mem.h"
#include "include/hmap.h"
#include "include/http.h"
#include "include/vessel.h"
#include "include/stream.h"
#include "include/response.h"
#include <time.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
Bool HTTPResponse_init(HTTPResponse *r, Stream *fp) {
if (!r || !fp)
return False;
if (!HMap_init(&r->header_buf))
return False;
r->fp = fp;
r->headered = False;
r->began = False;
return True;
}
uint64_t HTTPResponse_begin(HTTPResponse *r,
const char *version,
const uint16_t code,
const char *connection) {
uint64_t idx;
uint64_t total_written = 0, now_written;
if (!r || !version)
return STREAM_ERROR;
if (r->began)
return 0;
r->code = code;
strncpy(r->version, version, HTTP_VERSION_LENGTH);
r->version[HTTP_VERSION_LENGTH] = '\0';
/* Write response header */
now_written =
Stream_writef(r->fp, "%s %u %s" HTTP_CRLF, version, code, HTTP_code_to_message(code));
if (Stream_ignore_err(now_written) == 0)
return STREAM_ERROR;
total_written += now_written;
r->began = True;
/* Write the date */
HTTPTime date;
HTTP_time(time(NULL), date);
now_written = Stream_writef(r->fp,
"connection: %s" HTTP_CRLF "date: %s" HTTP_CRLF
#ifndef RESPONSE_NO_SERVER_TOKENS
"server: Vessel/" VESSEL_HEADER_VERSION HTTP_CRLF
#endif
#ifdef RESPONSE_5_GAY_RATS
"x-powered-by: 5 gay rats" HTTP_CRLF
#endif /* https://mk.woem.space/notes/a0cppnzuhguhx0pi */
,
connection,
date);
if (Stream_ignore_err(now_written) == 0)
return STREAM_ERROR;
total_written += now_written;
/* Write the headers in the buffer */
for (idx = 0; idx < r->header_buf.size; ++idx) {
HMapBucket b = r->header_buf.buckets[r->header_buf.occupied_els[idx]];
now_written = Stream_writef(r->fp, "%s: %s" HTTP_CRLF, b.key, (char *)b.value);
if (Stream_ignore_err(now_written) == 0)
break; /* Write failed so just ignore the rest of the headers.
*/
total_written += now_written;
}
HMap_destroy_free(&r->header_buf);
return total_written;
}
uint64_t HTTPResponse_header(HTTPResponse *r, const char *key, const char *value) {
if (!r || !key || !value)
return STREAM_ERROR;
if (r->headered)
return 0;
if (!r->began)
return HMap_insert(&r->header_buf, key, dupstr(value)) ? 0 : STREAM_ERROR;
return (r && key && value) ? Stream_writef(r->fp, "%s: %s" HTTP_CRLF, key, value)
: STREAM_ERROR;
}
uint64_t HTTPResponse_headerf(HTTPResponse *r, const char *key, const char *fmt, ...) {
if (!r || !key || !fmt)
return STREAM_ERROR;
if (r->headered)
return 0;
va_list args;
va_start(args, fmt);
char value[HTTP_HEADER_VALUE_MAX_LENGTH];
const int64_t len = vsnprintf(value, HTTP_HEADER_VALUE_MAX_LENGTH, fmt, args);
if (len < 0 || len >= HTTP_HEADER_VALUE_MAX_LENGTH) {
va_end(args);
return STREAM_ERROR;
}
va_end(args);
if (r->began)
return Stream_writef(r->fp, "%s: %s" HTTP_CRLF, key, value);
else
return HMap_insert(&r->header_buf, key, dupstr(value)) ? 0 : STREAM_ERROR;
}
uint64_t HTTPResponse_headers_end(HTTPResponse *r) {
if (!r->fp)
return STREAM_ERROR;
if (!r->began || r->headered)
return 0;
r->headered = True;
return Stream_write(r->fp, HTTP_CRLF, HTTP_CRLF_LENGTH);
}
uint64_t HTTPResponse_content_and_length(HTTPResponse *r, const char *content) {
if (!r || !content)
return STREAM_ERROR;
if (!r->began || r->headered)
return 0;
const uint64_t len = (uint64_t)strlen(content);
const uint64_t total = Stream_writef(r->fp, "content-length: %ju" HTTP_2CRLF, len);
if (!r->headered && Stream_ignore_err(total) == 0)
return STREAM_ERROR;
r->headered = True;
return Stream_write(r->fp, content, len);
}
uint64_t HTTPResponse_content_and_lengthf(HTTPResponse *r, const char *fmt, ...) {
if (!r || !fmt)
return STREAM_ERROR;
if (!r->began || r->headered)
return 0;
va_list args;
va_start(args, fmt);
va_list args_copy;
va_copy(args_copy, args);
/* Calculate the length needed for the formatted string */
const int64_t needed = vsnprintf(NULL, 0, fmt, args_copy);
if (needed < 0) {
va_end(args_copy);
va_end(args);
return STREAM_ERROR;
}
va_end(args_copy);
/* Allocate memory for the content (including null terminator) */
char *content = Malloc(((uint64_t)needed + 1) * sizeof(char));
if (!content) {
va_end(args);
return STREAM_ERROR;
}
/* Format the string into the allocated buffer */
vsnprintf(content, (uint64_t)needed + 1, fmt, args);
va_end(args);
/* Write the content and headers to the stream and return its length */
if (!r->headered &&
Stream_ignore_err(Stream_writef(r->fp, "content-length: %jd" HTTP_2CRLF, needed)) == 0) {
Free(content);
return STREAM_ERROR;
}
r->headered = True;
const uint64_t result = Stream_write(r->fp, content, (uint64_t)needed);
Free(content);
return result;
}
uint64_t HTTPResponse_content_sendfile(HTTPResponse *r,
const char *mimetype,
const char *path,
const char *filename) {
if (!r || !mimetype || !path)
return STREAM_ERROR;
if (!r->began || r->headered)
return 0;
uint8_t buf[RESPONSE_SENDFILE_BUFSZ];
uint64_t total_written = 0, now_written;
/* Open file */
File f;
FileStat fs;
if (!File_init(&f, NULL))
return STREAM_ERROR;
if (!File_open(&f, path, FILEF_RD, FILEM_NONE)) {
File_destroy(&f);
return STREAM_ERROR;
}
if (!File_stat(&f, &fs)) {
File_destroy(&f);
return STREAM_ERROR;
}
/* TODO: Cache file content? */
HTTPTime last_modified;
HTTP_time(fs.last_modification_time, last_modified);
now_written = Stream_writef(
r->fp, "content-type: %s" HTTP_CRLF "last-modified: %s" HTTP_CRLF, mimetype, last_modified);
if (!r->headered && Stream_ignore_err(now_written) <= 0)
goto error;
total_written += now_written;
/* HTTP/1.0 */
if (!filename || strcmp("HTTP/1.1", r->version) != 0) {
if (!r->headered && Stream_writef(r->fp, "content-length: %jd" HTTP_2CRLF, fs.size) <= 0)
goto error;
r->headered = True;
while (True) {
const uint64_t now_read = File_read(&f, buf, RESPONSE_SENDFILE_BUFSZ);
if (now_read <= 0)
break;
now_written = Stream_write(r->fp, buf, (uint64_t)now_read);
if (now_written != (uint64_t)now_read)
break;
total_written += now_written;
}
goto success; /* Skip 1.1 logic */
}
/* HTTP/1.1 */
/* TODO: Optimise */
uint64_t idx;
const uint64_t len = strlen(path);
char prev = filename[0];
/* Cannot have unescaped double quotes */
for (idx = 0; idx < len; ++idx) {
if ((path[idx] < 32 || path[idx] > 126) || (path[idx] == '"' && prev != '\\'))
goto error;
prev = path[idx];
}
/* Write headers */
now_written =
Stream_writef(r->fp,
"transfer-encoding: chunked" HTTP_CRLF
"content-disposition: attachment; filename=\"%s\"" HTTP_CRLF HTTP_CRLF,
filename);
if (!r->headered && Stream_ignore_err(now_written) == 0)
goto error;
r->headered = True;
/* Write content */
while (True) {
const uint64_t now_read = File_read(&f, buf, RESPONSE_SENDFILE_BUFSZ);
if (now_read <= 0)
break;
if (Stream_ignore_err(Stream_writef(r->fp, "%lX" HTTP_CRLF, now_read)) == 0)
break;
now_written = Stream_write(r->fp, buf, (uint64_t)now_read);
if (now_written != (uint64_t)now_read)
break;
total_written += now_written;
if (Stream_write(r->fp, HTTP_CRLF, HTTP_CRLF_LENGTH) != HTTP_CRLF_LENGTH)
break;
}
if (Stream_write(r->fp, "0" HTTP_2CRLF, HTTP_2CRLF_LENGTH + 1) != ((HTTP_CRLF_LENGTH * 2) + 1))
goto error;
success:
File_destroy(&f);
return total_written;
error:
File_destroy(&f);
return STREAM_ERROR;
}
Bool HTTPResponse_destroy(HTTPResponse *r) {
if (!r)
return False;
if (!r->began)
HMap_destroy_free(&r->header_buf);
return True;
}