/* 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
#ifdef W_ZLIB
#include
#endif
extern config_t *Config;
#define DEFAULT_EXPIRES "Headers/expires"
#define EXPIRES_BY_TYPE "Headers/expires_by_type/*"
#define EXPIRES_BY_TYPE_EXT "ext"
#define EXPIRES_BY_TYPE_EXPIRES "expires"
#define HTTP_HEADER_IF_MODIFIED_SINCE "HTTP_IF_MODIFIED_SINCE"
#define HTTP_HEADER_CONNECTION "HTTP_CONNECTION"
#define WR_NO_ENCODING 0
#define WR_DEFLATE_ENCODING 1
#define WR_GZIP_ENCODING 2
#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"
#define WR_WEBROAR_ANALYZER "webroar/analyzer"
/* from zutil.h */
#ifndef DEF_MEM_LEVEL
#if MAX_MEM_LEVEL >= 8
#define DEF_MEM_LEVEL 8
#else
#define DEF_MEM_LEVEL MAX_MEM_LEVEL
#endif
#endif
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*);
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*);
void http_resp_304(http_t*);
void http_resp_403(http_t*);
void http_resp_404(http_t*);
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\
"
/**************************************
* Private Functions
*************************************/
/** Searches path backward and returns pointer to the first character of extension */
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 */
void set_expires_time(static_server_t * s, char *ext, long int expires) {
int index;
char tmp_ext[STR_SIZE64], *p;
memcpy(tmp_ext, ext, strlen(ext)+1);
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 = s->map[index];
while (e) {
if (strcmp(e->ext, tmp_ext) == 0) {
//HTTP/1.1 servers SHOULD NOT send Expires dates more than one year in the future.
e->expires = expires;
break;
}
e = e->next;
}
}
}
/* Get mime-type */
static_file_t* get_mime_type(static_server_t *s) {
char *ext = get_file_ext(s->path);
char tmp_ext[STR_SIZE64], *p;
memcpy(tmp_ext, ext, strlen(ext)+1);
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 = s->map[index];
while (e) {
if (strcmp(e->ext, tmp_ext) == 0) {
return e;
}
e = e->next;
}
}
}
return s->map[MAP_SIZE];
}
/** Get response code */
short get_resp_code(static_server_t *s) {
LOG_FUNCTION
time_t modify_tm;
if (s->path == NULL) {
LOG_ERROR(WARN, "Requested file path is NULL.");
return HTTP_STATUS_404;
}
if (stat(s->path, &(s->buf))) {
LOG_ERROR(WARN, "Requested file %s does not exist.", s->path);
return HTTP_STATUS_404;
}
if (S_ISDIR(s->buf.st_mode) != 0) {
LOG_ERROR(WARN, "%s is a directory.", s->path)
return HTTP_STATUS_404;
}
if (strstr(s->path, "..")) {
LOG_ERROR(WARN, "Requested file path %s is forbidden.", s->path);
return HTTP_STATUS_403;
}
// Compare 'If-Modified-Since' time with file modication time
if (s->modify) {
// Assume 'If-Modified-Since' date zone is GMT
modify_tm = httpdate_to_c_time(s->modify) - timezone;
long int diff = difftime(s->buf.st_mtime, modify_tm);
if (diff <= 0) {
return HTTP_STATUS_304;
}
}
return HTTP_STATUS_200;
}
long int get_default_expires(node_t *root) {
char *node_value = yaml_get_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);
}
}
/** Read 'mime_type.yml' file and create dictionary for supported Content-Type. */
int create_dictionary(static_server_t *s, 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++) {
s->map[index] = NULL;
}
if (root == NULL) {
LOG_ERROR(SEVERE, "Could not read the file %s", mapping_file);
return -1;
}
node = yaml_get_node(root, "File Extensions");
if (root == NULL || node->child == NULL) {
LOG_ERROR(SEVERE, "Could not read 'File Extensions' from the file %s", mapping_file);
return -1;
}
node = node->child;
while (node) {
ext = wr_malloc(static_file_t);
memcpy(ext->ext, node->key, strlen(node->key)+1);
memcpy(ext->mime_type, node->value, strlen(node->value)+1);
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 = s->map[index];
s->map[index] = ext;
}else {
LOG_ERROR(WARN, "Mapping index out of bound for extension = %s", ext->ext);
free(ext);
}
node = node->next;
}
yaml_node_free(root);
// Set default mime type
ext = wr_malloc(static_file_t);
memcpy(ext->ext, "txt", strlen("txt")+1);
memcpy(ext->mime_type, "text/plain", strlen("text/plain")+1);
ext->expires = expires;
ext->next = NULL;
s->map[MAP_SIZE] = ext;
return 0;
}
void set_expires_by_type(static_server_t *s, node_t *root) {
node_t *node = yaml_get_node(root, EXPIRES_BY_TYPE);
long int expires;
char *types, *expires_str, *type;
while (node) {
types = yaml_get_value(node->child, EXPIRES_BY_TYPE_EXT);
expires_str = yaml_get_value(node->child, EXPIRES_BY_TYPE_EXPIRES);
expires = atol(expires_str);
type = strtok(types, " ,");
while (type != NULL) {
set_expires_time(s, type, expires);
type = strtok(NULL, " ,");
}
node = node->next;
}
}
#ifdef W_ZLIB
int deflate_file_compression(http_t *h) {
if(compress2((Bytef*)h->resp->resp_body->str, (uLongf*)&h->resp->resp_body->size,
(const Bytef*)h->stat->buffer->str, (uLongf)h->stat->buffer->len, Z_DEFAULT_COMPRESSION) != Z_OK){
wr_buffer_null(h->stat->buffer);
wr_buffer_null(h->resp->resp_body);
return FALSE;
} else {
h->resp->resp_body->len = h->resp->resp_body->size;
return TRUE;
}
}
int gzip_file_compression(http_t *h) {
z_stream c_stream;
int err;
c_stream.zalloc = NULL;
c_stream.zfree = NULL;
c_stream.opaque = NULL;
c_stream.total_out = 0;
err = deflateInit2(&c_stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
15+16, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY);
if (err != Z_OK) {
LOG_ERROR(WARN,"Deflate Init error");
return FALSE;
}
c_stream.next_in = (Bytef*) h->stat->buffer->str;
c_stream.avail_in = h->stat->buffer->len;
c_stream.next_out = (Bytef*) h->resp->resp_body->str;
c_stream.avail_out = h->resp->resp_body->size;
do {
c_stream.next_out = (Bytef*) (h->resp->resp_body->str + c_stream.total_out);
// Calculate the amount of remaining free space in the output buffer
// by subtracting the number of bytes that have been written so far
// from the buffer's total capacity
c_stream.avail_out = h->resp->resp_body->size - c_stream.total_out;
err = deflate(&c_stream, Z_FINISH);
} while (err == Z_OK);
if (err = Z_STREAM_END) {
err = deflateEnd(&c_stream);
if ( err != Z_OK) {
LOG_ERROR(WARN, "Deflate End error");
return FALSE;
} else {
h->resp->resp_body->len = c_stream.total_out;
return TRUE;
}
} else {
switch (err) {
case Z_ERRNO:
LOG_ERROR(WARN, "Error occured while reading file.");
break;
case Z_STREAM_ERROR:
LOG_ERROR(WARN, "The stream state was inconsistent (e.g., next_in or next_out was NULL).");
break;
case Z_DATA_ERROR:
LOG_ERROR(WARN, "The deflate data was invalid or incomplete.");
break;
case Z_MEM_ERROR:
LOG_ERROR(WARN, "Memory could not be allocated for processing.");
break;
case Z_BUF_ERROR:
LOG_ERROR(WARN, "Ran out of output buffer for writing compressed bytes.");
break;
case Z_VERSION_ERROR:
LOG_ERROR(WARN, "The version of zlib.h and the version of the library linked do not match.");
break;
default:
LOG_ERROR(WARN, "Unknown error code.");
break;
}
return FALSE;
}
}
/** Compress file */
/* Compress file if its size is >10kb and < 1mb and its mime-type has either
* text or xml */
int file_compress(http_t *h, static_file_t *ext) {
LOG_FUNCTION
int rv=0;
char *cache_control=NULL;
cache_control = scgi_header_value_get(h->req->scgi, "Cache-Control");
if(cache_control && strstr(cache_control, "no-transform")) {
return FALSE;
}
h->stat->encoding = scgi_header_value_get(h->req->scgi, "HTTP_ACCEPT_ENCODING");
h->stat->user_agent = scgi_header_value_get(h->req->scgi, "HTTP_USER_AGENT");
/* Skip compressing entity body if user agent is IE6
refer http://schroepl.net/projekte/mod_gzip/browser.htm and
http://support.microsoft.com/default.aspx?scid=kb;en-us;Q313712
for problem details
*/
if(h->stat->user_agent && strstr(h->stat->user_agent, "MSIE 6.0")) {
return FALSE;
}
if(h->stat->buf.st_size >= Config->Worker.Compress.lower_limit
&& h->stat->buf.st_size <= Config->Worker.Compress.upper_limit
&& h->stat->encoding) {
//TODO: look into Cache-Control request header and q value of Accept-Encoding request header
// to decide correct encoding
if (strstr(h->stat->encoding, "gzip")) {
h->resp->content_encoding = WR_GZIP_ENCODING;
}
else if (strstr(h->stat->encoding, "deflate")) {
h->resp->content_encoding = WR_DEFLATE_ENCODING;
}
else {
h->resp->content_encoding = WR_NO_ENCODING;
return FALSE;
}
#ifdef W_REGEX
if(h->stat->r_content_type){
if(regexec(h->stat->r_content_type, ext->mime_type, 0, NULL, 0) !=0 ) return FALSE;
}
if(h->stat->r_user_agent && h->stat->user_agent){
if(regexec(h->stat->r_user_agent, h->stat->user_agent, 0, NULL, 0) !=0 ) return FALSE;
}
#else
// Encode assets having Content-Type either 'text' or 'xml'.
if(strstr(ext->mime_type, "text") == NULL && strstr(ext->mime_type, "xml") == NULL
&& strstr(ext->mime_type, "css") == NULL && strstr(ext->mime_type, "javascript") == NULL){
return FALSE;
}
#endif
wr_u_int read;
FILE *file;
wr_buffer_create(h->stat->buffer, h->stat->buf.st_size);
if(h->stat->buffer->str == NULL) {
return FALSE;
}
file = fopen(h->stat->path, "r");
if(file == NULL) return FALSE;
while(h->stat->buffer->len < h->stat->buf.st_size){
read = fread(h->stat->buffer->str + h->stat->buffer->len, sizeof(char),
h->stat->buf.st_size - h->stat->buffer->len, file);
if(read < 0){
fclose(file);
wr_buffer_null(h->stat->buffer);
return FALSE;
}
h->stat->buffer->len += read;
}
fclose(file);
//zlib states that the source buffer must be at least 0.1 times larger than
//the source buffer plus 12 bytes to cope with the overhead of zlib data streams
wr_buffer_create(h->resp->resp_body, h->stat->buf.st_size * 1.01 + 12);
switch(h->resp->content_encoding) {
case WR_GZIP_ENCODING:
rv = gzip_file_compression(h);
LOG_DEBUG(DEBUG, "Done gzip encoding rv = %d", rv);
break;
case WR_DEFLATE_ENCODING:
rv = deflate_file_compression(h);
LOG_DEBUG(DEBUG, "Done deflate encoding rv = %d", rv);
break;
}
wr_buffer_null(h->stat->buffer);
if(rv == FALSE) {
wr_buffer_null(h->resp->resp_body);
}
return rv;
}
return FALSE;
}
#endif
void http_resp_200(http_t *h) {
LOG_FUNCTION
char str[STR_SIZE512], expire_date[STR_SIZE64] = "", current_date[STR_SIZE64] = "", modify_date[STR_SIZE64] = "";
int len;
int ret_val = FALSE;
time_t t;
static_file_t *ext = get_mime_type(h->stat);
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, STR_SIZE64);
time_to_httpdate(h->stat->buf.st_mtime, modify_date, STR_SIZE64);
#ifdef W_ZLIB
ret_val = file_compress(h, ext);
if(ret_val == FALSE){
#endif
h->resp->resp_body->len = h->stat->buf.st_size;
#ifdef _POSIX_C_SOURCE
h->resp->file = open(h->stat->path, O_RDONLY);
#else
h->resp->file = fopen(h->stat->path, "r");
#endif
#ifdef W_ZLIB
}
#endif
if (ext->expires > 0) {
t += ext->expires;
time_to_httpdate(t, expire_date, STR_SIZE64);
len = sprintf(str, "HTTP/1.1 200 OK\r\nDate: %s\r\nServer: %s-%s\r\nLast-Modified: %s\r\nExpires: %s\r\nConnection: %s\r\n%sContent-Type: %s\r\nContent-Length: %d\r\n\r\n",
current_date, Config->Worker.Server.name.str, Config->Worker.Server.version.str, modify_date, expire_date,
(conn_header ? conn_header : CONNECTION_CLOSE),
(ret_val == TRUE ? (h->resp->content_encoding == WR_GZIP_ENCODING ? "Content-Encoding: gzip\r\n" : "Content-Encoding: deflate\r\n") : ""),
ext->mime_type, h->resp->resp_body->len);
} else {
len = sprintf(str, "HTTP/1.1 200 OK\r\nDate: %s\r\nServer: %s-%s\r\nLast-Modified: %s\r\nConnection: %s\r\n%sContent-Type: %s\r\nContent-Length: %d\r\n\r\n",
current_date, Config->Worker.Server.name.str, Config->Worker.Server.version.str, modify_date,
(conn_header ? conn_header : CONNECTION_CLOSE),
(ret_val == TRUE ? (h->resp->content_encoding == WR_GZIP_ENCODING ? "Content-Encoding: gzip\r\n" : "Content-Encoding: deflate\r\n") : ""),
ext->mime_type, h->resp->resp_body->len);
}
wr_string_new(h->resp->header, str, len);
h->resp->resp_code = http_status[HTTP_STATUS_200].code;
}
void http_resp_304(http_t *h) {
LOG_FUNCTION
char str[STR_SIZE512], expire_date[STR_SIZE64] = "", current_date[STR_SIZE64] = "";
int len;
time_t t;
static_file_t *ext = get_mime_type(h->stat);
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, STR_SIZE64);
if (ext->expires > 0) {
t = t + ext->expires;
time_to_httpdate(t, expire_date, STR_SIZE64);
len = sprintf(str, "HTTP/1.1 304 Not Modified\r\nDate: %s\r\nServer: %s-%s\r\nExpires: %s\r\nConnection: %s\r\n\r\n",
current_date, Config->Worker.Server.name.str, Config->Worker.Server.version.str, expire_date,
(conn_header ? conn_header : CONNECTION_CLOSE));
}else {
len = sprintf(str, "HTTP/1.1 304 Not Modified\r\nDate: %s\r\nServer: %s-%s\r\nConnection: %s\r\n\r\n",
current_date, Config->Worker.Server.name.str, Config->Worker.Server.version.str, (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) {
LOG_FUNCTION
char str[STR_SIZE512], current_date[STR_SIZE64];
const char *conn_header = scgi_header_value_get(h->req->scgi, HTTP_HEADER_CONNECTION);
int len;
get_time(current_date, STR_SIZE64);
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, Config->Worker.Server.name.str, Config->Worker.Server.version.str);
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, Config->Worker.Server.name.str, Config->Worker.Server.version.str,
(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) {
LOG_FUNCTION
char str[STR_SIZE512], current_date[STR_SIZE64];
const char *conn_header = scgi_header_value_get(h->req->scgi, HTTP_HEADER_CONNECTION);
int len;
get_time(current_date, STR_SIZE64);
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, Config->Worker.Server.name.str, Config->Worker.Server.version.str);
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, Config->Worker.Server.name.str, Config->Worker.Server.version.str,
(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() {
LOG_FUNCTION
node_t *root = NULL;
// Read pid queue related configuration
root = yaml_parse(Config->Worker.File.internal_config.str);
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, *analytics = NULL;
wr_msg_queue_server_t *msg_queue_server = NULL;
wr_msg_queue_conn_t *msg_queue_conn = NULL;
char msg_value[STR_SIZE32];
int rv;
analytics = yaml_get_value(root, WR_WEBROAR_ANALYZER);
if(analytics && strcmp(analytics, "on")) return;
host = yaml_get_value(root, WR_MSG_QUEUE_SERVER_HOST);
port = yaml_get_value(root, WR_MSG_QUEUE_SERVER_PORT);
queue_name = yaml_get_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", Config->Worker.static_server.str, 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:
yaml_node_free(root);
wr_msg_queue_conn_free(msg_queue_conn);
wr_msg_queue_server_free(msg_queue_server);
return;
}
}
/* Initialize extension and mime-type map */
int static_module_init(static_server_t *s) {
LOG_FUNCTION
node_t *root;
long int expires;
root = yaml_parse(Config->Worker.File.config.str);
if (root == NULL) {
LOG_ERROR(SEVERE, "Could not read config.yml file");
return FALSE;
}
expires = get_default_expires(root);
if (create_dictionary(s, Config->Worker.File.mime_type.str, expires) != 0) {
yaml_node_free(root);
return FALSE;
}
set_expires_by_type(s, root);
yaml_node_free(root);
send_static_worker_pid();
return TRUE;
}
/* Free extension and mime-type map */
void static_module_free(static_server_t *s) {
LOG_FUNCTION
int i;
static_file_t *ext, *next_ext;
for (i = 0; i <= MAP_SIZE; i++) {
ext = s->map[i];
while (ext) {
next_ext = ext->next;
free(ext);
ext = next_ext;
}
}
}
/**************************************
* Public Functions
*************************************/
/** Create new static server */
static_server_t * static_server_new(void* ptr){
LOG_FUNCTION
wkr_t* w = (wkr_t*)ptr;
static_server_t *s = wr_malloc(static_server_t);
if(s == NULL) return NULL;
#ifdef W_ZLIB
if(w->tmp->lower_limit > 0)
Config->Worker.Compress.lower_limit = w->tmp->lower_limit;
if(w->tmp->upper_limit > 0)
Config->Worker.Compress.upper_limit = w->tmp->upper_limit;
#ifdef W_REGEX
s->r_content_type = NULL;
if(!wr_string_is_empty(w->tmp->r_content_type)){
int err_no;
s->r_content_type = wr_malloc(regex_t);
// #define REG_EXTENDED 1
if((err_no = regcomp(s->r_content_type, w->tmp->r_content_type.str, 1))!=0){ /* Compile the regex */
size_t length;
char *buffer;
length = regerror (err_no, s->r_content_type, NULL, 0);
buffer = malloc(length);
regerror (err_no, s->r_content_type, buffer, length);
LOG_ERROR(SEVERE, "%s", buffer); /* Print the error */
LOG_ERROR(SEVERE, "Now Static Workers allow encoding for only 'text' and 'xml' Content-Type.");
free(buffer);
regfree(s->r_content_type);
if((err_no = regcomp(s->r_content_type, "javascript|css|text|xml", 1))!=0){
regfree(s->r_content_type);
free(s->r_content_type);
s->r_content_type = NULL;
}
}
}
s->r_user_agent = NULL;
// Do not apply validation on User-Agent if it is not given or it is '.*'(allow all).
if(!wr_string_is_empty(w->tmp->r_user_agent) && strcmp(w->tmp->r_user_agent.str,".*") != 0){
int err_no;
s->r_user_agent = wr_malloc(regex_t);
if((err_no = regcomp(s->r_user_agent, w->tmp->r_content_type.str, 1))!=0){ /* Compile the regex */
size_t length;
char *buffer;
length = regerror (err_no, s->r_user_agent, NULL, 0);
buffer = malloc(length);
regerror (err_no, s->r_user_agent, buffer, length);
LOG_ERROR(SEVERE, "%s", buffer); /* Print the error */
LOG_ERROR(SEVERE, "Now Static Workers serves encoded assets to all User-Agent.");
free(buffer);
regfree(s->r_user_agent);
free(s->r_user_agent);
s->r_user_agent = NULL;
}
}
#endif
#endif
if(static_module_init(s) == FALSE){
free(s);
return NULL;
}
wr_buffer_new(s->buffer);
return s;
}
/** Delete static server */
void static_server_free(static_server_t *s){
if(s){
if(s->buffer) wr_buffer_free(s->buffer);
static_module_free(s);
#if defined(W_ZLIB) && defined(W_REGEX)
if(s->r_user_agent){
regfree(s->r_user_agent);
free(s->r_user_agent);
}
if(s->r_content_type){
regfree(s->r_content_type);
free(s->r_content_type);
}
#endif
free(s);
}
}
/* Serve static file content */
void static_file_process(void *http) {
LOG_FUNCTION
http_t *h = (http_t*) http;
wkr_t *w = h->wkr;
short resp_code;
h->stat->path = scgi_header_value_get(h->req->scgi, Config->Worker.Header.file_path.str);
h->stat->modify = scgi_header_value_get(h->req->scgi, HTTP_HEADER_IF_MODIFIED_SINCE);
LOG_DEBUG(DEBUG, "Path = %s", h->stat->path);
resp_code = get_resp_code(h->stat);
http_status[resp_code].fun(h);
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);
}