/* * A partial implementation of HTTP/1.0 * * This code is mainly intended as a replacement for the book's 'tiny.c' server * It provides a *partial* implementation of HTTP/1.0 which can form a basis for * the assignment. * * @author G. Back for CS 3214 Spring 2018 */ #include #include #include #include #include #include #include #include #include #include #include #include #include "http.h" #include "hexdump.h" #include "socket.h" #include "bufio.h" // Need macros here because of the sizeof #define CRLF "\r\n" #define STARTS_WITH(field_name, header) \ (!strncasecmp(field_name, header, sizeof(header) - 1)) char * server_root; // root from which static files are served /* Parse HTTP request line, setting req_method, req_path, and req_version. */ static bool http_parse_request(struct http_transaction *ta) { size_t req_offset; ssize_t len = bufio_readline(ta->client->bufio, &req_offset); if (len < 2) // error, EOF, or less than 2 characters return false; char *request = bufio_offset2ptr(ta->client->bufio, req_offset); char *endptr; char *method = strtok_r(request, " ", &endptr); if (method == NULL) return false; if (!strcmp(method, "GET")) ta->req_method = HTTP_GET; else if (!strcmp(method, "POST")) ta->req_method = HTTP_POST; else ta->req_method = HTTP_UNKNOWN; char *req_path = strtok_r(NULL, " ", &endptr); if (req_path == NULL) return false; ta->req_path = bufio_ptr2offset(ta->client->bufio, req_path); char *http_version = strtok_r(NULL, CRLF, &endptr); if (http_version == NULL) // would be HTTP 0.9 return false; if (!strcmp(http_version, "HTTP/1.1")) ta->req_version = HTTP_1_1; else if (!strcmp(http_version, "HTTP/1.0")) ta->req_version = HTTP_1_0; else return false; return true; } /* Process HTTP headers. */ static bool http_process_headers(struct http_transaction *ta) { for (;;) { size_t header_offset; ssize_t len = bufio_readline(ta->client->bufio, &header_offset); if (len <= 0) return false; char *header = bufio_offset2ptr(ta->client->bufio, header_offset); if (len == 2 && STARTS_WITH(header, CRLF)) // empty CRLF return true; header[len-2] = '\0'; /* Each header field consists of a name followed by a * colon (":") and the field value. Field names are * case-insensitive. The field value MAY be preceded by * any amount of LWS, though a single SP is preferred. */ char *endptr; char *field_name = strtok_r(header, ":", &endptr); char *field_value = strtok_r(NULL, " \t", &endptr); // skip leading & trailing OWS if (field_name == NULL) return false; // printf("Header: %s: %s\n", field_name, field_value); if (!strcasecmp(field_name, "Content-Length")) { ta->req_content_len = atoi(field_value); } /* Handle other headers here. */ } } const int MAX_HEADER_LEN = 2048; /* add a formatted header to the response buffer. */ void http_add_header(buffer_t * resp, char* key, char* fmt, ...) { va_list ap; buffer_appends(resp, key); buffer_appends(resp, ": "); va_start(ap, fmt); char *error = buffer_ensure_capacity(resp, MAX_HEADER_LEN); int len = vsnprintf(error, MAX_HEADER_LEN, fmt, ap); resp->len += len > MAX_HEADER_LEN ? MAX_HEADER_LEN - 1 : len; va_end(ap); buffer_appends(resp, "\r\n"); } /* add a content-length header. */ static void add_content_length(buffer_t *res, size_t len) { http_add_header(res, "Content-Length", "%ld", len); } /* start the response by writing the first line of the response * to the response buffer. Used in send_response_header */ static void start_response(struct http_transaction * ta, buffer_t *res) { buffer_appends(res, "HTTP/1.0 "); switch (ta->resp_status) { case HTTP_OK: buffer_appends(res, "200 OK"); break; case HTTP_BAD_REQUEST: buffer_appends(res, "400 Bad Request"); break; case HTTP_PERMISSION_DENIED: buffer_appends(res, "403 Permission Denied"); break; case HTTP_NOT_FOUND: buffer_appends(res, "404 Not Found"); break; case HTTP_METHOD_NOT_ALLOWED: buffer_appends(res, "405 Method Not Allowed"); break; case HTTP_REQUEST_TIMEOUT: buffer_appends(res, "408 Request Timeout"); break; case HTTP_REQUEST_TOO_LONG: buffer_appends(res, "414 Request Too Long"); break; case HTTP_NOT_IMPLEMENTED: buffer_appends(res, "501 Not Implemented"); break; case HTTP_SERVICE_UNAVAILABLE: buffer_appends(res, "503 Service Unavailable"); break; case HTTP_INTERNAL_ERROR: default: buffer_appends(res, "500 Internal Server Error"); break; } buffer_appends(res, CRLF); } /* Send response headers to client */ static bool send_response_header(struct http_transaction *ta) { buffer_t response; buffer_init(&response, 80); start_response(ta, &response); if (bufio_sendbuffer(ta->client->bufio, &response) == -1) return false; buffer_appends(&ta->resp_headers, CRLF); if (bufio_sendbuffer(ta->client->bufio, &ta->resp_headers) == -1) return false; buffer_delete(&response); return true; } /* Send a full response to client with the content in resp_body. */ static bool send_response(struct http_transaction *ta) { // add content-length. All other headers must have already been set. add_content_length(&ta->resp_headers, ta->resp_body.len); if (!send_response_header(ta)) return false; return bufio_sendbuffer(ta->client->bufio, &ta->resp_body) != -1; } const int MAX_ERROR_LEN = 2048; /* Send an error response. */ static bool send_error(struct http_transaction * ta, enum http_response_status status, const char *fmt, ...) { va_list ap; va_start(ap, fmt); char *error = buffer_ensure_capacity(&ta->resp_body, MAX_ERROR_LEN); int len = vsnprintf(error, MAX_ERROR_LEN, fmt, ap); ta->resp_body.len += len > MAX_ERROR_LEN ? MAX_ERROR_LEN - 1 : len; va_end(ap); ta->resp_status = status; return send_response(ta); } /* Send Not Found response. */ static bool send_not_found(struct http_transaction *ta) { return send_error(ta, HTTP_NOT_FOUND, "File %s not found", bufio_offset2ptr(ta->client->bufio, ta->req_path)); } /* A start at assigning an appropriate mime type. Real-world * servers use more extensive lists such as /etc/mime.types */ static const char * guess_mime_type(char *filename) { char *suffix = strrchr(filename, '.'); if (suffix == NULL) return "text/plain"; if (!strcasecmp(suffix, ".html")) return "text/html"; if (!strcasecmp(suffix, ".gif")) return "image/gif"; if (!strcasecmp(suffix, ".png")) return "image/png"; if (!strcasecmp(suffix, ".jpg")) return "image/jpeg"; if (!strcasecmp(suffix, ".js")) return "text/javascript"; return "text/plain"; } /* Handle HTTP transaction for static files. */ static bool handle_static_asset(struct http_transaction *ta, char *basedir) { char fname[PATH_MAX]; char *req_path = bufio_offset2ptr(ta->client->bufio, ta->req_path); // The code below is vulnerable to an attack. Can you see // which? Fix it to avoid indirect object reference (IDOR) attacks. snprintf(fname, sizeof fname, "%s%s", basedir, req_path); if (access(fname, R_OK)) { if (errno == EACCES) return send_error(ta, HTTP_PERMISSION_DENIED, "Permission denied."); else return send_not_found(ta); } // Determine file size struct stat st; int rc = stat(fname, &st); if (rc == -1) return send_error(ta, HTTP_INTERNAL_ERROR, "Could not stat file."); int filefd = open(fname, O_RDONLY); if (filefd == -1) { return send_not_found(ta); } ta->resp_status = HTTP_OK; add_content_length(&ta->resp_headers, st.st_size); http_add_header(&ta->resp_headers, "Content-Type", "%s", guess_mime_type(fname)); bool success = send_response_header(ta); if (!success) goto out; success = bufio_sendfile(ta->client->bufio, filefd, NULL, st.st_size) == st.st_size; out: close(filefd); return success; } static int handle_api(struct http_transaction *ta) { return send_error(ta, HTTP_NOT_FOUND, "API not implemented"); } /* Set up an http client, associating it with a bufio buffer. */ void http_setup_client(struct http_client *self, struct bufio *bufio) { self->bufio = bufio; } /* Handle a single HTTP transaction. Returns true on success. */ bool http_handle_transaction(struct http_client *self) { struct http_transaction ta; memset(&ta, 0, sizeof ta); ta.client = self; if (!http_parse_request(&ta)) return false; if (!http_process_headers(&ta)) return false; if (ta.req_content_len > 0) { int rc = bufio_read(self->bufio, ta.req_content_len, &ta.req_body); if (rc != ta.req_content_len) return false; // To see the body, use this: // char *body = bufio_offset2ptr(ta.client->bufio, ta.req_body); // hexdump(body, ta.req_content_len); } buffer_init(&ta.resp_headers, 1024); http_add_header(&ta.resp_headers, "Server", "CS3214-Personal-Server"); buffer_init(&ta.resp_body, 0); bool rc = false; char *req_path = bufio_offset2ptr(ta.client->bufio, ta.req_path); if (STARTS_WITH(req_path, "/api")) { rc = handle_api(&ta); } else if (STARTS_WITH(req_path, "/private")) { /* not implemented */ } else { rc = handle_static_asset(&ta, server_root); } buffer_delete(&ta.resp_headers); buffer_delete(&ta.resp_body); return rc; }