/* b01902062 藍ĉŒşç‘‹ */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #define CHTTPD_SERVER_ENABLE_ERRMSG #include "chttpd-conn.h" #include "chttpd-log.h" #include "chttpd-server.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define NL "\015\012" enum { HTTP_STATUS_200_OK, HTTP_STATUS_400_BAD_REQUEST, HTTP_STATUS_403_FORBIDDEN, HTTP_STATUS_404_NOT_FOUND, HTTP_STATUS_500_INTERNAL_SERVER_ERROR, HTTP_STATUS_501_NOT_IMPLEMENTED }; static char* http_status_hdr[] = { [HTTP_STATUS_200_OK] = "HTTP/1.1 200 OK" NL, [HTTP_STATUS_400_BAD_REQUEST] = "HTTP/1.1 400 Bad Request" NL, [HTTP_STATUS_403_FORBIDDEN] = "HTTP/1.1 403 Forbidden" NL, [HTTP_STATUS_404_NOT_FOUND] = "HTTP/1.1 404 Not Found" NL, [HTTP_STATUS_500_INTERNAL_SERVER_ERROR] = "HTTP/1.1 500 Internal Server Error" NL, [HTTP_STATUS_501_NOT_IMPLEMENTED] = "HTTP/1.1 501 Not Implemented" NL }; static char* http_status_msg[] = { [HTTP_STATUS_400_BAD_REQUEST] = "\n400 Bad Request" NL "

You browser sent a broken HTTP request." NL "" "[400]

" "" NL, [HTTP_STATUS_403_FORBIDDEN] = "\n403 Forbidden" NL "

You do not have permission to access this object." NL "" "[403]

" "" NL, [HTTP_STATUS_404_NOT_FOUND] = "\n404 Not Found" NL "

Request resource cannot be found on this server." NL "" "[404]

" "" NL, [HTTP_STATUS_500_INTERNAL_SERVER_ERROR] = "\n500 Internal Server Error" NL "

Server encountered an unexpected condition." NL "Please contact the administrator of this site." NL "" "[500]

" "" NL, [HTTP_STATUS_501_NOT_IMPLEMENTED] = "\n501 Not Implemented" NL "

Sorry, I don't know howto handle this method..." NL "" "[501]

" "" NL }; static inline void http_status_write (int connfd, int status_enum) { lbs_posix_write_all (connfd, http_status_hdr[status_enum], 0); lbs_posix_write_all (connfd, "Content-Type: text/html" NL NL, 0); lbs_posix_write_all (connfd, http_status_msg[status_enum], 0); } #define CHTTPD_CONN_THREAD_INIT \ ChttpdConn* conn = ptr_to_ChttpdConn; \ ChttpdServer* server = conn->server; \ ChttpdLog* hlog = conn->hlog; \ unsigned long long id = conn->id; \ int connfd = conn->connfd; \ pthread_sigmask (SIG_SETMASK, &server->conn_mask, NULL); #define CHTTPD_CONN_THREAD_DESTROY \ chttpd_log_write (hlog, "[%4llu] terminated", conn->id); \ pthread_rwlock_wrlock (&server->lock); \ lbs_list_remove (server->conn, conn->conn_node); \ pthread_rwlock_unlock (&server->lock); \ return NULL static inline char* internal_memstr (const void* haystack, size_t haystacklen, const char* needle) { const char* haychar = haystack; bool matched; for (size_t i = 0; i < haystacklen; i++, haychar++) { matched = true; for (size_t j = 0; needle[j] != '\0'; j++) { if (i + j >= haystacklen || haychar[j] != needle[j]) { matched = false; break; } } if (matched) { return (char*)haychar; } } return NULL; } static inline void internal_setenv (LbsStrv* strv, const char* var, const char* value) { LbsArray* str_wrapper = NULL; char* equ_loc = NULL; size_t var_len = strlen (var); size_t value_len = strlen (value); for (size_t i = 0; i < lbs_strv_get_len (strv); i++) { size_t slen = lbs_strv_get_str_len (strv, i); char* str_no_null = lbs_strv_get_str_not_null_terminated (strv, i); char* str_equ_loc = memchr (str_no_null, '=', slen); if (str_equ_loc != NULL && var_len < slen && strncmp (str_no_null, var, var_len) == 0) { str_wrapper = lbs_strv_get_str_wrapper (strv, i); equ_loc = str_equ_loc; break; } } if (str_wrapper != NULL && equ_loc != NULL) { /* Use existing string */ lbs_array_set_len (str_wrapper, var_len + 1); lbs_array_append_mass (str_wrapper, value, value_len); } else { /* Append new string */ lbs_strv_append_str_empty (strv); str_wrapper = lbs_strv_get_str_wrapper (strv, lbs_strv_get_len (strv) - 1); lbs_array_append_mass (str_wrapper, var, var_len); lbs_array_append_data (str_wrapper, &(char){ '=' }); lbs_array_append_mass (str_wrapper, value, value_len); } } #define ERRLEN 256 void* chttpd_conn_admin (void* ptr_to_ChttpdConn) { CHTTPD_CONN_THREAD_INIT; chttpd_log_write (hlog, "[%4llu] sorry, function not implemented", id); chttpd_log_write (hlog, "[%4llu] fd is %d", connfd); CHTTPD_CONN_THREAD_DESTROY; } void* chttpd_conn_http (void* ptr_to_ChttpdConn) { CHTTPD_CONN_THREAD_INIT; const char hdr_delim[] = "\015\012\015\012"; _Static_assert (LBS_STR_STATIC_STRLEN (hdr_delim) == 4, "HTTP header delimiter length must be 4 bytes!"); const char line_delim[] = "\015\012"; _Static_assert (LBS_STR_STATIC_STRLEN (line_delim) == 2, "HTTP line delimiter length must be 2bytes!"); char errmsg[ERRLEN]; LbsArray* hdr_buf = lbs_array_new (sizeof (char)); LbsArray* out_buf = lbs_array_new (sizeof (char)); ssize_t data_offset; ssize_t data_count; while (true) { ssize_t r = read (connfd, conn->buf, CHTTPD_CONN_BUF_SIZE); if (r < 0) { if (errno == EINTR || errno == EAGAIN) { continue; } chttpd_log_write (hlog, "[%4llu] incomplete header: read: %s", id, get_errmsg (errno, errmsg, ERRLEN)); goto http_exit; } else if (r == 0) { chttpd_log_write (hlog, "[%4llu] incomplete header: premature EOF", id); goto http_exit; } const char* hdr_end = internal_memstr (conn->buf, r, hdr_delim); if (hdr_end != NULL) { data_offset = hdr_end - conn->buf; lbs_array_append_mass (hdr_buf, conn->buf, data_offset); lbs_array_append_data (hdr_buf, &(char){ '\0' }); data_offset += 4; data_count = r - data_offset; break; } else { lbs_array_append_mass (hdr_buf, conn->buf, r); } } char* method_start = hdr_buf->data; char* hdr_start = internal_memstr (hdr_buf->data, hdr_buf->len, line_delim); if (hdr_start != NULL) { *hdr_start = '\0'; hdr_start += 2; } chttpd_log_write (hlog, "[%4llu] client request: %s", id, method_start); /* Parse method line */ char* tokstore; char* ma1 = strtok_r (method_start, " \t", &tokstore); if (ma1 == NULL) { chttpd_log_write (hlog, "[%4llu] 400 Bad Request: URL not found", id); http_status_write (connfd, HTTP_STATUS_400_BAD_REQUEST); goto http_exit; } char* ma2 = strtok_r (NULL, " \t", &tokstore); if (ma2 == NULL) { chttpd_log_write (hlog, "[%4llu] 400 Bad Request: What?", id); http_status_write (connfd, HTTP_STATUS_400_BAD_REQUEST); goto http_exit; } else { if (*ma2 != '/') { chttpd_log_write (hlog, "[%4llu] 400 Bad Request: URL not started with /", id); http_status_write (connfd, HTTP_STATUS_400_BAD_REQUEST); goto http_exit; } } char* ma3 = strtok_r (NULL, " \t", &tokstore); char* request_method = ma1; char* request_uri = ma2 + 1; char* server_protocol = (ma3 == NULL) ? "" : ma3; char* query_string = strchr (request_uri, '?'); if (query_string != NULL) { *query_string = '\0'; query_string++; } else { query_string = ""; } #ifdef SPHW_RESTRICTION for (char* p = request_uri; *p != '\0'; p++) { if (!(*p == '_' || isalnum (*p))) { chttpd_log_write (hlog, "[%4llu] 400 Bad Request: character" " `%c\' in request URI is not allowed", id, *p); http_status_write (connfd, HTTP_STATUS_400_BAD_REQUEST); goto http_exit; } } for (char* p = query_string; *p != '\0'; p++) { if (!(*p == '_' || *p == '=' || *p == '&' || isalnum (*p))) { chttpd_log_write (hlog, "[%4llu] 400 Bad Request: character" " `%c\' in query string is not allowed", id, *p); http_status_write (connfd, HTTP_STATUS_400_BAD_REQUEST); goto http_exit; } } #endif /* Only GET is implemented */ if (strcmp (request_method, "GET")) { chttpd_log_write (hlog, "[%4llu] 501 Not Implemented: unknown" "method `%s\'", id, request_method); http_status_write (connfd, HTTP_STATUS_501_NOT_IMPLEMENTED); goto http_exit; } int pipe_in[2], pipe_out[2], pipe_exec[2]; if (pipe (pipe_in) < 0) { chttpd_log_write (hlog, "[%4llu] 500 Internal Server Error:" "pipe: %s", id, get_errmsg (errno, errmsg, ERRLEN)); http_status_write (connfd, HTTP_STATUS_500_INTERNAL_SERVER_ERROR); goto http_exit; } if (pipe (pipe_out) < 0) { chttpd_log_write (hlog, "[%4llu] 500 Internal Server Error:" "pipe: %s", id, get_errmsg (errno, errmsg, ERRLEN)); http_status_write (connfd, HTTP_STATUS_500_INTERNAL_SERVER_ERROR); close (pipe_in[0]); close (pipe_in[1]); goto http_exit; } if (pipe (pipe_exec) < 0) { chttpd_log_write (hlog, "[%4llu] 500 Internal Server Error:" "pipe: %s", id, get_errmsg (errno, errmsg, ERRLEN)); http_status_write (connfd, HTTP_STATUS_500_INTERNAL_SERVER_ERROR); close (pipe_in[0]); close (pipe_in[1]); close (pipe_out[0]); close (pipe_out[1]); goto http_exit; } extern char** environ; LbsStrv* cgienv = lbs_strv_new (); lbs_strv_append_strv (cgienv, LBS_STRV_GENERIC_CONST (environ)); internal_setenv (cgienv, "GATEWAY_INTERFACE", "CGI/1.1"); internal_setenv (cgienv, "QUERY_STRING", query_string); internal_setenv (cgienv, "REQUEST_METHOD", request_method); internal_setenv (cgienv, "REQUEST_URI", request_uri - 1); internal_setenv (cgienv, "SERVER_PROTOCOL", server_protocol); char** cgienv_out = lbs_strv_drop_struct (cgienv); pid_t pid = fork (); if (pid < 0) { chttpd_log_write (hlog, "[%4llu] fork: %s", id, get_errmsg (errno, errmsg, ERRLEN)); lbs_strv_generic_free (cgienv_out); http_status_write (connfd, HTTP_STATUS_500_INTERNAL_SERVER_ERROR); close (pipe_in[0]); close (pipe_in[1]); close (pipe_out[0]); close (pipe_out[1]); close (pipe_exec[0]); close (pipe_exec[1]); goto http_exit; } else if (pid > 0) { close (pipe_in[0]); close (pipe_out[1]); close (pipe_exec[1]); lbs_strv_generic_free (cgienv_out); int errnum; int statfd; struct stat st; if (read (pipe_exec[0], &errnum, sizeof (int)) > 0) { close (pipe_in[1]); close (pipe_out[0]); close (pipe_exec[0]); switch (errnum) { case ENOENT: // File not found chttpd_log_write (hlog, "[%4llu] 404 Not Found", id); http_status_write (connfd, HTTP_STATUS_404_NOT_FOUND); goto http_exit; case EACCES: // Permission denied (static file) statfd = open (request_uri, O_RDONLY); if (statfd < 0) { switch (errno) { case ENOENT: chttpd_log_write (hlog, "[%4llu] (static) " "404 Not Found", id); http_status_write (connfd, HTTP_STATUS_404_NOT_FOUND); goto http_exit; case EACCES: chttpd_log_write (hlog, "[%4llu] (static) " "403 Forbidden", id); http_status_write (connfd, HTTP_STATUS_403_FORBIDDEN); goto http_exit; default: chttpd_log_write (hlog, "[%4llu] (static) " "500 Internal Server Error: open: %s", id, get_errmsg (errno, errmsg, ERRLEN)); goto http_exit; } } if (fstat (statfd, &st) < 0) { chttpd_log_write (hlog, "[%4llu] (static) " "500 Internal Server Error: fstat: %s", id, get_errmsg (errno, errmsg, ERRLEN)); close (statfd); goto http_exit; } off_t written = 0; off_t total = st.st_size; char* content_hdr = lbs_str_printf ( "Content-Type: application/octet-stream" NL "Content-Length: %lld" NL NL, (long long)total); lbs_posix_write_all (connfd, http_status_hdr[HTTP_STATUS_200_OK], 0); lbs_posix_write_all (connfd, content_hdr, 0); free (content_hdr); chttpd_log_write (hlog, "[%4llu] (static) 200 OK: " "Content-Length: %lld", id, (long long)total); while (written < total) { ssize_t r = read (statfd, conn->buf, CHTTPD_CONN_BUF_SIZE); if (r < 0) { if (errno == EINTR || errno == EAGAIN) { continue; } else { chttpd_log_write (hlog, "[%4llu] (static) " "read: %s", id, get_errmsg (errno, errmsg, ERRLEN)); break; } } else if (r == 0) { break; } lbs_posix_write_all (connfd, conn->buf, r); written += r; } chttpd_log_write (hlog, "[%4llu] (static) %lld bytes written", id, (long long)written); goto http_exit; default: chttpd_log_write (hlog, "[%4llu] 500 Internal Server Error: " "execve: %s", id, get_errmsg (errnum, errmsg, ERRLEN)); http_status_write (connfd, HTTP_STATUS_500_INTERNAL_SERVER_ERROR); goto http_exit; } } close (pipe_exec[0]); } else { close (pipe_in[1]); while (dup2 (pipe_in[0], 0) < 0); close (pipe_in[0]); close (pipe_out[0]); while (dup2 (pipe_out[1], 1) < 0); close (pipe_out[1]); while (lbs_posix_add_fd (pipe_exec[1], FD_CLOEXEC) < 0); close (pipe_exec[0]); /* Reset all signals */ struct sigaction sa; sa.sa_handler = SIG_DFL; sa.sa_flags = 0; sigemptyset (&sa.sa_mask); pthread_sigmask (SIG_SETMASK, &sa.sa_mask, NULL); sigaction (SIGPIPE, &sa, NULL); char* cgiargv[] = { request_uri, NULL }; execve (request_uri, cgiargv, cgienv_out); int errnum = errno; write (pipe_exec[1], &errnum, sizeof (int)); _exit (127); } pthread_rwlock_wrlock (&conn->lock); conn->pid = pid; pthread_rwlock_unlock (&conn->lock); int nfds = 1, mfds = connfd > pipe_out[0] ? (connfd + 1) : (pipe_out[0] + 1); fd_set setin, setout; FD_ZERO (&setin); // FD_SET (connfd, &setin); XXX Some data may be sent from the client FD_SET (pipe_out[0], &setin); while (nfds > 0 || data_count > 0) { if (data_count > 0) { size_t r = lbs_posix_write_all ( pipe_in[1], conn->buf + data_offset, data_count); if (r < data_count) { int errno_backup = errno; chttpd_log_write (hlog, "[%4llu] 500 Internal Server Error: " "write: %s", id, get_errmsg (errno, errmsg, ERRLEN)); if (errno_backup == EPIPE) { lbs_posix_write_all (connfd, http_status_hdr[HTTP_STATUS_500_INTERNAL_SERVER_ERROR], 0); lbs_posix_write_all (connfd, "Content-Type: text/html" NL NL, 0); lbs_posix_write_all (connfd, "\n500 Internal Server Error" NL "

Pipes closed the by CGI program." NL "" "[500]

" NL, 0); } else { http_status_write (connfd, HTTP_STATUS_500_INTERNAL_SERVER_ERROR); } close (pipe_in[1]); close (pipe_out[0]); goto http_exit; } data_count = 0; data_offset = 0; } if (nfds <= 0) { continue; } setout = setin; select (mfds, &setout, NULL, NULL, NULL); if (FD_ISSET (connfd, &setout)) { data_count = read (connfd, conn->buf, CHTTPD_CONN_BUF_SIZE); if (data_count == 0 || (data_count < 0 && (errno != EINTR && errno != EAGAIN))) { nfds--; FD_CLR (connfd, &setin); } } if (FD_ISSET (pipe_out[0], &setout)) { ssize_t r = read (pipe_out[0], conn->buf, CHTTPD_CONN_BUF_SIZE); if (r > 0) { lbs_array_append_mass (out_buf, conn->buf, r); } else if (r == 0 || (r < 0 && (errno != EINTR && errno != EAGAIN))) { nfds--; FD_CLR (pipe_out[0], &setin); } } } close (pipe_in[1]); close (pipe_out[0]); int cgistat; if (waitpid (pid, &cgistat, 0) > 0) { if (WIFSIGNALED (cgistat)) { int signum = WTERMSIG (cgistat); chttpd_log_write (hlog, "[%4llu] 500 Internal Server Error: " "CGI program is terminated by signal %d", id, signum); lbs_posix_write_all (connfd, http_status_hdr[HTTP_STATUS_500_INTERNAL_SERVER_ERROR], 0); lbs_posix_write_all (connfd, "Content-Type: text/html" NL NL, 0); lbs_posix_write_all (connfd, "\n500 Internal Server Error" NL "

CGI program was terminated abnormally." NL "" "[500]

" NL, 0); goto http_exit; } } FILE* connfp = fdopen (dup (connfd), "wb"); if (connfp == NULL) { chttpd_log_write (hlog, "[%4llu] cannot open a stdio stream ... " "send all CGI program output to client!"); lbs_posix_write_all (connfd, out_buf->data, out_buf->len); goto http_exit; } /* Compute Content-Length */ char* out_content = internal_memstr (out_buf->data, out_buf->len, hdr_delim); size_t out_content_len; size_t out_hdr_len; bool need_content_len = true; if (out_content == NULL) { out_content = ""; out_hdr_len = out_buf->len; out_content_len = 0; } else { *out_content = '\0'; out_hdr_len = out_content - (char*)(out_buf->data); out_content += 4; out_content_len = (char*)(out_buf->data) + out_buf->len - out_content; } /* Process the header returned by CGI program */ char* out_line = out_buf->data == NULL ? "" : out_buf->data; char* delim; bool out_line_first = true; do { delim = internal_memstr (out_line, out_hdr_len, line_delim); if (delim != NULL) { *delim = '\0'; delim += 2; out_hdr_len -= delim - out_line; } if (out_line_first) { if (lbs_str_has_prefix (out_line, "HTTP/1.1 ")) { chttpd_log_write (hlog, "[%4llu] CGI program says %s", id, out_line); fputs (out_line, connfp); } else { chttpd_log_write (hlog, "[%4llu] CGI program does not specify " "HTTP status (assume 200 OK)", id); fputs ("HTTP/1.1 200 OK", connfp); } } else { if (lbs_str_has_prefix (out_line, "Content-Length:")) { need_content_len = false; } fputs (out_line, connfp); } fputs (NL, connfp); out_line_first = false; } while (delim != NULL); if (need_content_len) { fprintf (connfp, "Content-Length: %zu" NL, out_content_len); } fputs (NL, connfp); fclose (connfp); if (out_content_len > 0) { lbs_posix_write_all (connfd, out_content, out_content_len); } http_exit: lbs_array_unref (hdr_buf); lbs_array_unref (out_buf); CHTTPD_CONN_THREAD_DESTROY; } #undef ERRLEN void chttpd_conn_ctor (void* conn_generic, unsigned long long id, int connfd, ChttpdLog* hlog, ChttpdServer* server, LbsListMeta* slist) { ChttpdConn* conn = conn_generic; conn->id = id; conn->connfd = connfd; conn->hlog = chttpd_log_ref (hlog); conn->server = server; conn->slist = slist; pthread_rwlock_init (&conn->lock, NULL); conn->pid = -1; } void chttpd_conn_dtor (void* conn_generic) { ChttpdConn* conn = conn_generic; close (conn->connfd); chttpd_log_unref (conn->hlog); pthread_rwlock_wrlock (&conn->lock); pthread_rwlock_unlock (&conn->lock); pthread_rwlock_destroy (&conn->lock); free (conn); }