Server is basically working.

Needs a re-design.
This commit is contained in:
2014-08-03 16:18:18 +01:00
parent e8d1e4d7d9
commit 34ef45478e
19 changed files with 671 additions and 265 deletions

View File

@@ -62,6 +62,7 @@ build: .build-post
# clean
clean: .clean-post
-rm --recursive ${CND_ARTIFACT_DIR_${CONF}}/
.clean-pre:
# Add your pre 'clean' code here...

24
content/dirindex.html Normal file
View File

@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<head>
<title>{{dirname}}</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<h1>{{dirname}}</h1>
<table>
<thead>
<tr>
<td>Name</td>
<td>Filesize</td>
<td>Last modified</td>
</tr>
</thead>
<tbody>
{{index}}
</tbody>
</table>
</body>
</html>

View File

@@ -43,11 +43,12 @@ OBJECTFILES= \
${OBJECTDIR}/src/http.o \
${OBJECTDIR}/src/main.o \
${OBJECTDIR}/src/mime.o \
${OBJECTDIR}/src/socket.o
${OBJECTDIR}/src/socket.o \
${OBJECTDIR}/src/util.o
# C Compiler Flags
CFLAGS=-O0
CFLAGS=-m64 -O0 -march=native
# CC Compiler Flags
CCFLAGS=
@@ -115,6 +116,11 @@ ${OBJECTDIR}/src/socket.o: nbproject/Makefile-${CND_CONF}.mk src/socket.c
${RM} "$@.d"
$(COMPILE.c) -g -Werror -DINI_ALLOW_BOM=0 -DINI_ALLOW_MULTILINE=0 -D_GNU_SOURCE -Ilib -std=c99 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/socket.o src/socket.c
${OBJECTDIR}/src/util.o: nbproject/Makefile-${CND_CONF}.mk src/util.c
${MKDIR} -p ${OBJECTDIR}/src
${RM} "$@.d"
$(COMPILE.c) -g -Werror -DINI_ALLOW_BOM=0 -DINI_ALLOW_MULTILINE=0 -D_GNU_SOURCE -Ilib -std=c99 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/util.o src/util.c
# Subprojects
.build-subprojects:

View File

@@ -43,11 +43,12 @@ OBJECTFILES= \
${OBJECTDIR}/src/http.o \
${OBJECTDIR}/src/main.o \
${OBJECTDIR}/src/mime.o \
${OBJECTDIR}/src/socket.o
${OBJECTDIR}/src/socket.o \
${OBJECTDIR}/src/util.o
# C Compiler Flags
CFLAGS=
CFLAGS=-m64 -march=native
# CC Compiler Flags
CCFLAGS=
@@ -60,7 +61,7 @@ FFLAGS=
ASFLAGS=
# Link Libraries and Options
LDLIBSOPTIONS=
LDLIBSOPTIONS=-lmagic
# Build Targets
.build-conf: ${BUILD_SUBPROJECTS}
@@ -70,50 +71,55 @@ ${CND_DISTDIR}/${CND_CONF}/${CND_PLATFORM}/khttp: ${OBJECTFILES}
${MKDIR} -p ${CND_DISTDIR}/${CND_CONF}/${CND_PLATFORM}
${LINK.c} -o ${CND_DISTDIR}/${CND_CONF}/${CND_PLATFORM}/khttp ${OBJECTFILES} ${LDLIBSOPTIONS}
${OBJECTDIR}/lib/http_parser.o: lib/http_parser.c
${OBJECTDIR}/lib/http_parser.o: nbproject/Makefile-${CND_CONF}.mk lib/http_parser.c
${MKDIR} -p ${OBJECTDIR}/lib
${RM} "$@.d"
$(COMPILE.c) -O2 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/lib/http_parser.o lib/http_parser.c
$(COMPILE.c) -O2 -Werror -DINI_ALLOW_BOM=0 -DINI_ALLOW_MULTILINE=0 -D_GNU_SOURCE -Ilib -std=c99 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/lib/http_parser.o lib/http_parser.c
${OBJECTDIR}/lib/ini.o: lib/ini.c
${OBJECTDIR}/lib/ini.o: nbproject/Makefile-${CND_CONF}.mk lib/ini.c
${MKDIR} -p ${OBJECTDIR}/lib
${RM} "$@.d"
$(COMPILE.c) -O2 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/lib/ini.o lib/ini.c
$(COMPILE.c) -O2 -Werror -DINI_ALLOW_BOM=0 -DINI_ALLOW_MULTILINE=0 -D_GNU_SOURCE -Ilib -std=c99 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/lib/ini.o lib/ini.c
${OBJECTDIR}/src/config.o: src/config.c
${OBJECTDIR}/src/config.o: nbproject/Makefile-${CND_CONF}.mk src/config.c
${MKDIR} -p ${OBJECTDIR}/src
${RM} "$@.d"
$(COMPILE.c) -O2 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/config.o src/config.c
$(COMPILE.c) -O2 -Werror -DINI_ALLOW_BOM=0 -DINI_ALLOW_MULTILINE=0 -D_GNU_SOURCE -Ilib -std=c99 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/config.o src/config.c
${OBJECTDIR}/src/http-reader.o: src/http-reader.c
${OBJECTDIR}/src/http-reader.o: nbproject/Makefile-${CND_CONF}.mk src/http-reader.c
${MKDIR} -p ${OBJECTDIR}/src
${RM} "$@.d"
$(COMPILE.c) -O2 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/http-reader.o src/http-reader.c
$(COMPILE.c) -O2 -Werror -DINI_ALLOW_BOM=0 -DINI_ALLOW_MULTILINE=0 -D_GNU_SOURCE -Ilib -std=c99 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/http-reader.o src/http-reader.c
${OBJECTDIR}/src/http-server.o: src/http-server.c
${OBJECTDIR}/src/http-server.o: nbproject/Makefile-${CND_CONF}.mk src/http-server.c
${MKDIR} -p ${OBJECTDIR}/src
${RM} "$@.d"
$(COMPILE.c) -O2 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/http-server.o src/http-server.c
$(COMPILE.c) -O2 -Werror -DINI_ALLOW_BOM=0 -DINI_ALLOW_MULTILINE=0 -D_GNU_SOURCE -Ilib -std=c99 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/http-server.o src/http-server.c
${OBJECTDIR}/src/http.o: src/http.c
${OBJECTDIR}/src/http.o: nbproject/Makefile-${CND_CONF}.mk src/http.c
${MKDIR} -p ${OBJECTDIR}/src
${RM} "$@.d"
$(COMPILE.c) -O2 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/http.o src/http.c
$(COMPILE.c) -O2 -Werror -DINI_ALLOW_BOM=0 -DINI_ALLOW_MULTILINE=0 -D_GNU_SOURCE -Ilib -std=c99 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/http.o src/http.c
${OBJECTDIR}/src/main.o: src/main.c
${OBJECTDIR}/src/main.o: nbproject/Makefile-${CND_CONF}.mk src/main.c
${MKDIR} -p ${OBJECTDIR}/src
${RM} "$@.d"
$(COMPILE.c) -O2 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/main.o src/main.c
$(COMPILE.c) -O2 -Werror -DINI_ALLOW_BOM=0 -DINI_ALLOW_MULTILINE=0 -D_GNU_SOURCE -Ilib -std=c99 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/main.o src/main.c
${OBJECTDIR}/src/mime.o: src/mime.c
${OBJECTDIR}/src/mime.o: nbproject/Makefile-${CND_CONF}.mk src/mime.c
${MKDIR} -p ${OBJECTDIR}/src
${RM} "$@.d"
$(COMPILE.c) -O2 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/mime.o src/mime.c
$(COMPILE.c) -O2 -Werror -DINI_ALLOW_BOM=0 -DINI_ALLOW_MULTILINE=0 -D_GNU_SOURCE -Ilib -std=c99 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/mime.o src/mime.c
${OBJECTDIR}/src/socket.o: src/socket.c
${OBJECTDIR}/src/socket.o: nbproject/Makefile-${CND_CONF}.mk src/socket.c
${MKDIR} -p ${OBJECTDIR}/src
${RM} "$@.d"
$(COMPILE.c) -O2 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/socket.o src/socket.c
$(COMPILE.c) -O2 -Werror -DINI_ALLOW_BOM=0 -DINI_ALLOW_MULTILINE=0 -D_GNU_SOURCE -Ilib -std=c99 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/socket.o src/socket.c
${OBJECTDIR}/src/util.o: nbproject/Makefile-${CND_CONF}.mk src/util.c
${MKDIR} -p ${OBJECTDIR}/src
${RM} "$@.d"
$(COMPILE.c) -O2 -Werror -DINI_ALLOW_BOM=0 -DINI_ALLOW_MULTILINE=0 -D_GNU_SOURCE -Ilib -std=c99 -MMD -MP -MF "$@.d" -o ${OBJECTDIR}/src/util.o src/util.c
# Subprojects
.build-subprojects:

View File

@@ -13,6 +13,7 @@
<itemPath>src/main.h</itemPath>
<itemPath>src/mime.h</itemPath>
<itemPath>src/socket.h</itemPath>
<itemPath>src/util.h</itemPath>
</logicalFolder>
<logicalFolder name="ResourceFiles"
displayName="Resource Files"
@@ -30,6 +31,7 @@
<itemPath>src/main.c</itemPath>
<itemPath>src/mime.c</itemPath>
<itemPath>src/socket.c</itemPath>
<itemPath>src/util.c</itemPath>
</logicalFolder>
<logicalFolder name="TestFiles"
displayName="Test Files"
@@ -57,11 +59,12 @@
</toolsSet>
<compileType>
<cTool>
<architecture>2</architecture>
<standard>3</standard>
<incDir>
<pElem>lib</pElem>
</incDir>
<commandLine>-O0</commandLine>
<commandLine>-O0 -march=native</commandLine>
<preprocessorList>
<Elem>INI_ALLOW_BOM=0</Elem>
<Elem>INI_ALLOW_MULTILINE=0</Elem>
@@ -119,26 +122,38 @@
</item>
<item path="src/socket.h" ex="false" tool="3" flavor2="0">
</item>
<item path="src/util.c" ex="false" tool="0" flavor2="0">
</item>
<item path="src/util.h" ex="false" tool="3" flavor2="0">
</item>
</conf>
<conf name="Release" type="1">
<toolsSet>
<compilerSet>default</compilerSet>
<dependencyChecking>true</dependencyChecking>
<rebuildPropChanged>false</rebuildPropChanged>
<rebuildPropChanged>true</rebuildPropChanged>
</toolsSet>
<compileType>
<cTool>
<developmentMode>5</developmentMode>
<architecture>2</architecture>
<standard>3</standard>
<incDir>
<pElem>lib</pElem>
</incDir>
<commandLine>-march=native</commandLine>
<preprocessorList>
<Elem>INI_ALLOW_BOM=0</Elem>
<Elem>INI_ALLOW_MULTILINE=0</Elem>
<Elem>_GNU_SOURCE</Elem>
</preprocessorList>
<warningLevel>3</warningLevel>
</cTool>
<ccTool>
<developmentMode>5</developmentMode>
</ccTool>
<fortranCompilerTool>
<developmentMode>5</developmentMode>
</fortranCompilerTool>
<asmTool>
<developmentMode>5</developmentMode>
</asmTool>
<linkerTool>
<linkerLibItems>
<linkerLibLibItem>magic</linkerLibLibItem>
</linkerLibItems>
</linkerTool>
</compileType>
<item path="content/error.html" ex="false" tool="3" flavor2="0">
</item>
@@ -184,6 +199,10 @@
</item>
<item path="src/socket.h" ex="false" tool="3" flavor2="0">
</item>
<item path="src/util.c" ex="false" tool="0" flavor2="0">
</item>
<item path="src/util.h" ex="false" tool="3" flavor2="0">
</item>
</conf>
</confs>
</configurationDescriptor>

View File

@@ -62,7 +62,7 @@
<runcommandpicklistitem>"${OUTPUT_PATH}"</runcommandpicklistitem>
</runcommandpicklist>
<runcommand>"${OUTPUT_PATH}"</runcommand>
<rundir></rundir>
<rundir>/home/sam/NetBeansProjects/KHttp/./dist/Debug/GNU-Linux-x86</rundir>
<buildfirst>true</buildfirst>
<terminal-type>0</terminal-type>
<remove-instrumentation>0</remove-instrumentation>

View File

@@ -8,19 +8,19 @@
<open-files xmlns="http://www.netbeans.org/ns/projectui-open-files/2">
<group>
<file>file:/home/sam/NetBeansProjects/KHttp/src/socket.c</file>
<file>file:/home/sam/NetBeansProjects/KHttp/content/khttpd.ini</file>
<file>file:/home/sam/NetBeansProjects/KHttp/src/config.c</file>
<file>file:/home/sam/NetBeansProjects/KHttp/content/dirindex.html</file>
<file>file:/home/sam/NetBeansProjects/KHttp/src/http-server.h</file>
<file>file:/home/sam/NetBeansProjects/KHttp/src/http.c</file>
<file>file:/home/sam/NetBeansProjects/KHttp/src/http-reader.h</file>
<file>file:/home/sam/NetBeansProjects/KHttp/lib/ut/utstring.h</file>
<file>file:/home/sam/NetBeansProjects/KHttp/src/main.h</file>
<file>file:/home/sam/NetBeansProjects/KHttp/lib/http_parser.h</file>
<file>file:/home/sam/NetBeansProjects/KHttp/Makefile</file>
<file>file:/home/sam/NetBeansProjects/KHttp/content/error.html</file>
<file>file:/home/sam/NetBeansProjects/KHttp/src/main.c</file>
<file>file:/home/sam/NetBeansProjects/KHttp/src/socket.h</file>
<file>file:/home/sam/NetBeansProjects/KHttp/src/http-server.c</file>
<file>file:/home/sam/NetBeansProjects/KHttp/lib/ini.c</file>
<file>file:/home/sam/NetBeansProjects/KHttp/src/http.h</file>
<file>file:/home/sam/NetBeansProjects/KHttp/src/http-reader.c</file>
<file>file:/home/sam/NetBeansProjects/KHttp/src/config.h</file>
</group>
</open-files>

View File

@@ -20,7 +20,7 @@ config_server* config_server_new() {
config->servername = calloc(128, sizeof(char));
if (gethostname(config->servername, 128) < 0) {
warning("failed to get server hostname", true);
warning(true, "failed to get server hostname");
free(config->servername);
config->servername = strdup(default_servername);
}
@@ -69,12 +69,20 @@ config_host* config_host_new() {
host->enabled = true;
host->hostname = NULL;
host->serve_dir = NULL;
host->dir_listings = true;
host->index_files = calloc(1, sizeof(char*));
host->index_files[0] = strdup("index.html");
host->index_files_count = 1;
return host;
}
void config_host_delete(config_host *host) {
if (host->hostname != NULL) free(host->hostname);
if (host->serve_dir != NULL) free(host->serve_dir);
for(int i=0; i<host->index_files_count; i++) {
free(host->index_files[i]);
}
free(host->index_files);
free(host);
}
@@ -92,18 +100,21 @@ static int config_read_ini_cb(void* _config, const char* section, const char* na
return -1;
} else if (name != NULL) {
if (MATCH("Server", "name")) {
config->servername = strdup(value);
config->servername = realloc(config->servername, strlen(value)+1);
strcpy(config->servername, value);
} else if (MATCH("Server", "admin")) {
config->administrator = strdup(value);
config->administrator = realloc(config->administrator, strlen(value)+1);
strcpy(config->administrator, value);
} else if (MATCH("Server", "listen")) {
errno = 0;
config->listen_port = (uint16_t)strtol(value, NULL, 10);
if (errno != 0) {
warning("Config: Invalid port number for [Server]listen", true);
warning(true, "Config: Invalid port number for [Server]listen");
}
return -1;
} else if (MATCH("Host", "name")) {
host->hostname = strdup(value);
host->hostname = realloc(host->hostname, strlen(value)+1);
strcpy(host->hostname, value);
} else if (MATCH("Host", "enabled")) {
if (strcasecmp(value, "yes") == 0) {
host->enabled = true;
@@ -125,17 +136,43 @@ static int config_read_ini_cb(void* _config, const char* section, const char* na
} else if (MATCH("Host", "serve")) {
char* serve_dir = realpath(value, NULL);
if (serve_dir == NULL) {
warning("Config: host serve directory is invalid", true);
warning(true, "Config: host serve directory is invalid");
return -1;
}
DIR* dir = opendir(serve_dir);
if (dir == NULL) {
free(serve_dir);
warning("Config: host serve directory is invalid", true);
warning(true, "Config: host serve directory is invalid");
return -1;
}
closedir(dir);
if (host->serve_dir != NULL) free(host->serve_dir);
host->serve_dir = serve_dir;
} else if (MATCH("Host", "index_files")) {
for(int i=0;i<host->index_files_count;i++) {
free(host->index_files[i]);
}
free(host->index_files);
host->index_files = NULL;
host->index_files_count = 0;
char* savepos=NULL;
char* value_cpy = strdup(value);
char* file = strtok_r(value_cpy, ",", &savepos);
while(file != NULL) {
host->index_files = realloc(host->index_files, sizeof(char*)*(host->index_files_count+1));
host->index_files[host->index_files_count++] = strdup(str_trimwhitespace(file));
file = strtok_r(NULL, ",", &savepos);
}
free(value_cpy);
} else if (MATCH("Host", "dir_listings")) {
if (strcasecmp(value, "yes") == 0) {
host->dir_listings = true;
} else if (strcasecmp(value, "no") == 0) {
host->dir_listings = false;
} else {
warning(false, "Unexpected value for [Host]dir_listings");
}
}
}
#undef MATCH

View File

@@ -23,6 +23,9 @@ extern "C" {
bool default_host;
bool enabled;
char *serve_dir;
char**index_files;
size_t index_files_count;
bool dir_listings;
} config_host;
typedef struct config_server {

View File

@@ -5,6 +5,9 @@
#include <magic.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <time.h>
#include "http_parser.h"
#include "http.h"
@@ -60,6 +63,7 @@ http_response* server_process_request(config_server* config, http_request *reque
if (http_parser_parse_url(request->req->uri, strlen(request->req->uri), false, url) < 0) {
response = http_response_create_builtin(400, "Invalid request");
http_header_list_add(response->headers, http_header_new(HEADER_CONNECTION, "close"), false);
free(url);
return response;
}
@@ -72,52 +76,52 @@ http_response* server_process_request(config_server* config, http_request *reque
//Not found (for some reason)
response = http_response_create_builtin(400, "URI is required or was invalid");
http_header_list_add(response->headers, http_header_new(HEADER_CONNECTION, "close"), false);
free(url);
return response;
}
free(url);
//Build actual path to file requested
char* filepath_requested = NULL;
size_t totallen = strlen(host_config->serve_dir) + strlen(uri) + 2;
filepath_requested = calloc(totallen, sizeof(char));
strcat(filepath_requested, host_config->serve_dir);
strcat(filepath_requested, "/");
strcat(filepath_requested, uri);
char* filepath = NULL;
size_t totallen = strlen(host_config->serve_dir) + strlen(uri) + 1;
filepath = calloc(totallen, sizeof(char));
strcat(filepath, host_config->serve_dir);
strcat(filepath, uri);
free(uri);
//Check that the file exists
char* filepath_actual = realpath(filepath_requested, NULL);
if (filepath_actual == NULL) {
warning("realpath: not found/error", true);
response = http_response_create_builtin(404, "File not found");
http_header_list_add(response->headers, http_header_new(HEADER_CONNECTION, "close"), false);
return response;
}
//Check that the file is within the server directory of the host
if (strncmp(host_config->serve_dir, filepath_actual, strlen(host_config->serve_dir)) != 0) {
//file is outside the server directory :S
response = http_response_create_builtin(400, "URI is required or was invalid");
http_header_list_add(response->headers, http_header_new(HEADER_CONNECTION, "close"), false);
return response;
}
server_file_result *file_result = server_determine_file(host_config, filepath);
free(filepath);
filepath=NULL;
struct stat *pathstat = calloc(1, sizeof(struct stat));
if (stat(filepath_actual, pathstat) < 0) {
warning("stat failed", true);
response = http_response_create_builtin(400, "URI is required or was invalid");
if (file_result->error != false) {
response = http_response_create_builtin(file_result->error_code, file_result->error_text);
http_header_list_add(response->headers, http_header_new(HEADER_CONNECTION, "close"), false);
server_file_result_delete(file_result);
return response;
}
//Is is a directory?
if (S_ISDIR(pathstat->st_mode) != 0) {
response = http_response_create_builtin(200, "Directory listing not implemented");
return response;
FILE *file = NULL;
if (file_result->dir == true) {
file = server_generate_directory_index(host_config, file_result->path);
if (file == NULL) {
response = http_response_create_builtin(500, "Failed to generate directory index");
http_header_list_add(response->headers, http_header_new(HEADER_CONNECTION, "close"), false);
server_file_result_delete(file_result);
return response;
}
} else {
filepath = strdup(file_result->path);
}
server_file_result_delete(file_result);
//Open file
FILE *file = fopen(filepath_actual, "r");
if (file == NULL) {
warning("failed to open file for reading", true);
file = fopen(filepath, "rb");
}
if (file == NULL) {
warning(true, "failed to open file for reading");
response = http_response_create_builtin(404, "File not found");
http_header_list_add(response->headers, http_header_new(HEADER_CONNECTION, "close"), false);
free(filepath);
return response;
}
@@ -128,20 +132,23 @@ http_response* server_process_request(config_server* config, http_request *reque
//Read file into response
//TODO: send file directly from the write loop
char* buffer = calloc(filesize, sizeof(char));
char* buffer = calloc(filesize+1, sizeof(char));
if (fread(buffer, sizeof(char), filesize, file) != filesize) {
warning("failed to read file into memory", true);
warning(true, "failed to read file into memory");
response = http_response_create_builtin(500, "Could not read file");
} else {
response = http_response_new(http_response_line_new(200));
response->resp->version = request->req->version;
http_header_list_add(response->headers, http_header_new(HEADER_CONTENT_TYPE, mime_get_type(filepath_actual, DEFAULT_CONTENT_TYPE)), false);
const char* mime_type = "text/html";
if (filepath != NULL) {
mime_type = mime_get_type(filepath, DEFAULT_CONTENT_TYPE);
}
http_header_list_add(response->headers, http_header_new(HEADER_CONTENT_TYPE, mime_type), false);
http_response_append_body(response, buffer);
}
fclose(file);
free(buffer);
free(filepath_requested);
free(filepath_actual);
free(filepath);
//Check to see if client requested the connection be closed
http_header* request_connection = http_header_list_get(request->headers, HEADER_CONNECTION);
@@ -151,3 +158,186 @@ http_response* server_process_request(config_server* config, http_request *reque
return response;
}
server_file_result* server_determine_file(config_host* hconfig, const char* requested_path) {
server_file_result *result = server_file_result_new();
//Check that the file exists
char* filepath_actual = realpath(requested_path, NULL);
if (filepath_actual == NULL) {
server_file_result_seterror(result, 404, "File not found");
return result;
}
//Check that the file is within the server directory of the host
if (strncmp(hconfig->serve_dir, filepath_actual, strlen(hconfig->serve_dir)) != 0) {
//file is outside the server directory :S
server_file_result_seterror(result, 404, "File not found");
free(filepath_actual);
return result;
}
struct stat *pathstat = calloc(1, sizeof(struct stat));
if (stat(filepath_actual, pathstat) < 0) {
server_file_result_seterror(result, 404, "File not found");
free(filepath_actual);
free(pathstat);
return result;
}
//If directory
if (S_ISDIR(pathstat->st_mode) != 0) {
char* dir_index = server_find_directory_index(hconfig, filepath_actual);
if (hconfig->index_files_count > 0 && dir_index != NULL) {
//Use the directory index
result->path = dir_index;
} else if (hconfig->dir_listings == true) {
//Show directory listing
result->dir = true;
result->path = strdup(filepath_actual);
} else {
server_file_result_seterror(result, 403, "No index file was found and directory indexes are disabled");
}
} else if (S_ISREG(pathstat->st_mode) != 0) {
result->path = strdup(filepath_actual);
}
free(pathstat);
free(filepath_actual);
return result;
}
char* server_find_directory_index(config_host *hconfig, char* path) {
DIR *dir = opendir(path);
if (dir == NULL) {
return NULL;
}
struct dirent *entry;
while((entry = readdir(dir)) != NULL) {
for(int i=0; i<hconfig->index_files_count; i++) {
if (strcasecmp(entry->d_name, hconfig->index_files[i]) == 0) {
char* dirindex = calloc(strlen(path)+strlen(entry->d_name)+2, sizeof(char));
strcat(dirindex, path);
strcat(dirindex, "/");
strcat(dirindex, entry->d_name);
closedir(dir);
return dirindex;
}
}
}
closedir(dir);
return NULL;
}
FILE * server_generate_directory_index(config_host *hconfig, const char* dirpath) {
DIR *dir = opendir(dirpath);
if (dir == NULL) {
return NULL;
}
UT_string *index = NULL;
utstring_new(index);
struct dirent *entry;
struct stat *filestat = calloc(1, sizeof(struct stat));
uint32_t count=0;
while((entry = readdir(dir)) != NULL) {
if (count++ > 1024*8) break;
char *filepath = calloc(strlen(dirpath)+strlen(entry->d_name)+2, sizeof(char));
strcat(filepath, dirpath);
strcat(filepath, "/");
strcat(filepath, entry->d_name);
if (stat(filepath, filestat) < 0) {
continue;
}
char* filesize = NULL;
if (S_ISDIR(filestat->st_mode)) {
filesize = strdup("[DIR]");
} else if (S_ISREG(filestat->st_mode)) {
filesize = server_get_filesize(filepath);
} else {
continue;
}
char* uri = strdup(filepath);
uri = str_replace(uri, hconfig->serve_dir, "");
if (uri[0] = '/') {
memmove(uri, uri+1, strlen(uri+1)+1);
uri = realloc(uri, strlen(uri)+1);
}
time_t file_mtime = (time_t)filestat->st_mtim.tv_sec;
char* file_mod_time = ctime(&file_mtime);
utstring_printf(index, "<tr><td>%s</td><td>%s</td><td>%s</td></tr>\r\n", uri,
(filesize!=NULL)?filesize:"N/A",
(file_mod_time!=NULL)?file_mod_time:"N/A");
free(filepath);
free(filesize);
free(uri);
}
closedir(dir);
free(filestat);
char *dirname = strdup(dirpath);
dirname = str_replace(dirname, hconfig->serve_dir, "/");
file_map *dirindex_map = file_map_new("dirindex.html");
if (dirindex_map == NULL) {
utstring_free(index);
free(dirname);
return NULL;
}
char* dirindex = strdup(dirindex_map->map);
file_map_delete(dirindex_map);
dirindex = str_replace(dirindex, "{{dirname}}", dirname);
free(dirname);
dirindex = str_replace(dirindex, "{{index}}", utstring_body(index));
utstring_free(index);
FILE *file = tmpfile();
fwrite(dirindex, sizeof(char), strlen(dirindex), file);
free(dirindex);
return file;
}
char* server_get_filesize(const char* filename) {
FILE *file = fopen(filename, "rb");
if (file == NULL) {
return NULL;
}
fseek(file, 0L, SEEK_END);
long int size = ftell(file);
fclose(file);
char* sizenames[] = {"b", "kb", "mb", "gb", "pb"};
int size_i = 0;
while(size > 1024) {
if (size_i > (sizeof(sizenames) / sizeof(char*))) {
break;
}
size_i++;
size /= 1024;
}
char* sizestr = calloc(100, sizeof(char));
snprintf(sizestr, 100, "%li%s", size, sizenames[size_i]);
sizestr = realloc(sizestr, sizeof(char)*(strlen(sizestr)+1));
return sizestr;
}
server_file_result* server_file_result_new() {
server_file_result *result = calloc(1, sizeof(server_file_result));
result->error = false;
result->error_code = 0;
result->error_text = NULL;
result->dir = false;
result->path = NULL;
return result;
}
void server_file_result_seterror(server_file_result *result, uint16_t code, const char* message) {
result->error = true;
result->error_code = code;
result->error_text = strdup(message);
}
void server_file_result_delete(server_file_result *result) {
free(result->error_text);
free(result->path);
free(result);
}

View File

@@ -12,12 +12,31 @@
extern "C" {
#endif
#include <stdbool.h>
#include <time.h>
#include <dirent.h>
#include "http.h"
#include "main.h"
#include "config.h"
http_response* server_process_request(config_server* config, http_request *request);
typedef struct server_file_result {
bool error;
uint16_t error_code;
char* error_text;
bool dir;
DIR *dirent;
char* path;
} server_file_result;
http_response* server_process_request(config_server* config, http_request *request);
server_file_result* server_determine_file(config_host* hconfig, const char* requested_path);
char* server_find_directory_index(config_host *hconfig, char* path);
FILE * server_generate_directory_index(config_host *hconfig, const char* dirpath);
char* server_get_filesize(const char* filename);
server_file_result* server_file_result_new();
void server_file_result_seterror(server_file_result *result, uint16_t code, const char* message);
void server_file_result_delete(server_file_result *result);
#ifdef __cplusplus
}

View File

@@ -29,7 +29,7 @@ extern "C" {
#define HEADER_IF_MODIFIED_SINCE "If-Modified-Since"
#define HEADER_IF_UNMODIFIED_SINCE "If-Unmodified-Since"
#define FORMAT_HEADER_DATE "%a, %e %h %Y %T %Z"
#define FORMAT_HEADER_DATE "%a, %d %h %Y %T %Z"
#define DEFAULT_CONTENT_TYPE "text/plain"
#define HTTP_CHUNK_MAXSIZE 1024*16

View File

@@ -9,11 +9,12 @@
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <ctype.h>
#include <signal.h>
#include "http_parser.h"
@@ -28,29 +29,39 @@
#include "mime.h"
int serverfd = 0;
volatile static bool stop = false;
static void signal_int(int signum) {
fprintf(stderr, "Terminating...\n");
stop = true;
}
int main(int argc, char** argv) {
mime_load(NULL);
config_server *config = config_server_new();
if (config_read_ini("khttpd.ini", config) < 0) {
return 1;
fatal("Could not read config");
}
signal(SIGINT, signal_int);
skt_elem *connections = NULL;
serverfd = svr_create();
svr_listen(serverfd, config->listen_port);
while(1) {
uint32_t counter;
uint32_t connections_open;
skt_elem *elem, *tmp;
//Accept new connections
LL_COUNT(connections, elem, counter);
while(counter < 100 && svr_canaccept(serverfd)) {
LL_COUNT(connections, elem, connections_open);
while(connections_open < 100 && svr_canaccept(serverfd)) {
skt_info *info = svr_accept(serverfd);
if (info != NULL) {
LL_APPEND(connections, skt_elem_new(info));
skt_elem *elem = skt_elem_new(info);
LL_APPEND(connections, elem);
}
}
@@ -78,7 +89,7 @@ int main(int argc, char** argv) {
"error parsing request (%s: %s). closing connection",
http_errno_name(elem->parser->http_errno),
http_errno_description(elem->parser->http_errno));
warning(warningmsg, false);
warning(false, warningmsg);
//send 400 back and close connection
http_response *resp400 = http_response_create_builtin(400, "Request was invalid or could not be read");
http_header_list_add(resp400->headers, http_header_new(HEADER_CONNECTION, "close"), false);
@@ -125,7 +136,7 @@ int main(int argc, char** argv) {
if (elem->info->close_afterwrite && utstring_len(elem->info->write) == 0) {
elem->info->close = true;
}
if (elem->info->close == true) {
if (elem->info->close == true || stop == true) {
skt_close(elem->info);
}
}
@@ -136,9 +147,13 @@ int main(int argc, char** argv) {
skt_elem_delete(elem);
}
}
if (stop == true) {
break;
}
}
mime_free();
config_server_delete(config);
svr_release(serverfd);
serverfd = 0;
@@ -168,13 +183,20 @@ void skt_elem_reset(skt_elem *elem) {
elem->request_complete = false;
}
void skt_elem_write_response(skt_elem *elem, http_response *response, bool dispose) {
char *response_str = http_response_write(response);
utstring_printf(elem->info->write, "%s", response_str);
free(response_str);
http_header* connection_header = http_header_list_get(response->headers, HEADER_CONNECTION);
if (connection_header != NULL && strcasecmp(connection_header->content, "close") == 0) {
elem->info->close_afterwrite = true;
}
if (connection_header == NULL) {
if (response->resp->version == HTTP11) {
http_header_list_add(response->headers, http_header_new(HEADER_CONNECTION, "Keep-Alive"), true);
} else if (response->resp->version == HTTP10) {
elem->info->close_afterwrite = true;
}
}
char *response_str = http_response_write(response);
utstring_printf(elem->info->write, "%s", response_str);
free(response_str);
if (dispose == true) {
http_response_delete(response);
}
@@ -182,136 +204,10 @@ void skt_elem_write_response(skt_elem *elem, http_response *response, bool dispo
void skt_elem_delete(skt_elem* elem) {
if (elem->info!=NULL) skt_delete(elem->info);
if (elem->current_request!=NULL) http_request_delete(elem->current_request);
if (elem->parser!= NULL) {
elem->parser->data = NULL;
free(elem->parser);
}
free(elem);
}
void fatal(char* msg) {
fprintf(stderr, "\n");
perror(msg);
exit(EXIT_FAILURE);
}
void warning(char* msg, bool showPError) {
char warning[1024];
memset(&warning, 0, 1024*sizeof(char));
snprintf(warning, 1024, "Warning: %s", msg);
if (showPError == true) {
perror(warning);
} else {
fprintf(stderr, "%s\n", warning);
}
}
void info(char* msg, ...) {
va_list va;
va_start(va, msg);
vfprintf(stdout, msg, va);
fputc('\n', stdout);
va_end(va);
}
char** str_splitlines(char *str, size_t *line_count) {
char **result;
*line_count = 0;
char *tmp = str;
while(*tmp) {
if (*tmp == '\n') {
(*line_count)++;
}
tmp++;
}
if (*line_count == 0) {
result = calloc(1, sizeof(char*));
result[0] = calloc(strlen(str), sizeof(char));
strcpy(result[0], str);
return result;
}
result = calloc(*line_count, sizeof(char*));
if (result == NULL) {
fatal("calloc failed");
}
size_t i=0, linelen = 0;
char *line = strtok(str, "\n");
while(line) {
linelen = strlen(line);
result[i] = calloc(linelen+1, sizeof(char));
if (result[i] == NULL) {
fatal("calloc failed");
}
strcpy(result[i], line);
if (result[i][linelen-1] == '\r') {
result[i][linelen-1] = '\0';
result[i] = realloc(result[i], linelen);
}
line = strtok(NULL, "\n");
i++;
}
return result;
}
file_map* file_map_new(const char* filename) {
int fd = open(filename, O_RDONLY);
if (fd < 0) {
warning("Failed to open file for memory mapping", true);
return NULL;
}
size_t size = lseek(fd, 0L, SEEK_END);
void* map = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
if (map == MAP_FAILED) {
warning("Failed to mmap file", true);
close(fd);
return NULL;
}
close(fd);
file_map* filemap = calloc(1, sizeof(file_map));
filemap->map = (char*)map;
filemap->size = size;
return filemap;
}
void file_map_delete(file_map* file) {
if (munmap((void*)file->map, file->size) < 0) {
warning("failed to unmap file", true);
}
free(file);
}
char* str_replace(char *haystack, const char *search, const char *replacement) {
size_t haystacklen = strlen(haystack);
size_t searchlen = strlen(search);
size_t replacementlen = strlen(replacement);
char* result = haystack;
if (searchlen > haystacklen || searchlen == 0) {
return result;
}
if (strstr(replacement, search) != NULL) {
warning("str_replace: replacement should not contain the search criteria", false);
}
int count = 0;
while(count++ < 1000) {
char* pos = strstr(result, search);
if (pos == NULL) {
break;
}
uint32_t start = (pos - result) / sizeof(char);
uint32_t end = start + searchlen;
size_t resultlen = strlen(result);
size_t newlen = resultlen + replacementlen - searchlen;
char* newstr = calloc(newlen+1, sizeof(char));
strncpy(newstr, result, start);
strcat(newstr, replacement);
strcat(newstr, pos+(searchlen*sizeof(char)));
free(result);
result = newstr;
}
return result;
}

View File

@@ -18,11 +18,7 @@ extern "C" {
#include "http_parser.h"
#include "socket.h"
#include "http.h"
typedef struct file_map {
char* map;
size_t size;
} file_map;
#include "util.h"
typedef enum skt_elem_hstate {HSTATE_NONE, HSTATE_VALUE, HSTATE_FIELD} skt_elem_hstate;
typedef struct skt_elem {
@@ -42,15 +38,7 @@ extern "C" {
int main(int argc, char** argv);
void fatal(char* msg);
void warning(char* msg, bool showPError);
void info(char* msg, ...);
char** str_splitlines(char *str, size_t *line_count);
char* str_replace(char *str, const char *search, const char *replacement);
file_map* file_map_new(const char* filename);
void file_map_delete(file_map* map);
#ifdef __cplusplus

View File

@@ -8,13 +8,14 @@
#include "mime.h"
#include "main.h"
mime_type *mime_list;
mime_type *mime_list = NULL;
int mime_load(const char* file) {
FILE *mimetypes = fopen(file == NULL ? MIME_DEFAULT_FILE : file, "r");
if (mimetypes == NULL) {
return -1;
}
mime_type *new_list = NULL;
size_t count = 1024;
char* buffer = calloc(count, sizeof(char));
ssize_t linelength;
@@ -35,7 +36,7 @@ int mime_load(const char* file) {
mime_type *new = calloc(1, sizeof(mime_type));
new->extension = strdup(ext);
new->mime = strdup(mime);
LL_APPEND(mime_list, new);
LL_APPEND(new_list, new);
ext = strtok_r(NULL, " \t", &saveptr);
}
}
@@ -43,6 +44,12 @@ int mime_load(const char* file) {
}
free(buffer);
fclose(mimetypes);
if (mime_list != NULL) {
mime_free();
}
mime_list = new_list;
count = 0;
mime_type *elem;
LL_COUNT(mime_list, elem, count);

View File

@@ -17,7 +17,7 @@
u_int64_t skt_nextid() {
static u_int64_t id = 0;
return id++;
return __atomic_fetch_add(&id, 1, __ATOMIC_SEQ_CST);
}
skt_info* skt_new(int fd) {
skt_info* skt = calloc(1, sizeof(skt_info));
@@ -29,7 +29,6 @@ skt_info* skt_new(int fd) {
skt->close = false;
skt->close_afterwrite = false;
skt->closed = false;
return skt;
}
void skt_delete(skt_info* skt) {
@@ -42,7 +41,7 @@ void skt_delete(skt_info* skt) {
bool skt_canread(skt_info* skt) {
int len = 0;
if (ioctl(skt->fd, FIONREAD, &len) < 0) {
warning("ioctl failed", true);
warning(true, "ioctl failed");
return false;
}
return len > 0;
@@ -52,9 +51,11 @@ uint32_t skt_read(skt_info* skt) {
memset(buffer, 0, 1024);
int result = read(skt->fd, &buffer,1023);
if (result < 0 && errno != EAGAIN && errno != EWOULDBLOCK) {
warning("read error", true);
skt->close = true;
if (result < 0) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
warning(true, "read error");
skt->close = true;
}
return 0;
}
skt->last_act = time(NULL);
@@ -67,17 +68,19 @@ uint32_t skt_write(skt_info* skt) {
}
int result = write(skt->fd, utstring_body(skt->write), utstring_len(skt->write));
if (result < 0 && errno != EAGAIN && errno != EWOULDBLOCK) {
warning("write error", true);
skt->close = true;
if (result < 0) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
warning(true, "write error");
skt->close = true;
}
return 0;
}
skt->last_act = time(NULL);
if (result == utstring_len(skt->write)) {
utstring_free(skt->write);
utstring_new(skt->write);
return 0;
utstring_clear(skt->write);
return result;
}
//remove first x chars
char* newstr = calloc(utstring_len(skt->write) - result + 1, sizeof(char));
@@ -85,8 +88,7 @@ uint32_t skt_write(skt_info* skt) {
char* writeBody = utstring_body(skt->write);
strcpy(newstr, writeBody + (sizeof(char) * result));
utstring_free(skt->write);
utstring_new(skt->write);
utstring_clear(skt->write);
utstring_printf(skt->write, "%s", newstr);
free(newstr);
return result; //bytes written
@@ -97,7 +99,7 @@ void skt_close(skt_info* skt) {
}
info("[#%lu %s] Closed", skt->id, skt_clientaddr(skt));
if (close(skt->fd) < 0) {
warning("error closing socket", true);
warning(true, "error closing socket");
}
skt->closed = true;
}
@@ -122,20 +124,18 @@ void svr_listen(int fd, uint16_t port) {
server_address.sin_port = htons(port);
if (bind(fd, (struct sockaddr*)&server_address, sizeof server_address) < 0) {
warning("failed to bind", true);
close(fd);
exit(EXIT_FAILURE);
fatal("Failed to bind to socket");
}
if (listen(fd, 16) < 0) {
warning("failed to listen", true);
close(fd);
exit(EXIT_FAILURE);
fatal("Could not set socket to listen mode");
}
info("Listening on port %u", port);
}
void svr_release(int fd) {
if (close(fd) < 0) {
warning("could not close socket", true);
warning(true, "could not close socket");
}
}
bool svr_canaccept(int fd) {
@@ -145,12 +145,15 @@ bool svr_canaccept(int fd) {
pfd[0].events = POLLIN;
if (poll(pfd, 1, 50/*ms*/) < 0) {
warning("poll failed", true);
warning(true, "poll failed");
free(pfd);
return false;
}
if ((pfd[0].revents & POLLIN) == POLLIN) {
free(pfd);
return true;
}
free(pfd);
return false;
}
skt_info* svr_accept(int fd) {
@@ -160,7 +163,7 @@ skt_info* svr_accept(int fd) {
socklen_t clientaddr_len = (socklen_t)sizeof(struct sockaddr_in);
clientfd = accept(fd, (struct sockaddr*)clientaddr, &clientaddr_len);
if (clientfd < 0 && errno != EAGAIN && errno != EWOULDBLOCK) {
warning("error accepting connection", true);
warning(true, "error accepting connection");
return NULL;
}

168
src/util.c Normal file
View File

@@ -0,0 +1,168 @@
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <ctype.h>
#include <string.h>
#include <stdarg.h>
#include <stdint.h>
#include "util.h"
void fatal(char* msg) {
fprintf(stderr, "\n");
perror(msg);
exit(EXIT_FAILURE);
}
void warning(bool showPError, char* msg, ...) {
char warning[128] = {0};
va_list va;
va_start(va, msg);
vsnprintf(warning, 128, msg, va);
va_end(va);
if (showPError == true) {
perror(warning);
} else {
fprintf(stderr, "%s\n", warning);
}
}
void info(char* msg, ...) {
va_list va;
va_start(va, msg);
vfprintf(stdout, msg, va);
fputc('\n', stdout);
va_end(va);
}
char** str_splitlines(char *str, size_t *line_count) {
char **result;
*line_count = 0;
char *tmp = str;
while(*tmp) {
if (*tmp == '\n') {
(*line_count)++;
}
tmp++;
}
if (*line_count == 0) {
result = calloc(1, sizeof(char*));
result[0] = calloc(strlen(str), sizeof(char));
strcpy(result[0], str);
return result;
}
result = calloc(*line_count, sizeof(char*));
if (result == NULL) {
fatal("calloc failed");
}
size_t i=0, linelen = 0;
char *line = strtok(str, "\n");
while(line) {
linelen = strlen(line);
result[i] = calloc(linelen+1, sizeof(char));
if (result[i] == NULL) {
fatal("calloc failed");
}
strcpy(result[i], line);
if (result[i][linelen-1] == '\r') {
result[i][linelen-1] = '\0';
result[i] = realloc(result[i], linelen);
}
line = strtok(NULL, "\n");
i++;
}
return result;
}
/* str_trimwhitespace
* Credit to https://stackoverflow.com/a/122721/428
*/
char* str_trimwhitespace(char *str)
{
char *end;
// Trim leading space
while(isspace(*str)) str++;
if(*str == 0) // All spaces?
return str;
// Trim trailing space
end = str + strlen(str) - 1;
while(end > str && isspace(*end)) end--;
// Write new null terminator
*(end+1) = 0;
return str;
}
file_map* file_map_new(const char* filename) {
int fd = open(filename, O_RDONLY);
if (fd < 0) {
warning(true, "Failed to open file for memory mapping");
return NULL;
}
size_t size = lseek(fd, 0L, SEEK_END);
void* map = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
if (map == MAP_FAILED) {
warning(true, "Failed to mmap file");
close(fd);
return NULL;
}
close(fd);
file_map* filemap = calloc(1, sizeof(file_map));
filemap->map = (char*)map;
filemap->size = size;
return filemap;
}
void file_map_delete(file_map* file) {
if (munmap((void*)file->map, file->size) < 0) {
warning(true, "failed to unmap file");
}
free(file);
}
char* str_replace(char *haystack, const char *search, const char *replacement) {
size_t haystacklen = strlen(haystack);
size_t searchlen = strlen(search);
size_t replacementlen = strlen(replacement);
char* result = haystack;
if (searchlen > haystacklen || searchlen == 0) {
return result;
}
if (strstr(replacement, search) != NULL) {
warning(false, "str_replace: replacement should not contain the search criteria");
}
int count = 0;
while(count++ < 1000) {
char* pos = strstr(result, search);
if (pos == NULL) {
break;
}
uint32_t start = (pos - result) / sizeof(char);
uint32_t end = start + searchlen;
size_t resultlen = strlen(result);
size_t newlen = resultlen + replacementlen - searchlen;
char* newstr = calloc(newlen+1, sizeof(char));
strncpy(newstr, result, start);
strcat(newstr, replacement);
strcat(newstr, pos+(searchlen*sizeof(char)));
free(result);
result = newstr;
}
return result;
}

39
src/util.h Normal file
View File

@@ -0,0 +1,39 @@
/*
* File: util.h
* Author: sam
*
* Created on 03 August 2014, 13:52
*/
#ifndef UTIL_H
#define UTIL_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
typedef struct file_map {
char* map;
size_t size;
} file_map;
void fatal(char* msg);
void warning(bool showPError, char* msg, ...);
void info(char* msg, ...);
char* str_trimwhitespace(char *str);
char** str_splitlines(char *str, size_t *line_count);
char* str_replace(char *str, const char *search, const char *replacement);
file_map* file_map_new(const char* filename);
void file_map_delete(file_map* map);
#ifdef __cplusplus
}
#endif
#endif /* UTIL_H */

View File

@@ -1,4 +1,4 @@
GET /index.html HTTP/1.1
GET / HTTP/1.1
Host: example.com
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36