#include "core.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int tip_client_method_not_found(struct tip_client *cli) { /* To meet RFC 7231, this function MUST generate an Allow header field * containing the correct methods. For example: "Allow: POST\r\n" */ char buf[] = "HTTP/1.1 405 Method Not Allowed\r\n" "Content-Length: 0\r\n\r\n"; send(tip_client_socket(cli), buf, strlen(buf), 0); return -1; } static int tip_client_file_not_found(struct tip_client *cli) { char buf[] = "HTTP/1.1 404 Not Found\r\n" "Content-Length: 0\r\n\r\n"; send(tip_client_socket(cli), buf, strlen(buf), 0); return -1; } static bool sanitize(const char *uri) { /* TODO: smarter sanitization. */ if (strstr(uri, "..")) return false; return true; } #define BLOCK 1024000 int tip_client_state_process_payload(struct tip_client *cli) { const char *trailer, *x_redirect; bool allow_redirect = true; char _uri[32], *uri = _uri; char allow_redirect_str[5]; char path[PATH_MAX + 1]; char *chunk = NULL; struct stat st; int err; /* syslog(LOG_DEBUG, "%s:rhu %.32s ...\n", inet_ntoa(cli->addr.sin_addr), ntohs(cli->addr.sin_port), cli->buf); */ if (!strncmp(cli->buf, "GET", strlen("GET"))) { cli->method = TIP_METHOD_GET; if (sscanf(cli->buf, "GET %31s HTTP/1.1", uri) != 1) return tip_client_method_not_found(cli); } else if (!strncmp(cli->buf, "HEAD", strlen("HEAD"))) { cli->method = TIP_METHOD_HEAD; if (sscanf(cli->buf, "HEAD %31s HTTP/1.1", uri) != 1) return tip_client_method_not_found(cli); } else if (!strncmp(cli->buf, "POST", strlen("POST"))) { cli->method = TIP_METHOD_POST; if (sscanf(cli->buf, "POST %31s HTTP/1.1", uri) != 1) return tip_client_method_not_found(cli); } else { return tip_client_method_not_found(cli); } x_redirect = strstr(cli->buf, "X-Accept-Redirect: "); if (x_redirect && sscanf(x_redirect, "X-Accept-Redirect: %4s", allow_redirect_str) == 1 && !strncmp(allow_redirect_str, "off", strlen("off"))) allow_redirect = false; trailer = strstr(cli->buf, "\r\n\r\n"); if (!sanitize(uri)) return tip_client_method_not_found(cli); /* skip initial / */ uri++; switch (cli->method) { case TIP_METHOD_GET: case TIP_METHOD_POST: if (!redirect) break; /* skip checksum files. */ if (strstr(uri, ".full.sum")) { cli->checksum = true; break; } /* get chunk number from file extension, e.g. FILE.0 */ chunk = strchr(uri, '.'); if (chunk) { *chunk = '\0'; chunk++; cli->chunk = atoi(chunk); if (cli->chunk >= MAX_CHUNKS) return tip_client_file_not_found(cli); } break; case TIP_METHOD_HEAD: break; } snprintf(path, PATH_MAX, "%s/%s", root, uri); err = stat(path, &st); if (err < 0) return tip_client_file_not_found(cli); /* restore the original uri that was mangled. */ if (chunk) { chunk--; *chunk = '.'; } cli->uri = strdup(uri); cli->path = strdup(path); cli->size = st.st_size; switch (cli->method) { case TIP_METHOD_GET: break; case TIP_METHOD_HEAD: cli->state = TIP_CLIENT_PROCESSING_REQUEST_2; return 0; case TIP_METHOD_POST: cli->allow_redirect = true; tip_client_redirect_create(cli); tip_client_activate_pending(true); cli->state = TIP_CLIENT_PROCESSING_REQUEST_2; return 0; } if (!tip_client_checksum_file(cli)) { cli->allow_redirect = allow_redirect; num_clients++; if (num_clients > max_clients) { if (!tip_client_redirect(cli)) { tip_client_pending(cli); return 1; } } } cli->state = TIP_CLIENT_PROCESSING_REQUEST_2; return 0; } int tip_client_state_process_payload_reply(struct tip_client *cli) { uint64_t chunk_size; uint32_t remainder; off_t chunk_offset; char buf[1024]; int fd; if (cli->redirect) { snprintf(buf, sizeof(buf), "HTTP/1.1 301 Moves Permanently\r\nLocation: http://%s:%hu/%s\r\n\r\n", inet_ntoa(cli->redirect_addr.sin_addr), htons(cli->redirect_addr.sin_port), cli->uri); send(tip_client_socket(cli), buf, strlen(buf), 0); return 1; } fd = open(cli->path, O_RDONLY); if (fd < 0) return tip_client_file_not_found(cli); switch (cli->method) { case TIP_METHOD_GET: if (!redirect) break; if (cli->chunk < 0) break; chunk_size = cli->size / MAX_CHUNKS; chunk_offset = chunk_size * cli->chunk; if (cli->chunk == MAX_CHUNKS - 1) { remainder = cli->size % MAX_CHUNKS; chunk_size += remainder; } cli->size = chunk_size; cli->offset = chunk_offset; break; case TIP_METHOD_POST: cli->size = 0; break; case TIP_METHOD_HEAD: break; } cli->left = cli->size; snprintf(buf, sizeof(buf), "HTTP/1.1 200 OK\r\nContent-Length: %lu\r\n\r\n", cli->size); send(tip_client_socket(cli), buf, strlen(buf), 0); cli->fd = fd; switch (cli->method) { case TIP_METHOD_GET: cli->state = TIP_CLIENT_PROCESSING_REQUEST_3; break; case TIP_METHOD_HEAD: case TIP_METHOD_POST: /* close connection. */ return 1; } return 0; } int tip_client_state_process_payload_bulk(struct tip_client *cli) { uint32_t bytes; int ret; if (cli->left < BLOCK) bytes = cli->left; else bytes = BLOCK; ret = sendfile(tip_client_socket(cli), cli->fd, &cli->offset, bytes); if (ret < 0) return -1; cli->left -= ret; if (cli->left <= 0) { cli->state = TIP_CLIENT_CLOSE_WAIT; return 1; } return 0; }