Files
dat-thang/extra/dat-dpi/src/dat_dpi.c
T

758 lines
25 KiB
C
Raw Normal View History

2026-10-19 00:12:36 -04:00
#define _POSIX_C_SOURCE 200809L
#include "dat.h"
#include "dat_dns.h"
#include "dat_fetch.h"
#include "dat_paths.h"
#include "dat_dpi_icons.h"
#include "s7.h"
#include "sleep_feed.h"
#include <ctype.h>
#include <errno.h>
#include <locale.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#define TAG_CAP 8192
#define DPI_ARCHIVES_MAX 128
typedef struct {
char key[65];
char root[1200];
char title[256];
char description[512];
uint64_t version;
int used;
} dpi_archive_t;
static dpi_archive_t open_archives[DPI_ARCHIVES_MAX];
static s7_scheme *metadata_scheme;
static int quit_requested;
typedef struct {
char *data;
size_t length;
size_t capacity;
} body_buffer_t;
static int body_appendf(body_buffer_t *body, const char *format, ...)
{
va_list args, copy;
int needed;
size_t capacity;
char *data;
va_start(args, format);
va_copy(copy, args);
needed = vsnprintf(NULL, 0, format, copy);
va_end(copy);
if (needed < 0) { va_end(args); return -1; }
if ((size_t)needed > SIZE_MAX - body->length - 1) { va_end(args); errno = EOVERFLOW; return -1; }
if (body->length + (size_t)needed + 1 > body->capacity) {
capacity = body->capacity ? body->capacity : 512;
while (capacity < body->length + (size_t)needed + 1) {
if (capacity > SIZE_MAX / 2) { capacity = body->length + (size_t)needed + 1; break; }
capacity *= 2;
}
data = realloc(body->data, capacity);
if (!data) { va_end(args); return -1; }
body->data = data;
body->capacity = capacity;
}
vsnprintf(body->data + body->length, body->capacity - body->length, format, args);
va_end(args);
body->length += (size_t)needed;
return 0;
}
static void send_page(const char *url, const char *mime, const void *body, size_t length)
{
const char *p;
printf("<cmd='start_send_page' url='");
for (p = url; *p; p++) { putchar(*p); if (*p == '\'') putchar(*p); }
printf("' '>\nContent-Type: %s\r\n\r\n", mime);
if (length) fwrite(body, 1, length, stdout);
fflush(stdout);
}
static int send_file(const char *url, const char *mime, const dat_archive_t *archive,
const sleep_metadata_entry_t *entry)
{
sleep_feed_t feed;
uint64_t block;
size_t sent = 0;
const char *p;
if (entry->size > SIZE_MAX) { errno = EOVERFLOW; return -1; }
if (sleep_feed_open(&feed, archive->dat_path, "content", NULL)) return -1;
for (block = 0; block < entry->block_count; block++) {
if (!sleep_feed_has(&feed, (size_t)entry->block_offset + (size_t)block)) {
sleep_feed_close(&feed); errno = ENOENT; return -1;
}
}
fputs("<cmd='start_send_page' url='", stdout);
for (p = url; *p; p++) { putchar(*p); if (*p == '\'') putchar(*p); }
printf("' '>\nContent-Type: %s\r\n\r\n", mime);
fflush(stdout);
for (block = 0; block < entry->block_count; block++) {
uint8_t *data = NULL;
size_t length = 0;
if (sleep_feed_read(&feed, (size_t)entry->block_offset + (size_t)block,
&data, &length) || length > (size_t)entry->size - sent) {
free(data); sleep_feed_close(&feed); errno = EIO; return -1;
}
if (length && fwrite(data, 1, length, stdout) != length) {
free(data); sleep_feed_close(&feed); errno = EIO; return -1;
}
sent += length;
free(data);
}
sleep_feed_close(&feed);
fflush(stdout);
if (sent != (size_t)entry->size) { errno = EPROTO; return -1; }
return ferror(stdout) ? -1 : 0;
}
static void send_status(const char *message)
{
const char *p;
fputs("<cmd='send_status_message' msg='", stdout);
for (p = message; *p; p++) { putchar(*p); if (*p == '\'') putchar(*p); }
fputs("' '>", stdout);
fflush(stdout);
}
static void send_reload(const char *url)
{
const char *p;
fputs("<cmd='reload_request' url='", stdout);
for (p = url; *p; p++) { putchar(*p); if (*p == '\'') putchar(*p); }
fputs("' '>", stdout);
fflush(stdout);
}
static int html_append(body_buffer_t *body, const char *text)
{
const unsigned char *p = (const unsigned char *)(text ? text : "");
for (; *p; p++) {
const char *escape = NULL;
switch (*p) {
case '&': escape = "&amp;"; break;
case '<': escape = "&lt;"; break;
case '>': escape = "&gt;"; break;
case '\"': escape = "&quot;"; break;
case '\'': escape = "&#39;"; break;
default: break;
}
if (escape) { if (body_appendf(body, "%s", escape)) return -1; }
else if (body_appendf(body, "%c", *p)) return -1;
}
return 0;
}
static int url_append_path(body_buffer_t *body, const char *path)
{
static const char hex[] = "0123456789ABCDEF";
const unsigned char *p = (const unsigned char *)path;
for (; *p; p++) {
if ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') ||
(*p >= '0' && *p <= '9') || strchr("/-_.~", *p)) {
if (body_appendf(body, "%c", *p)) return -1;
} else if (body_appendf(body, "%%%c%c", hex[*p >> 4], hex[*p & 15])) return -1;
}
return 0;
}
/* Dillo DPIP tags end in space, quote, greater-than. */
static int read_tag(FILE *in, char *tag, size_t cap)
{
int c, quote = 0;
size_t n = 0;
while ((c = fgetc(in)) != EOF) {
if (!n && isspace((unsigned char)c)) continue;
if (n + 1 >= cap) { errno = EOVERFLOW; return -1; }
tag[n++] = (char)c;
if (c == '\'') {
if (quote) {
c = fgetc(in);
if (c == '\'') {
if (n + 1 >= cap) { errno = EOVERFLOW; return -1; }
tag[n++] = (char)c;
continue;
}
if (c != EOF) ungetc(c, in);
}
quote = !quote;
}
if (n >= 2 && tag[n - 2] == '\'' && tag[n - 1] == '>') {
tag[n] = 0;
return 1;
}
}
return n ? -1 : 0;
}
static char *attribute(const char *tag, const char *name)
{
char needle[64];
const char *p, *start, *end;
char *out;
size_t n, used = 0;
if (snprintf(needle, sizeof(needle), " %s='", name) >= (int)sizeof(needle)) return NULL;
p = strstr(tag, needle);
if (p) start = p + strlen(needle);
else if (tag[0] == '<' && !strncmp(tag + 1, needle + 1, strlen(needle) - 1))
start = tag + strlen(needle);
else return NULL;
end = start;
while (*end) {
if (*end == '\'' && end[1] != '\'') break;
if (*end == '\'' && end[1] == '\'') end += 2;
else end++;
}
if (!*end) return NULL;
n = (size_t)(end - start);
out = malloc(n + 1);
if (!out) return NULL;
while (start < end) {
out[used++] = *start;
if (*start == '\'' && start + 1 < end && start[1] == '\'') start++;
start++;
}
out[used] = 0;
return out;
}
static int authenticate(const char *tag)
{
char path[1024], line[512], secret[128];
FILE *f;
char *msg;
int ok = 0;
const char *home = dat_home_directory();
if (!home || snprintf(path, sizeof(path), "%s/.dillo/dpid_comm_keys", home) >= (int)sizeof(path)) return 0;
f = fopen(path, "r");
if (!f) return 0;
if (fgets(line, sizeof(line), f) && sscanf(line, "%*u %127s", secret) == 1) {
msg = attribute(tag, "msg");
if (msg && strlen(msg) == strlen(secret)) ok = memcmp(msg, secret, strlen(secret)) == 0;
free(msg);
}
fclose(f);
return ok;
}
static s7_pointer manifest_field(s7_pointer record, const char *name)
{
s7_pointer fields = record;
while (s7_is_pair(fields)) {
s7_pointer field = s7_car(fields), key, values;
if (s7_is_pair(field)) {
key = s7_car(field);
values = s7_cdr(field);
if (s7_is_symbol(key) && !strcmp(s7_symbol_name(key), name) &&
s7_is_pair(values)) return s7_car(values);
}
fields = s7_cdr(fields);
}
return NULL;
}
static void read_manifest(const dat_archive_t *archive, uint64_t version,
dpi_archive_t *record)
{
dat_path_info_t info;
int listing;
uint8_t *data = NULL;
size_t length = 0;
if (!metadata_scheme) metadata_scheme = s7_init();
if (metadata_scheme &&
dat_archive_resolve_at(archive, version, "/dat.scm", 0, &info, &listing) == 0 &&
!listing && info.entry.type == SLEEP_ENTRY_FILE &&
dat_archive_read_file(archive, &info.entry, &data, &length) == 0 &&
length < 1024u * 1024u && !memchr(data, 0, length)) {
char *source = malloc(length + 1);
if (source) {
s7_pointer port, parsed, title, description;
memcpy(source, data, length); source[length] = '\0';
port = s7_open_input_string(metadata_scheme, source);
parsed = port ? s7_read(metadata_scheme, port) : NULL;
if (port) s7_close_input_port(metadata_scheme, port);
title = parsed ? manifest_field(parsed, "title") : NULL;
description = parsed ? manifest_field(parsed, "description") : NULL;
if (title && s7_is_string(title)) snprintf(record->title, sizeof(record->title), "%s", s7_string(title));
if (description && s7_is_string(description)) snprintf(record->description, sizeof(record->description), "%s", s7_string(description));
free(source);
}
}
free(data);
if (!record->title[0] || !record->description[0]) {
uint8_t *cname = NULL;
size_t cname_length = 0;
if (dat_archive_resolve_at(archive, version, "/CNAME", 0, &info, &listing) == 0 &&
!listing && info.entry.type == SLEEP_ENTRY_FILE &&
dat_archive_read_file(archive, &info.entry, &cname, &cname_length) == 0 &&
cname_length && cname_length < sizeof(record->title)) {
while (cname_length && (cname[cname_length - 1] == '\n' || cname[cname_length - 1] == '\r')) cname_length--;
if (!record->title[0]) { memcpy(record->title, cname, cname_length); record->title[cname_length] = '\0'; }
}
free(cname);
}
}
static dpi_archive_t *remember_archive(const dat_archive_t *archive, const char *keyhex,
const char *root, uint64_t version)
{
size_t i;
dpi_archive_t *record = NULL;
for (i = 0; i < DPI_ARCHIVES_MAX; i++) {
if (open_archives[i].used && !strcmp(open_archives[i].key, keyhex)) {
record = &open_archives[i]; break;
}
if (!open_archives[i].used && !record) record = &open_archives[i];
}
if (!record) return NULL;
if (!record->used) {
memset(record, 0, sizeof(*record));
snprintf(record->key, sizeof(record->key), "%s", keyhex);
snprintf(record->root, sizeof(record->root), "%s", root);
snprintf(record->title, sizeof(record->title), "%s", keyhex);
record->used = 1;
}
record->version = version;
read_manifest(archive, version, record);
return record;
}
static int parse_query_value(const char *url, const char *name, char *out, size_t capacity)
{
const char *query = strchr(url, '?');
size_t name_length = strlen(name);
if (!query) return 0;
query++;
while (*query) {
const char *end = strchr(query, '&');
size_t part_length = end ? (size_t)(end - query) : strlen(query);
const char *equals = memchr(query, '=', part_length);
if (equals && (size_t)(equals - query) == name_length && !memcmp(query, name, name_length)) {
size_t value_length = part_length - name_length - 1;
if (value_length >= capacity) { errno = ENAMETOOLONG; return -1; }
memcpy(out, equals + 1, value_length); out[value_length] = '\0';
return 1;
}
if (!end) break;
query = end + 1;
}
return 0;
}
static int error_page(const char *url, int status, const char *message)
{
const char *p;
(void)status;
printf("<cmd='start_send_page' url='");
for (p = url; *p; p++) { putchar(*p); if (*p == '\'') putchar(*p); }
printf("' '>\nContent-Type: text/plain\r\n\r\n%s\n", message);
fflush(stdout);
return ferror(stdout) ? -1 : 0;
}
typedef struct {
sleep_metadata_entry_t entry;
char name[1024];
int type;
} listing_context_t;
typedef struct {
listing_context_t *entries;
size_t count;
size_t capacity;
} listing_accumulator_t;
static int list_entry(const sleep_metadata_entry_t *entry, const char *name, void *context)
{
listing_accumulator_t *list = context;
listing_context_t *next;
if (list->count == list->capacity) {
size_t capacity = list->capacity ? list->capacity * 2 : 16;
next = realloc(list->entries, capacity * sizeof(*next));
if (!next) return -1;
list->entries = next; list->capacity = capacity;
}
next = &list->entries[list->count++];
memset(next, 0, sizeof(*next));
next->entry = *entry;
next->type = entry->type == SLEEP_ENTRY_DIRECTORY ? 3 : 2;
if (strlen(name) >= sizeof(next->name)) { errno = ENAMETOOLONG; return -1; }
strcpy(next->name, name);
return 0;
}
static int compare_listing(const void *left, const void *right)
{
const listing_context_t *a = left, *b = right;
if (a->type != b->type) return b->type - a->type;
return -strcoll(a->name, b->name);
}
static int append_listing_row(body_buffer_t *body, const dat_url_t *url,
const listing_context_t *entry, const char *link_path)
{
const char *icon = entry->type == 1 ? dat_dpi_icon_updir :
entry->type == 3 ? dat_dpi_icon_directory : dat_dpi_icon_file;
char date[64] = "";
if (entry->entry.mtime) {
time_t seconds = (time_t)(entry->entry.mtime / 1000u);
struct tm *tm = gmtime(&seconds);
if (tm) strftime(date, sizeof(date), "%Y-%m-%d %H:%M:%S", tm);
}
if (body_appendf(body, "<tr><td><img alt=\"%s\" src=\"data:image/png;base64,%s\"></td><td><a href=\"dat://",
entry->type == 1 ? "up" : entry->type == 2 ? "directory" : "file", icon)) return -1;
if (html_append(body, url->host)) return -1;
if (url->has_version && body_appendf(body, "+%llu", (unsigned long long)url->version)) return -1;
if (url_append_path(body, link_path) || body_appendf(body, "\">") ||
html_append(body, entry->name)) return -1;
if (entry->type == 3 && body_appendf(body, "/")) return -1;
if (body_appendf(body, "</a></td><td>")) return -1;
if (*date && html_append(body, date)) return -1;
if (body_appendf(body, "</td><td align=\"right\">")) return -1;
if (entry->type == 2 && body_appendf(body, "%llu", (unsigned long long)entry->entry.size)) return -1;
return body_appendf(body, "</td></tr>\n");
}
static const char *dpi_content_type(const char *pathname)
{
const char *dot = strrchr(pathname, '.');
if (!dot) return "text/plain";
dot++;
if (!strcmp(dot, "htm") || !strcmp(dot, "html")) return "text/html";
if (!strcmp(dot, "png")) return "image/png";
if (!strcmp(dot, "gif")) return "image/gif";
if (!strcmp(dot, "jpg")) return "image/jpeg";
return "text/plain";
}
static int serve_not_found(const char *url)
{
static const char page[] = "<h3>Not found</h3>";
send_page(url, "text/html", page, sizeof(page) - 1);
return -1;
}
static int serve_timeout(const char *url)
{
static const char page[] = "<h3>Timed out</h3>";
send_page(url, "text/html", page, sizeof(page) - 1);
return -1;
}
static int serve_directory(const char *url_text, const dat_url_t *url,
const dat_archive_t *archive, uint64_t version)
{
listing_accumulator_t entries = {0};
listing_context_t parent;
body_buffer_t body = {0};
char parent_path[2048];
size_t i, n;
int failed = 0;
if (dat_archive_readdir_at(archive, version, url->path, list_entry, &entries)) {
free(entries.entries);
return serve_not_found(url_text);
}
qsort(entries.entries, entries.count, sizeof(*entries.entries), compare_listing);
if (body_appendf(&body, "<!doctype html><html><head><title>")) failed = 1;
if (!failed && html_append(&body, url->path)) failed = 1;
if (!failed && body_appendf(&body, "</title></head><body><h4>")) failed = 1;
if (!failed && html_append(&body, url->path)) failed = 1;
if (!failed && body_appendf(&body, "</h4><table>")) failed = 1;
if (strcmp(url->path, "/")) {
memset(&parent, 0, sizeof(parent));
parent.type = 1;
strcpy(parent.name, "..");
strcpy(parent_path, url->path);
n = strlen(parent_path);
while (n && parent_path[n - 1] == '/') parent_path[--n] = '\0';
while (n && parent_path[n - 1] != '/') parent_path[--n] = '\0';
if (!n) strcpy(parent_path, "/");
else parent_path[n] = '\0';
if (!failed && append_listing_row(&body, url, &parent, parent_path)) failed = 1;
}
for (i = 0; !failed && i < entries.count; i++) {
body_buffer_t href = {0};
const listing_context_t *entry = &entries.entries[i];
if (body_appendf(&href, "%s", url->path) ||
(href.length && href.data[href.length - 1] != '/' && body_appendf(&href, "/")) ||
body_appendf(&href, "%s%s", entry->name, entry->type == 3 ? "/" : "") ||
append_listing_row(&body, url, entry, href.data)) failed = 1;
free(href.data);
}
if (!failed && body_appendf(&body, "</table></body></html>")) failed = 1;
if (failed) {
free(body.data); free(entries.entries);
return error_page(url_text, 500, "Unable to build directory listing");
}
send_page(url_text, "text/html", body.data, body.length);
free(body.data); free(entries.entries);
return 0;
}
static int json_append_string(body_buffer_t *body, const char *text)
{
const unsigned char *p = (const unsigned char *)(text ? text : "");
if (body_appendf(body, "\"")) return -1;
for (; *p; p++) {
if (*p == '\"' || *p == '\\') { if (body_appendf(body, "\\%c", *p)) return -1; }
else if (*p < 0x20) { if (body_appendf(body, "\\u%04x", *p)) return -1; }
else if (body_appendf(body, "%c", *p)) return -1;
}
return body_appendf(body, "\"");
}
static int serve(const char *url_text)
{
dat_url_t url;
uint8_t key[DAT_KEY_BYTES];
const char *home = dat_home_directory();
char root[1200], keyhex[65], fetch_error[512];
dat_archive_t archive;
dat_path_info_t info;
int listing = 0;
uint64_t version;
if (dat_url_parse(url_text, &url)) {
error_page(url_text, 400, "Invalid dat:// URL"); return -1;
}
send_status("resolving name");
if (dat_url_to_key(url.host, key) && dat_dns_resolve(url.host, key, NULL)) {
error_page(url_text, 404, "Dat hostname has no usable datkey TXT record"); return -1;
}
if (!home) { error_page(url_text, 500, "User home directory is unavailable"); return -1; }
if (dat_key_to_hex(key, keyhex)) { error_page(url_text, 500, "Unable to format archive key"); return -1; }
if (snprintf(root, sizeof(root), "%s/.dillo/dat/archives/%.2s/%s",
home, keyhex, keyhex + 2) >= (int)sizeof(root)) {
error_page(url_text, 400, "Dillo archive cache path is too long"); return -1;
}
if (dat_archive_open(&archive, root)) {
send_status("getting archive");
if (dat_fetch_archive(root, key, 10000u)) {
int saved = errno;
if (saved == ETIMEDOUT) return serve_timeout(url_text);
const char *reason = saved == ENETUNREACH ? "No Cesspool bootstrap is configured" :
saved == EHOSTUNREACH ? "Cesspool bootstrap did not answer" :
saved == ENOENT ? "No peer is announcing this archive key" :
strerror(saved);
snprintf(fetch_error, sizeof(fetch_error), "Cesspool archive fetch failed: %s", reason);
return error_page(url_text, 404, fetch_error);
}
if (dat_archive_open(&archive, root)) {
snprintf(fetch_error, sizeof(fetch_error), "Fetched archive cannot be opened: %s", strerror(errno));
return error_page(url_text, 404, fetch_error);
}
}
version = url.has_version ? url.version : dat_archive_version(&archive);
if (version > dat_archive_version(&archive)) {
dat_archive_close(&archive); return serve_not_found(url_text);
}
if (dat_archive_resolve_at(&archive, version, url.path,
url.force_directory_listing, &info, &listing)) {
int saved = errno;
dat_archive_close(&archive);
if (saved == ETIMEDOUT || saved == EAGAIN) return serve_timeout(url_text);
return serve_not_found(url_text);
}
(void)remember_archive(&archive, keyhex, root, version);
send_status("serving file");
if (url.wants_stat) {
body_buffer_t stat = {0};
if (body_appendf(&stat, "{\n \"path\": ") || json_append_string(&stat, url.path) ||
body_appendf(&stat, ",\n \"directory\": %s,\n \"size\": %llu,\n \"mtime\": %llu\n}\n",
info.is_directory ? "true" : "false",
(unsigned long long)info.entry.size, (unsigned long long)info.entry.mtime)) {
free(stat.data); dat_archive_close(&archive); return error_page(url_text, 500, "Stat response is too large");
}
send_page(url_text, "text/plain", stat.data, stat.length);
free(stat.data);
} else if (listing) {
serve_directory(url_text, &url, &archive, version);
} else {
if (info.entry.type != SLEEP_ENTRY_FILE) {
dat_archive_close(&archive); return serve_not_found(url_text);
}
if (send_file(url_text, dpi_content_type(info.entry.path), &archive, &info.entry)) {
dat_archive_close(&archive); return error_page(url_text, 500, "File content unavailable");
}
}
dat_archive_close(&archive);
fflush(stdout);
return ferror(stdout) ? -1 : 0;
}
static int serve_dashboard(const char *url)
{
char action[32], key[80], reload[2048];
int has_action, has_key, has_reload;
body_buffer_t body = {0};
size_t i, count = 0;
if (parse_query_value(url, "unswarm", action, sizeof(action)) > 0) {
has_key = parse_query_value(url, "key", key, sizeof(key));
has_reload = parse_query_value(url, "reload", reload, sizeof(reload));
if (has_key > 0) {
for (i = 0; i < DPI_ARCHIVES_MAX; i++)
if (open_archives[i].used && !strcmp(open_archives[i].key, key))
memset(&open_archives[i], 0, sizeof(open_archives[i]));
send_status("unswarming archive");
send_reload(has_reload > 0 ? reload : "dpi:/dat/");
return 0;
}
}
has_action = parse_query_value(url, "quit", action, sizeof(action));
if (has_action > 0) {
static const char quitting[] = "Quitting";
send_page(url, "text/plain", quitting, sizeof(quitting) - 1);
quit_requested = 1;
return 0;
}
if (body_appendf(&body, "<!doctype html><html><head><title>Dat</title></head><body>")) goto fail;
if (body_appendf(&body, "<form action=\"dpi:/dat/\"><input type=\"submit\" name=\"quit\" value=\"Quit\"></form><h3>Archives</h3>")) goto fail;
for (i = 0; i < DPI_ARCHIVES_MAX; i++) if (open_archives[i].used) count++;
if (!count && body_appendf(&body, "<p>None</p>")) goto fail;
for (i = 0; i < DPI_ARCHIVES_MAX; i++) {
dpi_archive_t *archive = &open_archives[i];
if (!archive->used) continue;
if (body_appendf(&body, "<p><a href=\"dat://%s\">", archive->key) ||
html_append(&body, archive->title[0] ? archive->title : archive->key) ||
body_appendf(&body, "</a>")) goto fail;
if (archive->description[0] &&
(body_appendf(&body, " - ") || html_append(&body, archive->description))) goto fail;
if (body_appendf(&body, "<br>version <a href=\"dat://%s+%llu\">%llu</a></p>",
archive->key, (unsigned long long)archive->version,
(unsigned long long)archive->version) ||
body_appendf(&body, "<blockquote><h3>Peers</h3>None")) goto fail;
if (body_appendf(&body, "<form action=\"dpi:/dat/\"><input type=\"hidden\" name=\"key\" value=\"%s\"><input type=\"hidden\" name=\"reload\" value=\"dpi:/dat/\"><input type=\"submit\" name=\"unswarm\" value=\"unswarm\"></form></blockquote>", archive->key)) goto fail;
}
if (body_appendf(&body, "</body></html>")) goto fail;
send_page(url, "text/html", body.data, body.length);
free(body.data);
return 0;
fail:
free(body.data);
return error_page(url, 500, "Unable to build Dat dashboard");
}
static int serve_internal(const char *url)
{
const char *path = strstr(url, "://");
const char *query;
char decoded[2048];
size_t n;
if (!strncmp(url, "dpi:/", 5)) path = url + 4;
else if (path) path = strchr(path + 3, '/');
if (!path) path = "/";
query = strchr(path, '?');
n = query ? (size_t)(query - path) : strlen(path);
if (n >= sizeof(decoded)) return error_page(url, 400, "Internal DPI path is too long");
memcpy(decoded, path, n); decoded[n] = '\0';
if (!strcmp(decoded, "/dat/")) return serve_dashboard(url);
return serve_not_found(url);
}
static int serve_stdio_requests(void)
{
char tag[TAG_CAP];
int result = read_tag(stdin, tag, sizeof(tag));
if (result <= 0 || !authenticate(tag)) return 1;
while (!quit_requested) {
char *cmd, *url;
result = read_tag(stdin, tag, sizeof(tag));
if (result <= 0) break;
cmd = attribute(tag, "cmd");
url = attribute(tag, "url");
if (cmd && !strcmp(cmd, "DpiBye")) { free(cmd); free(url); break; }
if (cmd && !strcmp(cmd, "open_url") && url) {
if (!strncmp(url, "dat:", 4)) serve(url);
else if (!strncmp(url, "dpi:/", 5)) serve_internal(url);
else error_page(url, 404, "Not found");
}
free(cmd);
free(url);
}
return 0;
}
static int serve_socket_requests(void)
{
int listener = dup(STDIN_FILENO), stopped = 0;
if (listener < 0) return -1;
while (!stopped) {
int client = accept(listener, NULL, NULL);
int saved_in, saved_out, stop_after_request = 0;
char tag[TAG_CAP];
if (client < 0) {
if (errno == EINTR) continue;
close(listener);
return -1;
}
saved_in = dup(STDIN_FILENO);
saved_out = dup(STDOUT_FILENO);
if (saved_in < 0 || saved_out < 0 || dup2(client, STDIN_FILENO) < 0 ||
dup2(client, STDOUT_FILENO) < 0) {
if (saved_in >= 0) close(saved_in);
if (saved_out >= 0) close(saved_out);
close(client);
close(listener);
return -1;
}
clearerr(stdin);
clearerr(stdout);
if (read_tag(stdin, tag, sizeof(tag)) > 0 && authenticate(tag)) {
while (!stop_after_request) {
char *cmd, *url;
int result = read_tag(stdin, tag, sizeof(tag));
if (result <= 0) break;
cmd = attribute(tag, "cmd");
url = attribute(tag, "url");
if (cmd && !strcmp(cmd, "DpiBye")) {
stopped = 1;
stop_after_request = 1;
} else if (cmd && !strcmp(cmd, "open_url") && url) {
if (!strncmp(url, "dat:", 4)) serve(url);
else if (!strncmp(url, "dpi:/", 5)) serve_internal(url);
else error_page(url, 404, "Not found");
stop_after_request = 1;
}
free(cmd);
free(url);
}
}
fflush(stdout);
/* The historical dillo-dat plugin ends the accepted socket per page. */
dup2(saved_in, STDIN_FILENO);
dup2(saved_out, STDOUT_FILENO);
close(saved_in);
close(saved_out);
close(client);
clearerr(stdin);
clearerr(stdout);
}
close(listener);
return 0;
}
int main(int argc, char **argv)
{
int result;
(void)setlocale(LC_COLLATE, "");
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
metadata_scheme = s7_init();
if (argc == 2 && !strcmp(argv[1], "--stdio-test"))
result = serve_stdio_requests();
else
result = serve_socket_requests();
if (metadata_scheme) s7_free(metadata_scheme);
return result < 0 ? 1 : result;
}