--- trunk/jscoverage-server.c 2008/09/23 03:48:37 189 +++ trunk/jscoverage-server.c 2009/10/04 06:05:17 475 @@ -1,6 +1,6 @@ /* jscoverage-server.c - JSCoverage server main routine - Copyright (C) 2008 siliconforks.com + Copyright (C) 2008, 2009 siliconforks.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -38,6 +38,7 @@ #include "stream.h" #include "util.h" +static const char * specified_encoding = NULL; const char * jscoverage_encoding = "ISO-8859-1"; bool jscoverage_highlight = true; @@ -242,10 +243,10 @@ return c - '0'; } else if ('A' <= c && c <= 'F') { - return c - 'A'; + return c - 'A' + 10; } else if ('a' <= c && c <= 'f') { - return c - 'a'; + return c - 'a' + 10; } else { return 0; @@ -443,9 +444,12 @@ case '\t': fputs("\\t", f); break; + /* IE doesn't support this */ + /* case '\v': fputs("\\v", f); break; + */ case '"': fputs("\\\"", f); break; @@ -512,7 +516,15 @@ else { /* check that the path begins with / */ if (file_coverage->id[0] == '/') { - char * source_path = make_path(document_root, file_coverage->id + 1); + char * decoded_path = decode_uri_component(file_coverage->id); + if (strstr(decoded_path, "..") != NULL) { + free(decoded_path); + fputs("[]", f); + HTTPServer_log_err("Warning: invalid source path: %s\n", file_coverage->id); + goto done; + } + char * source_path = make_path(document_root, decoded_path + 1); + free(decoded_path); FILE * source_file = fopen(source_path, "rb"); free(source_path); if (source_file == NULL) { @@ -559,6 +571,7 @@ } fputc(']', f); } +done: fputc('}', f); } @@ -566,7 +579,7 @@ static int write_json(Coverage * coverage, const char * path) { /* write the JSON */ - FILE * f = fopen(path, "w"); + FILE * f = fopen(path, "wb"); if (f == NULL) { return -1; } @@ -630,7 +643,7 @@ struct stat buf; if (stat(path, &buf) == 0) { /* it exists: merge */ - FILE * f = fopen(path, "r"); + FILE * f = fopen(path, "rb"); if (f == NULL) { result = 1; } @@ -852,12 +865,10 @@ free(encoding); Stream_delete(input_stream); if (result == JSCOVERAGE_ERROR_ENCODING_NOT_SUPPORTED) { - free(characters); - send_response(client_exchange, 502, "Encoding not supported\n"); + send_response(client_exchange, 500, "Encoding not supported\n"); goto done; } else if (result == JSCOVERAGE_ERROR_INVALID_BYTE_SEQUENCE) { - free(characters); send_response(client_exchange, 502, "Error decoding response\n"); goto done; } @@ -870,6 +881,10 @@ if (is_hop_by_hop_header(h->name) || strcasecmp(h->name, HTTP_CONTENT_LENGTH) == 0) { continue; } + else if (strcasecmp(h->name, HTTP_CONTENT_TYPE) == 0) { + HTTPExchange_add_response_header(client_exchange, HTTP_CONTENT_TYPE, "text/javascript; charset=ISO-8859-1"); + continue; + } HTTPExchange_add_response_header(client_exchange, h->name, h->value); } add_via_header(HTTPExchange_get_response_message(client_exchange), HTTPExchange_get_response_http_version(server_exchange)); @@ -933,22 +948,25 @@ /* add the `Server' response-header (RFC 2616 14.38, 3.8) */ HTTPExchange_add_response_header(exchange, HTTP_SERVER, "jscoverage-server/" VERSION); + char * decoded_path = NULL; char * filesystem_path = NULL; const char * abs_path = HTTPExchange_get_abs_path(exchange); assert(*abs_path != '\0'); - if (str_starts_with(abs_path, "/jscoverage")) { + decoded_path = decode_uri_component(abs_path); + + if (str_starts_with(decoded_path, "/jscoverage")) { handle_jscoverage_request(exchange); goto done; } - if (strstr(abs_path, "..") != NULL) { + if (strstr(decoded_path, "..") != NULL) { send_response(exchange, 403, "Forbidden\n"); goto done; } - filesystem_path = make_path(document_root, abs_path + 1); + filesystem_path = make_path(document_root, decoded_path + 1); size_t filesystem_path_length = strlen(filesystem_path); if (filesystem_path_length > 0 && filesystem_path[filesystem_path_length - 1] == '/') { /* stat on Windows doesn't work with trailing slash */ @@ -1003,9 +1021,22 @@ goto done; } + /* + When do we send a charset with Content-Type? + if Content-Type is "text" or "application" + if instrumented JavaScript + use Content-Type: application/javascript; charset=ISO-8859-1 + else if --encoding is given + use that encoding + else + send no charset + else + send no charset + */ const char * content_type = get_content_type(filesystem_path); - HTTPExchange_set_response_header(exchange, HTTP_CONTENT_TYPE, content_type); if (strcmp(content_type, "text/javascript") == 0 && ! is_no_instrument(abs_path)) { + HTTPExchange_set_response_header(exchange, HTTP_CONTENT_TYPE, "text/javascript; charset=ISO-8859-1"); + Stream * input_stream = Stream_new(0); Stream_write_file_contents(input_stream, f); @@ -1015,12 +1046,10 @@ Stream_delete(input_stream); if (result == JSCOVERAGE_ERROR_ENCODING_NOT_SUPPORTED) { - free(characters); send_response(exchange, 500, "Encoding not supported\n"); goto done; } else if (result == JSCOVERAGE_ERROR_INVALID_BYTE_SEQUENCE) { - free(characters); send_response(exchange, 500, "Error decoding JavaScript file\n"); goto done; } @@ -1035,6 +1064,17 @@ Stream_delete(output_stream); } else { + /* send the Content-Type with charset if necessary */ + if (specified_encoding != NULL && (str_starts_with(content_type, "text/") || str_starts_with(content_type, "application/"))) { + char * content_type_with_charset = NULL; + xasprintf(&content_type_with_charset, "%s; charset=%s", content_type, specified_encoding); + HTTPExchange_set_response_header(exchange, HTTP_CONTENT_TYPE, content_type_with_charset); + free(content_type_with_charset); + } + else { + HTTPExchange_set_response_header(exchange, HTTP_CONTENT_TYPE, content_type); + } + char buffer[8192]; size_t bytes_read; while ((bytes_read = fread(buffer, 1, 8192, f)) > 0) { @@ -1052,6 +1092,7 @@ done: free(filesystem_path); + free(decoded_path); } static void handler(HTTPExchange * exchange) { @@ -1082,8 +1123,7 @@ exit(EXIT_SUCCESS); } else if (strcmp(argv[i], "-V") == 0 || strcmp(argv[i], "--version") == 0) { - printf("jscoverage-server %s\n", VERSION); - exit(EXIT_SUCCESS); + version(); } else if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--verbose") == 0) { verbose = 1; @@ -1092,7 +1132,7 @@ else if (strcmp(argv[i], "--report-dir") == 0) { i++; if (i == argc) { - fatal("--report-dir: option requires an argument"); + fatal_command_line("--report-dir: option requires an argument"); } report_directory = argv[i]; } @@ -1103,7 +1143,7 @@ else if (strcmp(argv[i], "--document-root") == 0) { i++; if (i == argc) { - fatal("--document-root: option requires an argument"); + fatal_command_line("--document-root: option requires an argument"); } document_root = argv[i]; } @@ -1114,18 +1154,20 @@ else if (strcmp(argv[i], "--encoding") == 0) { i++; if (i == argc) { - fatal("--encoding: option requires an argument"); + fatal_command_line("--encoding: option requires an argument"); } jscoverage_encoding = argv[i]; + specified_encoding = jscoverage_encoding; } else if (strncmp(argv[i], "--encoding=", 11) == 0) { jscoverage_encoding = argv[i] + 11; + specified_encoding = jscoverage_encoding; } else if (strcmp(argv[i], "--ip-address") == 0) { i++; if (i == argc) { - fatal("--ip-address: option requires an argument"); + fatal_command_line("--ip-address: option requires an argument"); } ip_address = argv[i]; } @@ -1133,6 +1175,17 @@ ip_address = argv[i] + 13; } + else if (strcmp(argv[i], "--js-version") == 0) { + i++; + if (i == argc) { + fatal_command_line("--js-version: option requires an argument"); + } + jscoverage_set_js_version(argv[i]); + } + else if (strncmp(argv[i], "--js-version=", 13) == 0) { + jscoverage_set_js_version(argv[i] + 13); + } + else if (strcmp(argv[i], "--no-highlight") == 0) { jscoverage_highlight = false; } @@ -1140,7 +1193,7 @@ else if (strcmp(argv[i], "--no-instrument") == 0) { i++; if (i == argc) { - fatal("--no-instrument: option requires an argument"); + fatal_command_line("--no-instrument: option requires an argument"); } no_instrument[num_no_instrument] = argv[i]; num_no_instrument++; @@ -1153,7 +1206,7 @@ else if (strcmp(argv[i], "--port") == 0) { i++; if (i == argc) { - fatal("--port: option requires an argument"); + fatal_command_line("--port: option requires an argument"); } port = argv[i]; } @@ -1170,10 +1223,10 @@ } else if (strncmp(argv[i], "-", 1) == 0) { - fatal("unrecognized option `%s'", argv[i]); + fatal_command_line("unrecognized option `%s'", argv[i]); } else { - fatal("too many arguments"); + fatal_command_line("too many arguments"); } } @@ -1181,10 +1234,17 @@ char * end; unsigned long numeric_port = strtoul(port, &end, 10); if (*end != '\0') { - fatal("--port: option must be an integer"); + fatal_command_line("--port: option must be an integer"); } if (numeric_port > UINT16_MAX) { - fatal("--port: option must be 16 bits"); + fatal_command_line("--port: option must be 16 bits"); + } + + /* check the document root exists and is a directory */ + struct stat buf; + xstat(document_root, &buf); + if (! S_ISDIR(buf.st_mode)) { + fatal_command_line("--document-root: option must be a directory"); } /* is this a shutdown? */