/* WebROaR - Ruby Application Server - http://webroar.in/ * Copyright (C) 2009 Goonj LLC * * This file is part of WebROaR. * * WebROaR is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * WebROaR is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with WebROaR. If not, see . */ #include #include #include #include #include #include #include #include #include "wkr_static.h" #define MAP_SIZE 36 #define DEFAULT_EXPIRES "Headers/expires" #define EXPIRES_BY_TYPE "Headers/expires_by_type" #define EXPIRES_BY_TYPE_EXT "expires_by_type/ext" #define EXPIRES_BY_TYPE_EXPIRES "expires_by_type/expires" #define HTTP_HEADER_IF_MODIFIED_SINCE "HTTP_IF_MODIFIED_SINCE" #define HTTP_HEADER_CONNECTION "HTTP_CONNECTION" #define CONNECTION_CLOSE "Close" #define CONNECTION_KEEP_ALIVE "Keep-Alive" #define WR_MSG_QUEUE_SERVER_HOST "starling/host" #define WR_MSG_QUEUE_SERVER_PORT "starling/port" #define WR_PID_MSG_QUEUE_NAME "starling/pid_queue_name" typedef enum { HTTP_STATUS_200 = 0, HTTP_STATUS_304, HTTP_STATUS_403, HTTP_STATUS_404 } resp_status_t; typedef void (*resp_fun_t) (http_t*, const char*, struct stat *); typedef struct { wr_u_short code; char phrase[56]; char message[128]; resp_fun_t fun; } http_status_t; void http_resp_200(http_t*, const char*, struct stat *); void http_resp_304(http_t*, const char*, struct stat *); void http_resp_403(http_t*, const char*, struct stat *); void http_resp_404(http_t*, const char*, struct stat *); static http_status_t http_status [] = { {200, "200 OK", "", http_resp_200}, {304, "304 Not Modified", "", http_resp_304}, {403, "403 Forbidden", "The requested page is forbidden.", http_resp_403}, {404, "404 Not Found", "The requested page could not be found.", http_resp_404} }; #define HTTP_RESP_ERR_HEADERS "HTTP/1.1 %s\r\n\ Date: %s\r\n\ Server: %s-%s\r\n\ Content-Type: text/html\r\n\ Connection: %s\r\n\ Content-Length: %d\r\n\r\n" #define HTTP_RESP_ERR_BODY "\r\n\ \r\n\ %s\r\n\ \r\n\

%s

\r\n\

%s

\r\n\

%s-%s\ " // Static mapping object static static_file_t* map[MAP_SIZE + 1]; /************************************** * Private Functions *************************************/ /** Searches path backward and returns pointer to the first character of extension */ static char* get_file_ext(const char *path) { int len = strlen(path); char *ext = path + len; while (len) { if (*(--ext) == '.') { return ext + 1; } len--; } return NULL; } /** Set expires time based on file type */ static void set_expires_time(char *ext, long int expires) { int index; char tmp_ext[WR_STR_LEN], *p; strcpy(tmp_ext, ext); p = tmp_ext; while(*p){ *p = wr_tolower(*p); p++; } if(*tmp_ext >= '0' && *tmp_ext <= '9'){ index = (*tmp_ext) - '0'; }else if(*tmp_ext >= 'a' && *tmp_ext <= 'z'){ index = (*tmp_ext) - 'a' + 10; }else{ LOG_ERROR(WARN, "Extension %s is not supported", tmp_ext); return; } if (index >= 0 && index < MAP_SIZE) { static_file_t *e = map[index]; while (e) { if (strcmp(e->ext, tmp_ext) == 0) { e->expires = expires; break; } e = e->next; } } } /* Get mime-type */ static static_file_t* get_mime_type(const char *path) { char *ext = get_file_ext(path); char tmp_ext[WR_STR_LEN], *p; strcpy(tmp_ext, ext); p = tmp_ext; while(*p){ *p = wr_tolower(*p); p++; } if (tmp_ext) { int index; if(*tmp_ext >= '0' && *tmp_ext <= '9'){ index = (*tmp_ext) - '0'; }else if(*tmp_ext >= 'a' && *tmp_ext <= 'z') { index = (*tmp_ext) - 'a' + 10; }else { index = MAP_SIZE; LOG_ERROR(WARN, "Extension %s is not supported", tmp_ext); } if (index >= 0 && index < MAP_SIZE) { static_file_t *e = map[index]; while (e) { if (strcmp(e->ext, tmp_ext) == 0) { return e; } e = e->next; } } } return map[MAP_SIZE]; } /** Get response code */ static short get_resp_code(http_t *h, const char *path, struct stat *buf) { LOG_FUNCTION const char *modify = scgi_header_value_get(h->req->scgi, HTTP_HEADER_IF_MODIFIED_SINCE); time_t modify_tm; if (path == NULL) { LOG_ERROR(WARN, "Requested file path is NULL."); return HTTP_STATUS_404; } if (stat(path, buf)) { LOG_ERROR(WARN, "Requested file %s does not exist.", path); return HTTP_STATUS_404; } if (S_ISDIR(buf->st_mode) != 0) { LOG_ERROR(WARN, "%s is a directory.", path) return HTTP_STATUS_404; } if (strstr(path, "..")) { LOG_ERROR(WARN, "Requested file path %s is forbidden.", path); return HTTP_STATUS_403; } // Compare 'If-Modified-Since' time with file modication time if (modify) { // Assume 'If-Modified-Since' date zone is GMT modify_tm = httpdate_to_c_time(modify) - timezone; long int diff = difftime(buf->st_mtime, modify_tm); if (diff <= 0) { return HTTP_STATUS_304; } } return HTTP_STATUS_200; } static long int get_default_expires(node_t *root) { char *node_value = get_node_value(root, DEFAULT_EXPIRES); if (node_value == NULL) { //return EXPIRES_DURATION; return 0; }else if (strcmp(node_value, "off") == 0) { return 0; }else { return atol(node_value); } } static int create_dictionary(const char *mapping_file, long int expires) { node_t *root = yaml_parse(mapping_file), *node; static_file_t *ext; int index; // Initialize map with NULL value for (index = 0; index < MAP_SIZE; index++) { map[index] = NULL; } if (root == NULL) { LOG_ERROR(SEVERE, "Could not read the file %s", mapping_file); return -1; } node = root; while (node) { ext = wr_malloc(static_file_t); strcpy(ext->ext, node->name); strcpy(ext->mime_type, node->value); ext->expires = expires; int index; if(ext->ext[0] >= '0' && ext->ext[0] <= '9'){ index = ext->ext[0] - '0'; }else if(ext->ext[0] >= 'a' && ext->ext[0] <= 'z'){ index = ext->ext[0] - 'a' + 10; }else{ index = MAP_SIZE; } if (index >= 0 && index < MAP_SIZE) { ext->next = map[index]; map[index] = ext; }else { LOG_ERROR(WARN, "Mapping index out of bound for extension = %s", ext->ext); free(ext); } node = node->next; } node_free(root); // Set default mime type ext = wr_malloc(static_file_t); strcpy(ext->ext, "txt"); strcpy(ext->mime_type, "text/plain"); ext->expires = expires; ext->next = NULL; map[MAP_SIZE] = ext; return 0; } static void set_expires_by_type(node_t *root) { node_t *node = get_nodes(root, EXPIRES_BY_TYPE); long int expires; char *types, *expires_str, *type; while (node) { types = get_node_value(node, EXPIRES_BY_TYPE_EXT); expires_str = get_node_value(node, EXPIRES_BY_TYPE_EXPIRES); expires = atol(expires_str); type = strtok(types, " ,"); while (type != NULL) { set_expires_time(type, expires); type = strtok(NULL, " ,"); } node = NODE_NEXT(node); } } void http_resp_200(http_t *h, const char *path, struct stat *buf) { LOG_FUNCTION char str[WR_LONG_LONG_STR_LEN], expire_date[WR_STR_LEN] = "", current_date[WR_STR_LEN] = "", modify_date[WR_STR_LEN] = ""; int len; time_t t; static_file_t *ext = get_mime_type(path); LOG_DEBUG(DEBUG,"File extension = %s, mimetype = %s, expires = %d ", ext->ext, ext->mime_type, ext->expires); const char *conn_header = scgi_header_value_get(h->req->scgi, HTTP_HEADER_CONNECTION); t = get_time(current_date, WR_STR_LEN); time_to_httpdate(buf->st_mtime, modify_date, WR_STR_LEN); if (ext->expires > 0) { t += ext->expires; time_to_httpdate(t, expire_date, WR_STR_LEN); len = sprintf(str, "HTTP/1.1 200 OK\r\nDate: %s\r\nServer: %s-%s\r\nLast-Modified: %s\r\nExpires: %s\r\nCache-Control: max-age=%ld, public\r\nConnection: %s\r\nContent-Type: %s\r\nContent-Length: %d\r\n\r\n", current_date, WR_SERVER, WR_VERSION, modify_date, expire_date, ext->expires, (conn_header ? conn_header : CONNECTION_CLOSE), ext->mime_type, buf->st_size); }else { len = sprintf(str, "HTTP/1.1 200 OK\r\nDate: %s\r\nServer: %s-%s\r\nLast-Modified: %s\r\nCache-Control: no-cache\r\nConnection: %s\r\nContent-Type: %s\r\nContent-Length: %d\r\n\r\n", current_date, WR_SERVER, WR_VERSION, modify_date, (conn_header ? conn_header : CONNECTION_CLOSE), ext->mime_type, buf->st_size); } wr_string_new(h->resp->header, str, len); h->resp->resp_body->len = buf->st_size; h->resp->resp_code = http_status[HTTP_STATUS_200].code; #ifdef _POSIX_C_SOURCE h->resp->file = open(path, O_RDONLY); #else h->resp->file = fopen(path, "r"); #endif } void http_resp_304(http_t *h, const char *path, struct stat *buf) { LOG_FUNCTION char str[WR_LONG_LONG_STR_LEN], expire_date[WR_STR_LEN] = "", current_date[WR_STR_LEN] = ""; int len; time_t t; static_file_t *ext = get_mime_type(path); LOG_DEBUG(DEBUG,"File extension = %s, mimetype = %s, expires = %d ", ext->ext, ext->mime_type, ext->expires); const char *conn_header = scgi_header_value_get(h->req->scgi, HTTP_HEADER_CONNECTION); t = get_time(current_date, WR_STR_LEN); if (ext->expires > 0) { t = t + ext->expires; time_to_httpdate(t, expire_date, WR_STR_LEN); len = sprintf(str, "HTTP/1.1 304 Not Modified\r\nDate: %s\r\nServer: %s-%s\r\nExpires: %s\r\nCache-Control: max-age=%ld, public\r\nConnection: %s\r\n\r\n", current_date, WR_SERVER, WR_VERSION, expire_date, ext->expires, (conn_header ? conn_header : CONNECTION_CLOSE)); }else { len = sprintf(str, "HTTP/1.1 304 Not Modified\r\nDate: %s\r\nServer: %s-%s\r\nCache-Control: no-cache\r\nConnection: %s\r\n\r\n", current_date, WR_SERVER, WR_VERSION, (conn_header ? conn_header : CONNECTION_CLOSE)); } wr_string_new(h->resp->header, str, len); h->resp->resp_code = http_status[HTTP_STATUS_304].code; } void http_resp_403(http_t *h, const char *path, struct stat *buf) { LOG_FUNCTION char str[WR_LONG_LONG_STR_LEN], current_date[WR_STR_LEN]; const char *conn_header = scgi_header_value_get(h->req->scgi, HTTP_HEADER_CONNECTION); int len; get_time(current_date, WR_STR_LEN); len = sprintf(str, HTTP_RESP_ERR_BODY, http_status[HTTP_STATUS_403].phrase, http_status[HTTP_STATUS_403].phrase + 4, http_status[HTTP_STATUS_403].message, WR_SERVER, WR_VERSION); wr_buffer_create(h->resp->resp_body, len); wr_buffer_add(h->resp->resp_body, str, len); len = sprintf(str, HTTP_RESP_ERR_HEADERS, current_date, http_status[HTTP_STATUS_403].phrase, WR_SERVER, WR_VERSION, (conn_header ? conn_header : CONNECTION_CLOSE), len); wr_string_new(h->resp->header, str, len); h->resp->resp_code = http_status[HTTP_STATUS_403].code; } void http_resp_404(http_t *h, const char *path, struct stat *buf) { LOG_FUNCTION char str[WR_LONG_LONG_STR_LEN], current_date[WR_STR_LEN]; const char *conn_header = scgi_header_value_get(h->req->scgi, HTTP_HEADER_CONNECTION); int len; get_time(current_date, WR_STR_LEN); len = sprintf(str, HTTP_RESP_ERR_BODY, http_status[HTTP_STATUS_404].phrase, http_status[HTTP_STATUS_404].phrase + 4, http_status[HTTP_STATUS_404].message, WR_SERVER, WR_VERSION); wr_buffer_create(h->resp->resp_body, len); wr_buffer_add(h->resp->resp_body, str, len); len = sprintf(str, HTTP_RESP_ERR_HEADERS, current_date, http_status[HTTP_STATUS_404].phrase, WR_SERVER, WR_VERSION, (conn_header ? conn_header : CONNECTION_CLOSE), len); wr_string_new(h->resp->header, str, len); h->resp->resp_code = http_status[HTTP_STATUS_404].code; } void send_static_worker_pid(char *root_path) { LOG_FUNCTION node_t *root = NULL; char file_name[WR_LONG_STR_LEN]; // Read pid queue related configuration sprintf(file_name, "%s%s", root_path, WR_SERVER_INTERNAL_CONF_PATH); root = yaml_parse(file_name); if(root == NULL) { LOG_ERROR(SEVERE, "Could not parse server_internal_config.yml file. PID can not be sent to analyzer"); return; } else { char *host = NULL, *port = NULL, *queue_name = NULL; wr_msg_queue_server_t *msg_queue_server = NULL; wr_msg_queue_conn_t *msg_queue_conn = NULL; char msg_value[WR_SHORT_STR_LEN]; int rv; host = get_node_value(root, WR_MSG_QUEUE_SERVER_HOST); port = get_node_value(root, WR_MSG_QUEUE_SERVER_PORT); queue_name = get_node_value(root, WR_PID_MSG_QUEUE_NAME); if (!host || !port || !queue_name) { LOG_ERROR(SEVERE, "Error getting message queue configuration"); goto err; } msg_queue_server = wr_msg_queue_server_new(host, atoi(port)); if (!msg_queue_server) { LOG_ERROR(WARN, "Error initializing message queue server object"); goto err; } msg_queue_conn = wr_msg_queue_conn_new(msg_queue_server); if (!msg_queue_conn) { LOG_ERROR(WARN, "Error initializing message queue connection object"); goto err; } rv = wr_msg_queue_conn_open(msg_queue_conn); if (rv < 0) { LOG_ERROR(WARN, "Error establising connection with message queue server"); goto err; } rv = sprintf(msg_value, "%s:%d", WR_STATIC_FILE_SERVER_NAME, getpid()); rv = wr_msg_queue_set(msg_queue_conn, queue_name, msg_value, rv); if (rv < 0) { LOG_ERROR(SEVERE, "Failed to send PID to message queue"); } else { LOG_INFO("PID sent to queue successfully."); } err: node_free(root); wr_msg_queue_conn_free(msg_queue_conn); wr_msg_queue_server_free(msg_queue_server); return; } } /************************************** * Public Functions *************************************/ /* Initialize extension and mime-type map */ int static_module_init(char *root_path) { LOG_FUNCTION node_t *root; char file_name[100]; long int expires; sprintf(file_name, "%s%s", root_path, WR_CONF_PATH); root = yaml_parse(file_name); if (root == NULL) { LOG_ERROR(SEVERE, "Could not read config.yml file"); return -1; } expires = get_default_expires(root); sprintf(file_name, "%s%s", root_path, WR_MIME_TYPE_PATH); if (create_dictionary(file_name, expires) != 0) { node_free(root); return -1; } set_expires_by_type(root); node_free(root); send_static_worker_pid(root_path); return 0; } /* Free extension and mime-type map */ void static_module_free() { LOG_FUNCTION int i; static_file_t *ext, *next_ext; for (i = 0; i <= MAP_SIZE; i++) { ext = map[i]; while (ext) { next_ext = ext->next; free(ext); ext = next_ext; } } } /* Serve static file content */ void static_file_process(http_t *h) { LOG_FUNCTION wkr_t *w = h->wkr; short resp_code; struct stat buf; const char *path = scgi_header_value_get(h->req->scgi, WR_HTTP_FILE_PATH); LOG_DEBUG(DEBUG, "Path = %s", path); resp_code = get_resp_code(h, path, &buf); http_status[resp_code].fun(h, path, &buf); http_req_set(h->req); http_resp_process(h->resp); ev_io_init(&(w->w_req), http_resp_scgi_write_cb, w->req_fd, EV_WRITE); ev_io_start(w->loop, &w->w_req); }