#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dpi.h" #include "io.h" #include "sundown/markdown.h" #include "sundown/html.h" #include "sundown/buffer.h" #include "sundown/houdini.h" static const size_t sigil_size = sizeof("§")-1; static const int id_length = 16; static const char ssb_ref_base[] = "http://localhost:8027/"; static const char ssb_channel_base[] = "http://localhost:8027/channel/"; static const int max_cols = 40; static char server_hostname[256]; static char server_url[256]; static char zet_dir[_POSIX_PATH_MAX-64]; struct http_request { char *method; char *path; char *query; char *hash; char *http_version; char *host; char *referer; ssize_t content_length; char *content_type; char *multipart_boundary; int fd; char *data, *full_data; size_t len; }; struct zet_search { DIR *dir; char *id; char *query; }; enum page { PAGE_NEW, PAGE_ALL, PAGE_ZET, PAGE_SEARCH, }; static int count_lines_in(const char *str) { int count = 0; char c; int col = 0; while ((c = *str++)) { if (c == '\n' || col++ > max_cols) { count++; col = 0; } } return count; } static int count_lines(int fd) { int count = 0; do { unsigned char buf[4096]; size_t len = sizeof(buf)-1; int rc = read_some(fd, buf, &len); if (rc < 0) { if (errno == EPIPE) break; return -1; } buf[len] = '\0'; count += count_lines_in((char *)buf); } while (1); return count; } static int write_html_char(int fd, char c) { switch (c) { case '&': return write_buf(fd, "&"); case '<': return write_buf(fd, "<"); case '>': return write_buf(fd, ">"); case '"': return write_buf(fd, """); } return write_char(fd, c); } static int write_html(int fd, const char *buf, size_t len) { while (len > 0) { // note: buf must null-terminated char *next = strpbrk(buf, "&<>\""); if (next == NULL) return write_all(fd, (unsigned char *)buf, len); char c = *next; size_t sublen = next - buf; int rc = write_all(fd, (unsigned char *)buf, sublen); if (rc < 0) return -1; buf += sublen + 1; len -= sublen + 1; rc = write_html_char(fd, c); if (rc < 0) return -1; } return 0; } static int write_topbar(int fd, enum page page, const char *str) { int rc = write_buf(fd, "
"); if (page == PAGE_ALL) rc |= write_buf(fd, ""); rc |= write_buf(fd, "All"); if (page == PAGE_ALL) rc |= write_buf(fd, ""); rc |= write_buf(fd, " "); if (page == PAGE_NEW) rc |= write_buf(fd, ""); rc |= write_buf(fd, "New"); if (page == PAGE_NEW) rc |= write_buf(fd, ""); rc |= write_buf(fd, " "); if (page == PAGE_ZET) { rc |= dprintf(fd, " §%s", str, str); } rc |= write_buf(fd, "
"); return rc; } static int passthrough_note_html(int fd, int note_fd) { int rc; do { unsigned char buf[4096]; size_t len = sizeof(buf)-1; rc = read_some(note_fd, buf, &len); if (rc < 0) { if (errno != EPIPE) dprintf(fd, "\nError: %s", strerror(errno)); break; } buf[len] = '\0'; rc = write_html(fd, (char *)buf, len); if (rc < 0) warn("write_html"); } while (rc == 0); return 0; } static int dpi_respond_err(int fd, const char *fmt, ...) { int rc; va_list ap; int err = errno; va_start(ap, fmt); rc = dpi_send_header(fd, "text/plain"); rc |= vdprintf(fd, fmt, ap); rc |= dprintf(fd, ": %s", strerror(err)); va_end(ap); rc |= close(fd); return rc; } static char *zet_get_title(const char *id, char *buf, size_t len) { char path_buf[_POSIX_PATH_MAX]; ssize_t sz = snprintf(path_buf, sizeof(path_buf), "%s/%s", zet_dir, id); if (sz < 0 || (size_t)sz >= sizeof(path_buf)) return NULL; int fd = open(path_buf, O_RDONLY); if (fd < 0) return NULL; int rc = read_some(fd, (unsigned char *)buf, &len); if (rc < 0) { close(fd); return NULL; } buf[len] = '\0'; char *end = strchr(buf, '\n'); if (end != NULL) *end = '\0'; close(fd); while (*buf == '#') buf++; while (*buf == ' ') buf++; return buf; } static char *zet_get_buf_title(const struct buf *link, char *buf, size_t len) { char id_buf[128]; strncpy(id_buf, link->data, link->size); id_buf[sizeof(id_buf)-1] = '\0'; return zet_get_title(id_buf + sigil_size, buf, len); } static int zet_links_in(const char *str, const char *id_to) { while (1) { str = strstr(str, "§"); if (str == NULL) return 0; str += sigil_size; if (!strncmp(str, id_to, id_length)) return 1; } return 0; } static int zet_links_to(const char *id_from, const char *id_to) { // TODO: index links, so that every source file doesn't have to be read all // the time int rc; char path_buf[_POSIX_PATH_MAX]; ssize_t sz = snprintf(path_buf, sizeof(path_buf), "%s/%s", zet_dir, id_from); if (sz < 0 || (size_t)sz >= sizeof(path_buf)) return -1; int fd = open(path_buf, O_RDONLY); if (fd < 0) return -1; size_t fringe = 0; size_t link_len = strlen(id_to) + id_length; unsigned char buf[4096]; do { size_t len = sizeof(buf)-fringe-1; rc = read_some(fd, buf + fringe, &len); if (rc < 0) { if (errno != EPIPE) { close(fd); return -1; } close(fd); return 0; } buf[len - fringe] = '\0'; rc = zet_links_in((char *)buf, id_to); if (rc < 0) { close(fd); return -1; } // in case a link is across the boundary between this buf and the next, // keep some "fringe" of the end of this buf in the beginning of the // next one if (len > link_len) { fringe = link_len; memcpy(buf, buf + len - fringe, fringe); } } while (rc == 0); close(fd); return rc; } static int zet_matches_query(const char *id, const char *query) { char path_buf[_POSIX_PATH_MAX]; ssize_t sz = snprintf(path_buf, sizeof(path_buf), "%s/%s", zet_dir, id); if (sz < 0 || (size_t)sz >= sizeof(path_buf)) return -1; int note_fd = open(path_buf, O_RDONLY); if (note_fd < 0) return -1; char *text = read_full(note_fd); bool found = strcasestr(text, query) != NULL; free(text); return found ? 1 : 0; } static int zet_search_start(struct zet_search *zs, char *id, char *query) { zs->dir = opendir(zet_dir); if (zs->dir == NULL) return -1; zs->id = id; zs->query = query; return 0; } static int zet_search_next(struct zet_search *zs, const char **idp) { int rc; struct dirent *ent; const char *id; char *const dest_id = zs->id; char *const query = zs->query; while (1) { errno = 0; ent = readdir(zs->dir); if (ent == NULL) { if (errno != 0) return -1; id = NULL; break; } id = ent->d_name; if (id[0] == '.') continue; if (strpbrk(id, "<>&\"~") != NULL) continue; if (dest_id != NULL) { rc = zet_links_to(id, dest_id); if (rc < 0) return -1; if (rc != 1) continue; } if (query != NULL) { rc = zet_matches_query(id, query); if (rc < 0) return -1; if (rc != 1) continue; } break; }; *idp = id; return 0; } static int zet_search_close(struct zet_search *sz) { return closedir(sz->dir); } static int dpi_serve_zet_index(int fd) { int rc; struct zet_search zs; rc = zet_search_start(&zs, NULL, NULL); if (rc < 0) return dpi_respond_err(fd, "Unable to list"); rc = dpi_send_header(fd, "text/html"); if (rc < 0) { warn("dpi_send_header"); close(fd); return 0; } rc = dprintf(fd, "" "Notes" "" "" ""); rc |= write_topbar(fd, PAGE_ALL, NULL); rc |= write_buf(fd, "
    "); if (rc < 0) { warn("write"); close(fd); return 0; } const char *id; while (1) { rc = zet_search_next(&zs, &id); if (rc < 0) { rc = dprintf(fd, "\nError: %s\n", strerror(errno)); if (rc < 0) warnx("dprintf"); close(fd); return 0; } if (id == NULL) break; char title_buf[128]; char *title = zet_get_title(id, title_buf, sizeof(title_buf)); if (title == NULL) { warn("zet_get_title"); title = title_buf; strncpy(title, id, sizeof(title)-1); title[sizeof(title)-1] = '\0'; return 0; } rc = dprintf(fd, "
  • ", id); rc |= write_html(fd, title, strlen(title)); rc |= write_buf(fd, "
  • "); if (rc < 0) { warnx("write"); close(fd); return 0; } } rc = dprintf(fd, "
"); if (rc < 0) { warn("dprintf"); close(fd); return 0; } rc = zet_search_close(&zs); if (rc < 0) warn("closedir"); close(fd); return 0; } static int dpi_serve_zet_search(int fd, char *qs) { int rc; struct zet_search zs; char *query = NULL; char *name = qs, *next_name; while (name != NULL) { char *value = strchr(name, '='); if (value != NULL) *value++ = '\0'; next_name = strchr(value != NULL ? value : name, '&'); if (next_name != NULL) *next_name++ = '\0'; if (!strcmp(name, "q")) query = value; name = next_name; } rc = zet_search_start(&zs, NULL, query); if (rc < 0) return dpi_respond_err(fd, "Unable to list"); rc = dpi_send_header(fd, "text/html"); if (rc < 0) { warn("dpi_send_header"); close(fd); return 0; } rc = dprintf(fd, "" "Notes" "" "" "" ); rc |= write_topbar(fd, PAGE_SEARCH, query); rc |= write_buf(fd, "
    "); if (rc < 0) { warn("write"); close(fd); return 0; } const char *id; while (1) { rc = zet_search_next(&zs, &id); if (rc < 0) { rc = dprintf(fd, "\nError: %s\n", strerror(errno)); if (rc < 0) warnx("dprintf"); close(fd); return 0; } if (id == NULL) break; char title_buf[128]; char *title = zet_get_title(id, title_buf, sizeof(title_buf)); if (title == NULL) { warn("zet_get_title"); title = title_buf; strncpy(title, id, sizeof(title)-1); title[sizeof(title)-1] = '\0'; return 0; } rc = dprintf(fd, "
  • ", id); rc |= write_html(fd, title, strlen(title)); rc |= write_buf(fd, "
  • "); if (rc < 0) { warnx("dprintf/write_html/write_buf"); close(fd); return 0; } } rc = dprintf(fd, "
"); if (rc < 0) { warn("dprintf"); close(fd); return 0; } rc = zet_search_close(&zs); if (rc < 0) warn("closedir"); close(fd); return 0; } static int dpi_serve_not_found(int fd) { int rc = dpi_send_header(fd, "text/html"); if (rc < 0) { warn("dpi_send_header"); close(fd); return 0; } write_buf(fd, "Not Found"); close(fd); return 0; } static int dpi_serve_zet_new(int fd) { int rc = dpi_send_header(fd, "text/html"); if (rc < 0) { warn("dpi_send_header"); close(fd); return 0; } dprintf(fd, "" "New Note" "" "" ""); write_topbar(fd, PAGE_NEW, NULL); dprintf(fd, "
" "
" "" "
" , server_url); close(fd); return 0; } static void parse_req_uri(char *path, char **hashp, char **queryp) { char *hash = strchr(path, '#'); if (hash != NULL) *hash++ = '\0'; *hashp = hash; char *query = strchr(path, '?'); if (query != NULL) *query++ = '\0'; *queryp = query; } static int md_rndr_link(struct buf *ob, const struct buf *link, const struct buf *title, const struct buf *content, void *opaque) { struct html_renderopt *options = opaque; if (link != NULL && (options->flags & HTML_SAFELINK) != 0 && !sd_autolink_issafe(link->data, link->size)) return 0; BUFPUTSL(ob, "size > sigil_size) { if (!strncmp((char *)link->data, "§", sigil_size)) houdini_escape_href(ob, link->data + sigil_size, link->size - sigil_size); if (link->data[0] == '#') { BUFPUTSL(ob, ssb_channel_base); houdini_escape_href(ob, link->data + 1, link->size - 1); } else if (link->data[0] == '#' || link->data[0] == '@' || link->data[0] == '&' || link->data[0] == '%') { BUFPUTSL(ob, ssb_ref_base); houdini_escape_href(ob, link->data, link->size); } else houdini_escape_href(ob, link->data, link->size); } if (title && title->size) { BUFPUTSL(ob, "\" title=\""); houdini_escape_html0(ob, title->data, title->size, 0); } if (options->link_attributes) { bufputc(ob, '\"'); options->link_attributes(ob, link, opaque); bufputc(ob, '>'); } else { BUFPUTSL(ob, "\">"); } if (content && content->size) bufput(ob, content->data, content->size); BUFPUTSL(ob, ""); return 1; } static void put_link_truncated(struct buf *ob, const struct buf *link, int max_length) { if (link->size > max_length) { houdini_escape_html0(ob, link->data, max_length, 0); BUFPUTSL(ob, "…"); } else { houdini_escape_html0(ob, link->data, link->size, 0); } } static int md_rndr_autolink(struct buf *ob, const struct buf *link, enum mkd_autolink type, void *opaque) { struct html_renderopt *options = opaque; if (!link || !link->size) return 0; if ((options->flags & HTML_SAFELINK) != 0 && !sd_autolink_issafe(link->data, link->size) && type != MKDA_EMAIL) return 0; BUFPUTSL(ob, "data + sigil_size, link->size - sigil_size); else if (type == MKDA_SSB) { if (link->data[0] == '#') { BUFPUTSL(ob, ssb_channel_base); houdini_escape_href(ob, link->data + 1, link->size - 1); } else { BUFPUTSL(ob, ssb_ref_base); houdini_escape_href(ob, link->data, link->size); } } else houdini_escape_href(ob, link->data, link->size); if (type == MKDA_ZET) { char title_buf[128]; char *title = zet_get_buf_title(link, title_buf, sizeof(title_buf)); if (title != NULL) { BUFPUTSL(ob, "\" title=\""); houdini_escape_html0(ob, title, strlen(title), 0); } } if (options->link_attributes) { bufputc(ob, '\"'); options->link_attributes(ob, link, opaque); bufputc(ob, '>'); } else { BUFPUTSL(ob, "\">"); } /* * Pretty printing: if we get an email address as * an actual URI, e.g. `mailto:foo@bar.com`, we don't * want to print the `mailto:` prefix */ if (bufprefix(link, "mailto:") == 0) { houdini_escape_html0(ob, link->data + 7, link->size - 7, 0); } else if (link->data[0] == '&' || link->data[0] == '@' || link->data[0] == '%') { put_link_truncated(ob, link, 8); } else { put_link_truncated(ob, link, max_cols); } BUFPUTSL(ob, ""); return 1; } static int dpi_serve_zet(int fd, char *path) { int rc; char *hash, *query; parse_req_uri(path, &hash, &query); if (!strcmp(path, "/")) return dpi_serve_zet_index(fd); if (!strcmp(path, "/search")) return dpi_serve_zet_search(fd, query); if (!strcmp(path, "/new")) return dpi_serve_zet_new(fd); char path_buf[_POSIX_PATH_MAX]; char *id = path+1; ssize_t sz = snprintf(path_buf, sizeof(path_buf), "%s/%s", zet_dir, id); if (sz < 0 || (size_t)sz >= sizeof(path_buf)) return dpi_respond_err(fd, "read_file"); int note_fd = open(path_buf, O_RDONLY); if (note_fd < 0 && errno == ENOENT) return dpi_serve_not_found(fd); if (note_fd < 0) return dpi_respond_err(fd, "open"); int rows = count_lines(note_fd); if (rows < 0) return dpi_respond_err(fd, "count_lines"); static const int max_rows = 57, min_rows = 8; if (rows > max_rows) rows = max_rows; else if (rows < min_rows) rows = min_rows; rc = lseek(note_fd, 0, SEEK_SET); if (rc < 0) return dpi_respond_err(fd, "lseek"); rc = dpi_send_header(fd, "text/html"); rc |= dprintf(fd, "" "§%s" "" "", id); rc |= write_topbar(fd, PAGE_ZET, id); rc |= dprintf(fd, "" "" "" ""); if (rc < 0) warn("dprintf"); rc = dprintf(fd, ""); if (rc < 0) warn("dprintf"); (void)zet_search_close(&zs); rc = dprintf(fd, "
" "
" "" "
" ); if (rc < 0) { warn("dpi_send_header"); close(fd); return 0; } rc = lseek(note_fd, 0, SEEK_SET); if (rc < 0) { rc = dprintf(fd, "\nError: %s", strerror(errno)); if (rc < 0) warnx("dprintf"); close(fd); } char *text = read_full(note_fd); if (text == NULL) { rc = dprintf(fd, "\nError: %s", strerror(errno)); if (rc < 0) warnx("dprintf"); close(fd); } struct sd_callbacks callbacks; struct html_renderopt options; struct sd_markdown *markdown; struct buf *ob = bufnew(128); sdhtml_renderer(&callbacks, &options, HTML_ESCAPE); callbacks.link = md_rndr_link; callbacks.autolink = md_rndr_autolink; static const unsigned int extensions = MKDEXT_NO_INTRA_EMPHASIS | MKDEXT_TABLES | MKDEXT_FENCED_CODE | MKDEXT_AUTOLINK | MKDEXT_STRIKETHROUGH | MKDEXT_SPACE_HEADERS | MKDEXT_LAX_SPACING; markdown = sd_markdown_new(extensions, 16, &callbacks, &options); sd_markdown_render(ob, (unsigned char *)text, strlen(text), markdown); free(text); sd_markdown_free(markdown); rc = write_all(fd, ob->data, ob->size); if (rc < 0) warn("write_all"); bufrelease(ob); rc = dprintf(fd, "
"); if (rc < 0) warn("dprintf"); struct zet_search zs; bool first = true; rc = zet_search_start(&zs, id, NULL); if (rc < 0) warn("zet_search_start"); do { const char *result_id; rc = zet_search_next(&zs, &result_id); if (rc < 0) warn("zet_search_next"); if (result_id == NULL) break; if (first) { first = false; rc = dprintf(fd, "Backlinks:
    "); if (rc < 0) warn("dprintf"); } char title_buf[128]; char *result_title = zet_get_title(result_id, title_buf, sizeof(title_buf)); if (result_title == NULL) { warn("zet_get_title"); result_title = title_buf; strncpy(result_title, result_id, sizeof(result_title)-1); result_title[sizeof(result_title)-1] = '\0'; return 0; } rc = dprintf(fd, "
  • ", result_id); rc |= write_html(fd, result_title, strlen(result_title)); rc |= write_buf(fd, "
  • "); } while (1); if (!first) (void)dprintf(fd, "
"); rc = dprintf(fd, "
"); if (rc < 0) warn("dprintf"); close(fd); return 0; } static int handle_dpi_client(int fd) { int rc; char url[2048]; rc = dpi_check_auth(fd); if (rc < 0) { warn("dpi auth"); close(fd); return 0; } rc = dpi_read_request(fd, url, sizeof(url)); if (rc < 0) { if (errno == ESHUTDOWN) return -1; warn("dpi request"); dpi_respond_err(fd, "Unable to read request"); return 0; } if (!strncmp(url, "dpi:/zet/", 9)) { return dpi_serve_zet(fd, url + 8); } return dpi_serve_not_found(fd); } static char *next_line(char *str) { char *nextline = strchr(str, '\n'); if (nextline != NULL) { if (nextline[-1] == '\r') nextline[-1] = '\0'; else *nextline = '\0'; nextline++; } return nextline; } static int http_serve_index(struct http_request *req) { write_buf(req->fd, "HTTP/1.0 200 OK\r\nConnection: close\r\n\r\n"); close(req->fd); return 0; } static int save_part(struct http_request *req, int note_fd) { int rc = write_all(note_fd, (unsigned char *)req->data, req->len); rc |= close(note_fd); free(req->full_data); return rc; } static int parse_multipart(struct http_request *req) { size_t boundary_len = req->multipart_boundary != NULL ? strlen(req->multipart_boundary) : 0; if (boundary_len == 0) return -1; ssize_t len = req->content_length; if (len < 0) return -1; if (req->len < (size_t)len) { req->full_data = malloc(len+1); memcpy(req->full_data, req->data, req->len); char *rest = req->full_data + req->len; int rc = read_all(req->fd, (unsigned char *)rest, len - req->len); if (rc < 0) return -1; req->data = req->full_data; req->len = len; } req->data[req->len] = '\0'; char *boundary_start = NULL; while (req->len > 0) { boundary_start = strstr(req->data, req->multipart_boundary); if (boundary_start == NULL) return -1; boundary_start += boundary_len; char *line = boundary_start; char *cdisp = NULL; bool is_text = false; if (*line == '\r') line++; if (*line == '\n') line++; char *nextline; while (line != NULL) { nextline = next_line(line); if (!line[0]) break; char *name = line; char *value = strchr(line, ':'); if (value != NULL) { *value++ = '\0'; while (*value == ' ') value++; } if (!strncmp(name, "Content-Disposition", 3)) cdisp = value; line = nextline; } if (cdisp != NULL && !strcmp(cdisp, "form-data; name=\"text\"")) { is_text = true; } req->len -= nextline - req->data; req->data = nextline; char *end = strstr(nextline, req->multipart_boundary); if (end == NULL || strncmp(end + boundary_len, "--", 2)) return -1; if (end[-1] == '\n') end--; if (end[-1] == '\r') end--; *end = '\0'; req->len = end - nextline; if (is_text) break; } return 0; } static int http_serve_new(struct http_request *req) { int rc; char note_path[_POSIX_PATH_MAX]; char id[64]; rc = parse_multipart(req); if (rc < 0) { write_buf(req->fd, "HTTP/1.0 400 Bad Request\r\n" "Content-Type: text/plain\r\n" "Connection: close\r\n\r\n" "Unable to parse form submission"); close(req->fd); return 0; } (void)mkdir(zet_dir, 0700); srandom(time(NULL) ^ getpid()); (void)snprintf(id, sizeof(id), "%08lx%08lx", random(), random()); (void)snprintf(note_path, sizeof(note_path), "%s/%s", zet_dir, id); int note_fd = open(note_path, O_CREAT|O_WRONLY|O_TRUNC, 0640); if (note_fd < 0) { dprintf(req->fd, "HTTP/1.0 500 Internal Server Error\r\n" "Content-Type: text/plain\r\n" "Connection: close\r\n\r\n" "Unable to open file: %s\n", strerror(errno)); close(req->fd); return 0; } rc = save_part(req, note_fd); if (rc < 0) { dprintf(req->fd, "HTTP/1.0 200 OK\r\n" "Content-Type: text/plain\r\n" "Connection: close\r\n\r\n" "Error saving: %s", strerror(errno)); close(req->fd); return 0; } dprintf(req->fd, "HTTP/1.0 302 Found\r\n" "Content-Type: text/html\r\n" "Location: dpi:/zet/%s\r\n" "Connection: close\r\n\r\n" "" "Created: §%s" "" "" "Created: §%s" "" , id, id, id, id); close(req->fd); return 0; } static int http_serve_not_found(struct http_request *req) { dprintf(req->fd, "HTTP/1.0 200 OK\r\n" "Connection: close\r\n\r\n" "Not found\n"); close(req->fd); return 0; } static int http_serve_zet(struct http_request *req) { int rc; char note_path[_POSIX_PATH_MAX]; char note_path_backup[_POSIX_PATH_MAX]; char *id = req->path + 1; rc = parse_multipart(req); if (rc < 0) { write_buf(req->fd, "HTTP/1.0 400 Bad Request\r\n" "Content-Type: text/plain\r\n" "Connection: close\r\n\r\n" "Unable to parse form submission"); close(req->fd); return 0; } (void)snprintf(note_path, sizeof(note_path), "%s/%s", zet_dir, id); (void)snprintf(note_path_backup, sizeof(note_path_backup), "%s/%s~", zet_dir, id); rc = rename(note_path, note_path_backup); if (rc < 0 && errno != ENOENT) warn("rename"); int note_fd = open(note_path, O_CREAT|O_WRONLY|O_TRUNC, 0640); if (note_fd < 0) { if (errno == ENOENT) return http_serve_not_found(req); dprintf(req->fd, "HTTP/1.0 500 Internal Server Error\r\n" "Content-Type: text/plain\r\n" "Connection: close\r\n\r\n" "Unable to open file: %s\n", strerror(errno)); close(req->fd); return 0; } rc = save_part(req, note_fd); if (rc < 0) { dprintf(req->fd, "HTTP/1.0 200 OK\r\n" "Content-Type: text/plain\r\n" "Connection: close\r\n\r\n" "Error saving: %s", strerror(errno)); close(req->fd); return 0; } dprintf(req->fd, "HTTP/1.0 302 Found\r\n" "Content-Type: text/html\r\n" "Location: dpi:/zet/%s\r\n" "Connection: close\r\n\r\n" "" "Saved: §%s" "" "" "Saved: §%s" "" , id, id, id, id); close(req->fd); return 0; } static int handle_http_client(int fd, int firstchar) { int rc; struct http_request req; char buf[2048]; buf[0] = firstchar; size_t len = sizeof(buf)-1; rc = read_some(fd, (unsigned char *)buf+1, &len); buf[len+1] = '\0'; len++; if (rc < 0) { close(fd); return 0; } char *nextline = next_line(buf); req.method = buf; req.path = strchr(buf, ' '); if (req.path == NULL) { close(fd); return 0; } *req.path++ = '\0'; req.http_version = strchr(req.path, ' '); if (req.http_version != NULL) *req.http_version++ = '\0'; req.referer = NULL; req.host = NULL; req.content_length = -1; req.content_type = NULL; req.multipart_boundary = NULL; char *line = nextline; while (line != NULL) { nextline = next_line(line); if (!line[0]) break; char *name = line; char *value = strchr(line, ':'); if (value != NULL) { *value++ = '\0'; while (*value == ' ') value++; } if (!strncmp(name, "Host", 3)) req.host = value; else if (!strncmp(name, "Referer", 7)) req.referer = value; else if (!strncmp(name, "Content-Length", 14)) req.content_length = atoi(value); else if (!strncmp(name, "Content-Type", 14)) req.content_type = value; // else fprintf(stderr, "%s = %s\n", name, value); line = nextline; } if (req.content_type != NULL && !strncmp(req.content_type, "multipart/", 10)) { char *subtype = req.content_type + 10; char *boundary = strstr(subtype, "; boundary="); if (boundary != NULL) { *boundary = '\0'; boundary += 11; if (boundary[0] == '"') { boundary++; char *end = strchr(boundary, '"'); if (end != NULL) *end = '\0'; } *--boundary = '-'; *--boundary = '-'; req.multipart_boundary = boundary; } } if (req.referer == NULL || strncmp(req.referer, server_url, strlen(server_url))) { dprintf(fd, "HTTP/1.0 403 Forbidden\r\n" "Connection: close\r\n\r\n" "Invalid Referer\n"); close(fd); return 0; } if (req.host != NULL && strcmp(req.host, server_hostname)) { dprintf(fd, "HTTP/1.0 403 Forbidden\r\n" "Connection: close\r\n\r\n" "Invalid Host\n"); close(fd); return 0; } ptrdiff_t offset = nextline - buf; req.data = nextline; req.full_data = NULL; req.len = len - offset; req.fd = fd; parse_req_uri(req.path, &req.hash, &req.query); if (!strcmp(req.path, "/")) return http_serve_index(&req); if (!strcmp(req.path, "/new")) return http_serve_new(&req); return http_serve_zet(&req); } static int handle_client(int server_fd) { int fd; do { fd = accept(server_fd, NULL, NULL); if (fd < 0 && errno != ENETDOWN && errno != EPROTO && errno != ENOPROTOOPT && errno != EHOSTDOWN && errno != ENONET && errno != EHOSTUNREACH && errno != EOPNOTSUPP && errno != ENETUNREACH ) return -1; } while (fd < 0); int firstchar = read_char(fd); if (firstchar < 0) { // ignore errno close(fd); return 0; } if (firstchar == '<') return handle_dpi_client(fd); return handle_http_client(fd, firstchar); } void get_server_address() { struct sockaddr_storage addr; struct sockaddr *addrp = (struct sockaddr *)&addr; socklen_t addr_len = sizeof(addr); char host_buf[128]; const char *host; unsigned short port; int rc = getsockname(STDIN_FILENO, addrp, &addr_len); if (rc < 0) err(1, "getsockname"); if (addr.ss_family == AF_INET) { struct sockaddr_in *addr_in = (struct sockaddr_in *)&addr; port = ntohs(addr_in->sin_port); host = inet_ntop(AF_INET, &addr_in->sin_addr, host_buf, sizeof(host_buf)); } else if (addr.ss_family == AF_INET6) { struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)&addr; port = ntohs(addr_in6->sin6_port); host = inet_ntop(AF_INET6, &addr_in6->sin6_addr, host_buf, sizeof(host_buf)); } else { errx(1, "Unknown server address family: %d", addr.ss_family); } int wrote = snprintf(server_hostname, sizeof(server_hostname), "%s:%hu", host, port); if (wrote <= 0 || wrote >= (int)sizeof(server_hostname)) { errx(1, "Unable to write hostname"); } wrote = snprintf(server_url, sizeof(server_url), "http://%s/", server_hostname); if (wrote <= 0 || wrote >= (int)sizeof(server_url)) { errx(1, "Unable to write local URL"); } } int main(int argc, char *argv[]) { (void)argc; (void)argv; warnx("started"); const char *home = getenv("HOME"); int len = snprintf(zet_dir, sizeof(zet_dir), "%s/.zet", home); if (len < 0 || len >= (int)sizeof(zet_dir)) { err(1, "unable to build directory name"); } // Since dpi requests don't support a request body, we POST via HTTP // to our dpi server socket which is already listing on a local TCP port. get_server_address(); // handle connections one at a time while (handle_client(STDIN_FILENO) == 0); warnx("exiting"); }