/[jscoverage]/trunk/jscoverage-server.c
ViewVC logotype

Diff of /trunk/jscoverage-server.c

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 189 by siliconforks, Tue Sep 23 03:48:37 2008 UTC revision 583 by siliconforks, Sat Sep 11 20:20:14 2010 UTC
# Line 1  Line 1 
1  /*  /*
2      jscoverage-server.c - JSCoverage server main routine      jscoverage-server.c - JSCoverage server main routine
3      Copyright (C) 2008 siliconforks.com      Copyright (C) 2008, 2009, 2010 siliconforks.com
4    
5      This program is free software; you can redistribute it and/or modify      This program is free software; you can redistribute it and/or modify
6      it under the terms of the GNU General Public License as published by      it under the terms of the GNU General Public License as published by
# Line 38  Line 38 
38  #include "stream.h"  #include "stream.h"
39  #include "util.h"  #include "util.h"
40    
41    static const char * specified_encoding = NULL;
42  const char * jscoverage_encoding = "ISO-8859-1";  const char * jscoverage_encoding = "ISO-8859-1";
43  bool jscoverage_highlight = true;  bool jscoverage_highlight = true;
44    
45    struct in_addr jscoverage_ip_address;
46    
47  typedef struct SourceCache {  typedef struct SourceCache {
48    char * url;    char * url;
49    uint16_t * characters;    uint16_t * characters;
# Line 242  Line 245 
245      return c - '0';      return c - '0';
246    }    }
247    else if ('A' <= c && c <= 'F') {    else if ('A' <= c && c <= 'F') {
248      return c - 'A';      return c - 'A' + 10;
249    }    }
250    else if ('a' <= c && c <= 'f') {    else if ('a' <= c && c <= 'f') {
251      return c - 'a';      return c - 'a' + 10;
252    }    }
253    else {    else {
254      return 0;      return 0;
# Line 397  Line 400 
400    }    }
401  }  }
402    
403  static bool should_instrument_request(HTTPExchange * exchange) {  static bool should_instrument_request(HTTPExchange * exchange, const char * uri) {
404    if (! is_javascript(exchange)) {    if (! is_javascript(exchange)) {
405      return false;      return false;
406    }    }
407    
408    if (is_no_instrument(HTTPExchange_get_request_uri(exchange))) {    if (is_no_instrument(uri)) {
409      return false;      return false;
410    }    }
411    
# Line 443  Line 446 
446      case '\t':      case '\t':
447        fputs("\\t", f);        fputs("\\t", f);
448        break;        break;
449        /* IE doesn't support this */
450        /*
451      case '\v':      case '\v':
452        fputs("\\v", f);        fputs("\\v", f);
453        break;        break;
454        */
455      case '"':      case '"':
456        fputs("\\\"", f);        fputs("\\\"", f);
457        break;        break;
# Line 512  Line 518 
518      else {      else {
519        /* check that the path begins with / */        /* check that the path begins with / */
520        if (file_coverage->id[0] == '/') {        if (file_coverage->id[0] == '/') {
521          char * source_path = make_path(document_root, file_coverage->id + 1);          char * decoded_path = decode_uri_component(file_coverage->id);
522            if (strstr(decoded_path, "..") != NULL) {
523              free(decoded_path);
524              fputs("[]", f);
525              HTTPServer_log_err("Warning: invalid source path: %s\n", file_coverage->id);
526              goto done;
527            }
528            char * source_path = make_path(document_root, decoded_path + 1);
529            free(decoded_path);
530          FILE * source_file = fopen(source_path, "rb");          FILE * source_file = fopen(source_path, "rb");
531          free(source_path);          free(source_path);
532          if (source_file == NULL) {          if (source_file == NULL) {
# Line 559  Line 573 
573      }      }
574      fputc(']', f);      fputc(']', f);
575    }    }
576    done:
577    fputc('}', f);    fputc('}', f);
578  }  }
579    
# Line 566  Line 581 
581    
582  static int write_json(Coverage * coverage, const char * path) {  static int write_json(Coverage * coverage, const char * path) {
583    /* write the JSON */    /* write the JSON */
584    FILE * f = fopen(path, "w");    FILE * f = fopen(path, "wb");
585    if (f == NULL) {    if (f == NULL) {
586      return -1;      return -1;
587    }    }
# Line 630  Line 645 
645      struct stat buf;      struct stat buf;
646      if (stat(path, &buf) == 0) {      if (stat(path, &buf) == 0) {
647        /* it exists: merge */        /* it exists: merge */
648        FILE * f = fopen(path, "r");        FILE * f = fopen(path, "rb");
649        if (f == NULL) {        if (f == NULL) {
650          result = 1;          result = 1;
651        }        }
# Line 683  Line 698 
698        return;        return;
699      }      }
700    
701      /* allow only from localhost */      /* check client IP address */
702      struct sockaddr_in client;      struct sockaddr_in client;
703      if (HTTPExchange_get_peer(exchange, &client) != 0) {      if (HTTPExchange_get_peer(exchange, &client) != 0) {
704        send_response(exchange, 500, "Cannot get client address\n");        send_response(exchange, 500, "Cannot get client address\n");
705        return;        return;
706      }      }
707      if (client.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) {  
708        /*
709        We allow connections from loopback 127.0.0.0 to 127.255.255.255 (127/8)
710        and from the address specified with --ip-address
711        */
712        bool client_allowed = false;
713    
714        if (client.sin_addr.s_addr == htonl(INADDR_LOOPBACK)) {
715          client_allowed = true;
716        }
717    
718        if ((ntohl(client.sin_addr.s_addr) & 0xFF000000) == 0x7F000000) {
719          client_allowed = true;
720        }
721    
722        if (client.sin_addr.s_addr == jscoverage_ip_address.s_addr) {
723          client_allowed = true;
724        }
725    
726        if (! client_allowed) {
727        send_response(exchange, 403, "This operation can be performed only by localhost\n");        send_response(exchange, 403, "This operation can be performed only by localhost\n");
728        return;        return;
729      }      }
# Line 788  Line 822 
822    
823    /* create a new exchange */    /* create a new exchange */
824    server_exchange = HTTPExchange_new(server_connection);    server_exchange = HTTPExchange_new(server_connection);
825    
826    HTTPExchange_set_method(server_exchange, HTTPExchange_get_method(client_exchange));    HTTPExchange_set_method(server_exchange, HTTPExchange_get_method(client_exchange));
827    HTTPExchange_set_request_uri(server_exchange, HTTPExchange_get_request_uri(client_exchange));  
828      /* don't send full URI to origin server - just send abs_path and query */
829      const char * query = HTTPExchange_get_query(client_exchange);
830      char * origin_server_request_uri;
831      if (query == NULL) {
832        origin_server_request_uri = xstrdup(abs_path);
833      }
834      else {
835        size_t abs_path_length = strlen(abs_path);
836        size_t query_length = strlen(query);
837        size_t origin_server_request_uri_length = addst(abs_path_length, query_length);
838        origin_server_request_uri_length = addst(origin_server_request_uri_length, 2);
839        origin_server_request_uri = xmalloc(origin_server_request_uri_length);
840        strcpy(origin_server_request_uri, abs_path);
841        origin_server_request_uri[abs_path_length] = '?';
842        strcpy(origin_server_request_uri + abs_path_length + 1, query);
843      }
844      HTTPExchange_set_request_uri(server_exchange, origin_server_request_uri);
845      free(origin_server_request_uri);
846    
847    for (const HTTPHeader * h = HTTPExchange_get_request_headers(client_exchange); h != NULL; h = h->next) {    for (const HTTPHeader * h = HTTPExchange_get_request_headers(client_exchange); h != NULL; h = h->next) {
848      if (strcasecmp(h->name, HTTP_TRAILER) == 0 || strcasecmp(h->name, HTTP_TRANSFER_ENCODING) == 0) {      if (strcasecmp(h->name, HTTP_TRAILER) == 0 || strcasecmp(h->name, HTTP_TRANSFER_ENCODING) == 0) {
849        /* do nothing: we want to keep this header */        /* do nothing: we want to keep this header */
# Line 832  Line 886 
886    
887    HTTPExchange_set_status_code(client_exchange, HTTPExchange_get_status_code(server_exchange));    HTTPExchange_set_status_code(client_exchange, HTTPExchange_get_status_code(server_exchange));
888    
889    if (HTTPExchange_response_has_body(server_exchange) && should_instrument_request(server_exchange)) {    if (HTTPExchange_response_has_body(server_exchange) && should_instrument_request(server_exchange, HTTPExchange_get_request_uri(client_exchange))) {
890      /* needs instrumentation */      /* needs instrumentation */
891      Stream * input_stream = Stream_new(0);      Stream * input_stream = Stream_new(0);
892      if (HTTPExchange_read_entire_response_entity_body(server_exchange, input_stream) != 0) {      if (HTTPExchange_read_entire_response_entity_body(server_exchange, input_stream) != 0) {
# Line 852  Line 906 
906      free(encoding);      free(encoding);
907      Stream_delete(input_stream);      Stream_delete(input_stream);
908      if (result == JSCOVERAGE_ERROR_ENCODING_NOT_SUPPORTED) {      if (result == JSCOVERAGE_ERROR_ENCODING_NOT_SUPPORTED) {
909        free(characters);        send_response(client_exchange, 500, "Encoding not supported\n");
       send_response(client_exchange, 502, "Encoding not supported\n");  
910        goto done;        goto done;
911      }      }
912      else if (result == JSCOVERAGE_ERROR_INVALID_BYTE_SEQUENCE) {      else if (result == JSCOVERAGE_ERROR_INVALID_BYTE_SEQUENCE) {
       free(characters);  
913        send_response(client_exchange, 502, "Error decoding response\n");        send_response(client_exchange, 502, "Error decoding response\n");
914        goto done;        goto done;
915      }      }
# Line 870  Line 922 
922        if (is_hop_by_hop_header(h->name) || strcasecmp(h->name, HTTP_CONTENT_LENGTH) == 0) {        if (is_hop_by_hop_header(h->name) || strcasecmp(h->name, HTTP_CONTENT_LENGTH) == 0) {
923          continue;          continue;
924        }        }
925          else if (strcasecmp(h->name, HTTP_CONTENT_TYPE) == 0) {
926            HTTPExchange_add_response_header(client_exchange, HTTP_CONTENT_TYPE, "text/javascript; charset=ISO-8859-1");
927            continue;
928          }
929        HTTPExchange_add_response_header(client_exchange, h->name, h->value);        HTTPExchange_add_response_header(client_exchange, h->name, h->value);
930      }      }
931      add_via_header(HTTPExchange_get_response_message(client_exchange), HTTPExchange_get_response_http_version(server_exchange));      add_via_header(HTTPExchange_get_response_message(client_exchange), HTTPExchange_get_response_http_version(server_exchange));
# Line 933  Line 989 
989    /* add the `Server' response-header (RFC 2616 14.38, 3.8) */    /* add the `Server' response-header (RFC 2616 14.38, 3.8) */
990    HTTPExchange_add_response_header(exchange, HTTP_SERVER, "jscoverage-server/" VERSION);    HTTPExchange_add_response_header(exchange, HTTP_SERVER, "jscoverage-server/" VERSION);
991    
992      char * decoded_path = NULL;
993    char * filesystem_path = NULL;    char * filesystem_path = NULL;
994    
995    const char * abs_path = HTTPExchange_get_abs_path(exchange);    const char * abs_path = HTTPExchange_get_abs_path(exchange);
996    assert(*abs_path != '\0');    assert(*abs_path != '\0');
997    
998    if (str_starts_with(abs_path, "/jscoverage")) {    decoded_path = decode_uri_component(abs_path);
999    
1000      if (str_starts_with(decoded_path, "/jscoverage")) {
1001      handle_jscoverage_request(exchange);      handle_jscoverage_request(exchange);
1002      goto done;      goto done;
1003    }    }
1004    
1005    if (strstr(abs_path, "..") != NULL) {    if (strstr(decoded_path, "..") != NULL) {
1006      send_response(exchange, 403, "Forbidden\n");      send_response(exchange, 403, "Forbidden\n");
1007      goto done;      goto done;
1008    }    }
1009    
1010    filesystem_path = make_path(document_root, abs_path + 1);    filesystem_path = make_path(document_root, decoded_path + 1);
1011    size_t filesystem_path_length = strlen(filesystem_path);    size_t filesystem_path_length = strlen(filesystem_path);
1012    if (filesystem_path_length > 0 && filesystem_path[filesystem_path_length - 1] == '/') {    if (filesystem_path_length > 0 && filesystem_path[filesystem_path_length - 1] == '/') {
1013      /* stat on Windows doesn't work with trailing slash */      /* stat on Windows doesn't work with trailing slash */
# Line 1003  Line 1062 
1062        goto done;        goto done;
1063      }      }
1064    
1065        /*
1066        When do we send a charset with Content-Type?
1067        if Content-Type is "text" or "application"
1068          if instrumented JavaScript
1069            use Content-Type: application/javascript; charset=ISO-8859-1
1070          else if --encoding is given
1071            use that encoding
1072          else
1073            send no charset
1074        else
1075          send no charset
1076        */
1077      const char * content_type = get_content_type(filesystem_path);      const char * content_type = get_content_type(filesystem_path);
     HTTPExchange_set_response_header(exchange, HTTP_CONTENT_TYPE, content_type);  
1078      if (strcmp(content_type, "text/javascript") == 0 && ! is_no_instrument(abs_path)) {      if (strcmp(content_type, "text/javascript") == 0 && ! is_no_instrument(abs_path)) {
1079          HTTPExchange_set_response_header(exchange, HTTP_CONTENT_TYPE, "text/javascript; charset=ISO-8859-1");
1080    
1081        Stream * input_stream = Stream_new(0);        Stream * input_stream = Stream_new(0);
1082        Stream_write_file_contents(input_stream, f);        Stream_write_file_contents(input_stream, f);
1083    
# Line 1015  Line 1087 
1087        Stream_delete(input_stream);        Stream_delete(input_stream);
1088    
1089        if (result == JSCOVERAGE_ERROR_ENCODING_NOT_SUPPORTED) {        if (result == JSCOVERAGE_ERROR_ENCODING_NOT_SUPPORTED) {
         free(characters);  
1090          send_response(exchange, 500, "Encoding not supported\n");          send_response(exchange, 500, "Encoding not supported\n");
1091          goto done;          goto done;
1092        }        }
1093        else if (result == JSCOVERAGE_ERROR_INVALID_BYTE_SEQUENCE) {        else if (result == JSCOVERAGE_ERROR_INVALID_BYTE_SEQUENCE) {
         free(characters);  
1094          send_response(exchange, 500, "Error decoding JavaScript file\n");          send_response(exchange, 500, "Error decoding JavaScript file\n");
1095          goto done;          goto done;
1096        }        }
# Line 1035  Line 1105 
1105        Stream_delete(output_stream);        Stream_delete(output_stream);
1106      }      }
1107      else {      else {
1108          /* send the Content-Type with charset if necessary */
1109          if (specified_encoding != NULL && (str_starts_with(content_type, "text/") || str_starts_with(content_type, "application/"))) {
1110            char * content_type_with_charset = NULL;
1111            xasprintf(&content_type_with_charset, "%s; charset=%s", content_type, specified_encoding);
1112            HTTPExchange_set_response_header(exchange, HTTP_CONTENT_TYPE, content_type_with_charset);
1113            free(content_type_with_charset);
1114          }
1115          else {
1116            HTTPExchange_set_response_header(exchange, HTTP_CONTENT_TYPE, content_type);
1117          }
1118    
1119        char buffer[8192];        char buffer[8192];
1120        size_t bytes_read;        size_t bytes_read;
1121        while ((bytes_read = fread(buffer, 1, 8192, f)) > 0) {        while ((bytes_read = fread(buffer, 1, 8192, f)) > 0) {
# Line 1052  Line 1133 
1133    
1134  done:  done:
1135    free(filesystem_path);    free(filesystem_path);
1136      free(decoded_path);
1137  }  }
1138    
1139  static void handler(HTTPExchange * exchange) {  static void handler(HTTPExchange * exchange) {
# Line 1082  Line 1164 
1164        exit(EXIT_SUCCESS);        exit(EXIT_SUCCESS);
1165      }      }
1166      else if (strcmp(argv[i], "-V") == 0 || strcmp(argv[i], "--version") == 0) {      else if (strcmp(argv[i], "-V") == 0 || strcmp(argv[i], "--version") == 0) {
1167        printf("jscoverage-server %s\n", VERSION);        version();
       exit(EXIT_SUCCESS);  
1168      }      }
1169      else if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--verbose") == 0) {      else if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--verbose") == 0) {
1170        verbose = 1;        verbose = 1;
# Line 1092  Line 1173 
1173      else if (strcmp(argv[i], "--report-dir") == 0) {      else if (strcmp(argv[i], "--report-dir") == 0) {
1174        i++;        i++;
1175        if (i == argc) {        if (i == argc) {
1176          fatal("--report-dir: option requires an argument");          fatal_command_line("--report-dir: option requires an argument");
1177        }        }
1178        report_directory = argv[i];        report_directory = argv[i];
1179      }      }
# Line 1103  Line 1184 
1184      else if (strcmp(argv[i], "--document-root") == 0) {      else if (strcmp(argv[i], "--document-root") == 0) {
1185        i++;        i++;
1186        if (i == argc) {        if (i == argc) {
1187          fatal("--document-root: option requires an argument");          fatal_command_line("--document-root: option requires an argument");
1188        }        }
1189        document_root = argv[i];        document_root = argv[i];
1190      }      }
# Line 1114  Line 1195 
1195      else if (strcmp(argv[i], "--encoding") == 0) {      else if (strcmp(argv[i], "--encoding") == 0) {
1196        i++;        i++;
1197        if (i == argc) {        if (i == argc) {
1198          fatal("--encoding: option requires an argument");          fatal_command_line("--encoding: option requires an argument");
1199        }        }
1200        jscoverage_encoding = argv[i];        jscoverage_encoding = argv[i];
1201          specified_encoding = jscoverage_encoding;
1202      }      }
1203      else if (strncmp(argv[i], "--encoding=", 11) == 0) {      else if (strncmp(argv[i], "--encoding=", 11) == 0) {
1204        jscoverage_encoding = argv[i] + 11;        jscoverage_encoding = argv[i] + 11;
1205          specified_encoding = jscoverage_encoding;
1206      }      }
1207    
1208      else if (strcmp(argv[i], "--ip-address") == 0) {      else if (strcmp(argv[i], "--ip-address") == 0) {
1209        i++;        i++;
1210        if (i == argc) {        if (i == argc) {
1211          fatal("--ip-address: option requires an argument");          fatal_command_line("--ip-address: option requires an argument");
1212        }        }
1213        ip_address = argv[i];        ip_address = argv[i];
1214      }      }
# Line 1133  Line 1216 
1216        ip_address = argv[i] + 13;        ip_address = argv[i] + 13;
1217      }      }
1218    
1219        else if (strcmp(argv[i], "--js-version") == 0) {
1220          i++;
1221          if (i == argc) {
1222            fatal_command_line("--js-version: option requires an argument");
1223          }
1224          jscoverage_set_js_version(argv[i]);
1225        }
1226        else if (strncmp(argv[i], "--js-version=", 13) == 0) {
1227          jscoverage_set_js_version(argv[i] + 13);
1228        }
1229    
1230      else if (strcmp(argv[i], "--no-highlight") == 0) {      else if (strcmp(argv[i], "--no-highlight") == 0) {
1231        jscoverage_highlight = false;        jscoverage_highlight = false;
1232      }      }
# Line 1140  Line 1234 
1234      else if (strcmp(argv[i], "--no-instrument") == 0) {      else if (strcmp(argv[i], "--no-instrument") == 0) {
1235        i++;        i++;
1236        if (i == argc) {        if (i == argc) {
1237          fatal("--no-instrument: option requires an argument");          fatal_command_line("--no-instrument: option requires an argument");
1238        }        }
1239        no_instrument[num_no_instrument] = argv[i];        no_instrument[num_no_instrument] = argv[i];
1240        num_no_instrument++;        num_no_instrument++;
# Line 1153  Line 1247 
1247      else if (strcmp(argv[i], "--port") == 0) {      else if (strcmp(argv[i], "--port") == 0) {
1248        i++;        i++;
1249        if (i == argc) {        if (i == argc) {
1250          fatal("--port: option requires an argument");          fatal_command_line("--port: option requires an argument");
1251        }        }
1252        port = argv[i];        port = argv[i];
1253      }      }
# Line 1170  Line 1264 
1264      }      }
1265    
1266      else if (strncmp(argv[i], "-", 1) == 0) {      else if (strncmp(argv[i], "-", 1) == 0) {
1267        fatal("unrecognized option `%s'", argv[i]);        fatal_command_line("unrecognized option `%s'", argv[i]);
1268      }      }
1269      else {      else {
1270        fatal("too many arguments");        fatal_command_line("too many arguments");
1271      }      }
1272    }    }
1273    
# Line 1181  Line 1275 
1275    char * end;    char * end;
1276    unsigned long numeric_port = strtoul(port, &end, 10);    unsigned long numeric_port = strtoul(port, &end, 10);
1277    if (*end != '\0') {    if (*end != '\0') {
1278      fatal("--port: option must be an integer");      fatal_command_line("--port: option must be an integer");
1279    }    }
1280    if (numeric_port > UINT16_MAX) {    if (numeric_port > UINT16_MAX) {
1281      fatal("--port: option must be 16 bits");      fatal_command_line("--port: option must be 16 bits");
1282      }
1283    
1284      /* check the document root exists and is a directory */
1285      struct stat buf;
1286      xstat(document_root, &buf);
1287      if (! S_ISDIR(buf.st_mode)) {
1288        fatal_command_line("--document-root: option must be a directory");
1289    }    }
1290    
1291    /* is this a shutdown? */    /* is this a shutdown? */
# Line 1196  Line 1297 
1297      }      }
1298  #endif  #endif
1299    
1300      /* INADDR_LOOPBACK */      HTTPConnection * connection = HTTPConnection_new_client(ip_address, numeric_port);
     HTTPConnection * connection = HTTPConnection_new_client("127.0.0.1", numeric_port);  
1301      if (connection == NULL) {      if (connection == NULL) {
1302        fatal("could not connect to server");        fatal("could not connect to server");
1303      }      }
# Line 1239  Line 1339 
1339      printf("Starting HTTP server on %s:%lu\n", ip_address, numeric_port);      printf("Starting HTTP server on %s:%lu\n", ip_address, numeric_port);
1340      fflush(stdout);      fflush(stdout);
1341    }    }
1342    
1343      /* set the IP address */
1344      if (! inet_aton(ip_address, &jscoverage_ip_address)) {
1345        fatal("invalid IP address: %s\n", ip_address);
1346      }
1347    
1348    HTTPServer_run(ip_address, (uint16_t) numeric_port, handler);    HTTPServer_run(ip_address, (uint16_t) numeric_port, handler);
1349    if (verbose) {    if (verbose) {
1350      printf("Stopping HTTP server\n");      printf("Stopping HTTP server\n");

Legend:
Removed from v.189  
changed lines
  Added in v.583

  ViewVC Help
Powered by ViewVC 1.1.24