22 |
#include <assert.h> |
#include <assert.h> |
23 |
#include <ctype.h> |
#include <ctype.h> |
24 |
#include <signal.h> |
#include <signal.h> |
25 |
|
#include <stdint.h> |
26 |
#include <string.h> |
#include <string.h> |
27 |
|
|
28 |
#include <dirent.h> |
#include <dirent.h> |
30 |
#include <pthread.h> |
#include <pthread.h> |
31 |
#endif |
#endif |
32 |
|
|
33 |
|
#include "encoding.h" |
34 |
|
#include "global.h" |
35 |
#include "http-server.h" |
#include "http-server.h" |
36 |
#include "instrument-js.h" |
#include "instrument-js.h" |
37 |
#include "resource-manager.h" |
#include "resource-manager.h" |
38 |
#include "stream.h" |
#include "stream.h" |
39 |
#include "util.h" |
#include "util.h" |
40 |
|
|
41 |
|
const char * jscoverage_encoding = "ISO-8859-1"; |
42 |
|
bool jscoverage_highlight = true; |
43 |
|
|
44 |
typedef struct SourceCache { |
typedef struct SourceCache { |
45 |
char * url; |
char * url; |
46 |
Stream * source; |
uint16_t * characters; |
47 |
|
size_t num_characters; |
48 |
struct SourceCache * next; |
struct SourceCache * next; |
49 |
} SourceCache; |
} SourceCache; |
50 |
|
|
85 |
#define UNLOCK pthread_mutex_unlock |
#define UNLOCK pthread_mutex_unlock |
86 |
#endif |
#endif |
87 |
|
|
88 |
static Stream * find_cached_source(const char * url) { |
static const SourceCache * find_cached_source(const char * url) { |
89 |
Stream * result = NULL; |
SourceCache * result = NULL; |
90 |
LOCK(&source_cache_mutex); |
LOCK(&source_cache_mutex); |
91 |
for (SourceCache * p = source_cache; p != NULL; p = p->next) { |
for (SourceCache * p = source_cache; p != NULL; p = p->next) { |
92 |
if (strcmp(url, p->url) == 0) { |
if (strcmp(url, p->url) == 0) { |
93 |
result = p->source; |
result = p; |
94 |
break; |
break; |
95 |
} |
} |
96 |
} |
} |
98 |
return result; |
return result; |
99 |
} |
} |
100 |
|
|
101 |
static void add_cached_source(const char * url, Stream * source) { |
static void add_cached_source(const char * url, uint16_t * characters, size_t num_characters) { |
102 |
SourceCache * new_source_cache = xmalloc(sizeof(SourceCache)); |
SourceCache * new_source_cache = xmalloc(sizeof(SourceCache)); |
103 |
new_source_cache->url = xstrdup(url); |
new_source_cache->url = xstrdup(url); |
104 |
new_source_cache->source = source; |
new_source_cache->characters = characters; |
105 |
|
new_source_cache->num_characters = num_characters; |
106 |
LOCK(&source_cache_mutex); |
LOCK(&source_cache_mutex); |
107 |
new_source_cache->next = source_cache; |
new_source_cache->next = source_cache; |
108 |
source_cache = new_source_cache; |
source_cache = new_source_cache; |
109 |
UNLOCK(&source_cache_mutex); |
UNLOCK(&source_cache_mutex); |
110 |
} |
} |
111 |
|
|
112 |
static int get(const char * url, Stream * stream) __attribute__((warn_unused_result)); |
static int get(const char * url, uint16_t ** characters, size_t * num_characters) __attribute__((warn_unused_result)); |
113 |
|
|
114 |
static int get(const char * url, Stream * stream) { |
static int get(const char * url, uint16_t ** characters, size_t * num_characters) { |
115 |
char * host = NULL; |
char * host = NULL; |
116 |
uint16_t port; |
uint16_t port; |
117 |
char * abs_path = NULL; |
char * abs_path = NULL; |
118 |
char * query = NULL; |
char * query = NULL; |
119 |
HTTPConnection * connection = NULL; |
HTTPConnection * connection = NULL; |
120 |
HTTPExchange * exchange = NULL; |
HTTPExchange * exchange = NULL; |
121 |
|
Stream * stream = NULL; |
122 |
|
|
123 |
int result = URL_parse(url, &host, &port, &abs_path, &query); |
int result = URL_parse(url, &host, &port, &abs_path, &query); |
124 |
if (result != 0) { |
if (result != 0) { |
143 |
goto done; |
goto done; |
144 |
} |
} |
145 |
|
|
146 |
|
stream = Stream_new(0); |
147 |
result = HTTPExchange_read_entire_response_entity_body(exchange, stream); |
result = HTTPExchange_read_entire_response_entity_body(exchange, stream); |
148 |
if (result != 0) { |
if (result != 0) { |
149 |
goto done; |
goto done; |
150 |
} |
} |
151 |
|
char * encoding = HTTPMessage_get_charset(HTTPExchange_get_response_message(exchange)); |
152 |
|
if (encoding == NULL) { |
153 |
|
encoding = xstrdup(jscoverage_encoding); |
154 |
|
} |
155 |
|
result = jscoverage_bytes_to_characters(encoding, stream->data, stream->length, characters, num_characters); |
156 |
|
free(encoding); |
157 |
|
if (result != 0) { |
158 |
|
goto done; |
159 |
|
} |
160 |
|
|
161 |
result = 0; |
result = 0; |
162 |
|
|
163 |
done: |
done: |
164 |
|
if (stream != NULL) { |
165 |
|
Stream_delete(stream); |
166 |
|
} |
167 |
if (exchange != NULL) { |
if (exchange != NULL) { |
168 |
HTTPExchange_delete(exchange); |
HTTPExchange_delete(exchange); |
169 |
} |
} |
237 |
return result; |
return result; |
238 |
} |
} |
239 |
|
|
240 |
|
static unsigned int hex_value(char c) { |
241 |
|
if ('0' <= c && c <= '9') { |
242 |
|
return c - '0'; |
243 |
|
} |
244 |
|
else if ('A' <= c && c <= 'F') { |
245 |
|
return c - 'A'; |
246 |
|
} |
247 |
|
else if ('a' <= c && c <= 'f') { |
248 |
|
return c - 'a'; |
249 |
|
} |
250 |
|
else { |
251 |
|
return 0; |
252 |
|
} |
253 |
|
} |
254 |
|
|
255 |
|
static char * decode_uri_component(const char * s) { |
256 |
|
size_t length = strlen(s); |
257 |
|
char * result = xmalloc(length + 1); |
258 |
|
char * p = result; |
259 |
|
while (*s != '\0') { |
260 |
|
if (*s == '%') { |
261 |
|
if (s[1] == '\0' || s[2] == '\0') { |
262 |
|
*p = '\0'; |
263 |
|
return result; |
264 |
|
} |
265 |
|
*p = hex_value(s[1]) * 16 + hex_value(s[2]); |
266 |
|
s += 2; |
267 |
|
} |
268 |
|
else { |
269 |
|
*p = *s; |
270 |
|
} |
271 |
|
p++; |
272 |
|
s++; |
273 |
|
} |
274 |
|
*p = '\0'; |
275 |
|
return result; |
276 |
|
} |
277 |
|
|
278 |
static const char * get_entity(char c) { |
static const char * get_entity(char c) { |
279 |
switch(c) { |
switch(c) { |
280 |
case '<': |
case '<': |
460 |
putc('"', f); |
putc('"', f); |
461 |
} |
} |
462 |
|
|
463 |
|
static void write_source(const char * id, const uint16_t * characters, size_t num_characters, FILE * f) { |
464 |
|
Stream * output = Stream_new(num_characters); |
465 |
|
jscoverage_write_source(id, characters, num_characters, output); |
466 |
|
fwrite(output->data, 1, output->length, f); |
467 |
|
Stream_delete(output); |
468 |
|
} |
469 |
|
|
470 |
static void write_json_for_file(const FileCoverage * file_coverage, int i, void * p) { |
static void write_json_for_file(const FileCoverage * file_coverage, int i, void * p) { |
471 |
FILE * f = p; |
FILE * f = p; |
472 |
|
|
477 |
write_js_quoted_string(f, file_coverage->id, strlen(file_coverage->id)); |
write_js_quoted_string(f, file_coverage->id, strlen(file_coverage->id)); |
478 |
|
|
479 |
fputs(":{\"coverage\":[", f); |
fputs(":{\"coverage\":[", f); |
480 |
for (uint32_t i = 0; i <= file_coverage->num_lines; i++) { |
for (uint32_t i = 0; i < file_coverage->num_coverage_lines; i++) { |
481 |
if (i > 0) { |
if (i > 0) { |
482 |
putc(',', f); |
putc(',', f); |
483 |
} |
} |
484 |
int timesExecuted = file_coverage->lines[i]; |
int timesExecuted = file_coverage->coverage_lines[i]; |
485 |
if (timesExecuted < 0) { |
if (timesExecuted < 0) { |
486 |
fputs("null", f); |
fputs("null", f); |
487 |
} |
} |
490 |
} |
} |
491 |
} |
} |
492 |
fputs("],\"source\":", f); |
fputs("],\"source\":", f); |
493 |
if (file_coverage->source == NULL) { |
if (file_coverage->source_lines == NULL) { |
494 |
if (proxy) { |
if (proxy) { |
495 |
Stream * stream = find_cached_source(file_coverage->id); |
const SourceCache * cached = find_cached_source(file_coverage->id); |
496 |
if (stream == NULL) { |
if (cached == NULL) { |
497 |
stream = Stream_new(0); |
uint16_t * characters; |
498 |
if (get(file_coverage->id, stream) == 0) { |
size_t num_characters; |
499 |
write_js_quoted_string(f, stream->data, stream->length); |
if (get(file_coverage->id, &characters, &num_characters) == 0) { |
500 |
add_cached_source(file_coverage->id, stream); |
write_source(file_coverage->id, characters, num_characters, f); |
501 |
|
add_cached_source(file_coverage->id, characters, num_characters); |
502 |
} |
} |
503 |
else { |
else { |
504 |
fputs("\"\"", f); |
fputs("[]", f); |
505 |
HTTPServer_log_err("Warning: cannot retrieve URL: %s\n", file_coverage->id); |
HTTPServer_log_err("Warning: cannot retrieve URL: %s\n", file_coverage->id); |
|
Stream_delete(stream); |
|
506 |
} |
} |
507 |
} |
} |
508 |
else { |
else { |
509 |
write_js_quoted_string(f, stream->data, stream->length); |
write_source(file_coverage->id, cached->characters, cached->num_characters, f); |
510 |
} |
} |
511 |
} |
} |
512 |
else { |
else { |
516 |
FILE * source_file = fopen(source_path, "rb"); |
FILE * source_file = fopen(source_path, "rb"); |
517 |
free(source_path); |
free(source_path); |
518 |
if (source_file == NULL) { |
if (source_file == NULL) { |
519 |
fputs("\"\"", f); |
fputs("[]", f); |
520 |
HTTPServer_log_err("Warning: cannot open file: %s\n", file_coverage->id); |
HTTPServer_log_err("Warning: cannot open file: %s\n", file_coverage->id); |
521 |
} |
} |
522 |
else { |
else { |
523 |
Stream * stream = Stream_new(0); |
Stream * stream = Stream_new(0); |
524 |
Stream_write_file_contents(stream, source_file); |
Stream_write_file_contents(stream, source_file); |
525 |
fclose(source_file); |
fclose(source_file); |
526 |
write_js_quoted_string(f, stream->data, stream->length); |
uint16_t * characters; |
527 |
|
size_t num_characters; |
528 |
|
int result = jscoverage_bytes_to_characters(jscoverage_encoding, stream->data, stream->length, &characters, &num_characters); |
529 |
Stream_delete(stream); |
Stream_delete(stream); |
530 |
|
if (result == JSCOVERAGE_ERROR_ENCODING_NOT_SUPPORTED) { |
531 |
|
fputs("[]", f); |
532 |
|
HTTPServer_log_err("Warning: encoding %s not supported\n", jscoverage_encoding); |
533 |
|
} |
534 |
|
else if (result == JSCOVERAGE_ERROR_INVALID_BYTE_SEQUENCE) { |
535 |
|
fputs("[]", f); |
536 |
|
HTTPServer_log_err("Warning: error decoding %s in file %s\n", jscoverage_encoding, file_coverage->id); |
537 |
|
} |
538 |
|
else { |
539 |
|
write_source(file_coverage->id, characters, num_characters, f); |
540 |
|
free(characters); |
541 |
|
} |
542 |
} |
} |
543 |
} |
} |
544 |
else { |
else { |
545 |
/* path does not begin with / */ |
/* path does not begin with / */ |
546 |
fputs("\"\"", f); |
fputs("[]", f); |
547 |
HTTPServer_log_err("Warning: invalid source path: %s\n", file_coverage->id); |
HTTPServer_log_err("Warning: invalid source path: %s\n", file_coverage->id); |
548 |
} |
} |
549 |
} |
} |
550 |
} |
} |
551 |
else { |
else { |
552 |
write_js_quoted_string(f, file_coverage->source, strlen(file_coverage->source)); |
fputc('[', f); |
553 |
|
for (uint32_t i = 0; i < file_coverage->num_source_lines; i++) { |
554 |
|
if (i > 0) { |
555 |
|
fputc(',', f); |
556 |
|
} |
557 |
|
char * source_line = file_coverage->source_lines[i]; |
558 |
|
write_js_quoted_string(f, source_line, strlen(source_line)); |
559 |
|
} |
560 |
|
fputc(']', f); |
561 |
} |
} |
562 |
fputc('}', f); |
fputc('}', f); |
563 |
} |
} |
614 |
} |
} |
615 |
|
|
616 |
mkdir_if_necessary(report_directory); |
mkdir_if_necessary(report_directory); |
617 |
char * path = make_path(report_directory, "jscoverage.json"); |
char * current_report_directory; |
618 |
|
if (str_starts_with(abs_path, "/jscoverage-store/") && abs_path[18] != '\0') { |
619 |
|
char * dir = decode_uri_component(abs_path + 18); |
620 |
|
current_report_directory = make_path(report_directory, dir); |
621 |
|
free(dir); |
622 |
|
} |
623 |
|
else { |
624 |
|
current_report_directory = xstrdup(report_directory); |
625 |
|
} |
626 |
|
mkdir_if_necessary(current_report_directory); |
627 |
|
char * path = make_path(current_report_directory, "jscoverage.json"); |
628 |
|
|
629 |
/* check if the JSON file exists */ |
/* check if the JSON file exists */ |
630 |
struct stat buf; |
struct stat buf; |
641 |
} |
} |
642 |
} |
} |
643 |
if (result != 0) { |
if (result != 0) { |
644 |
|
free(current_report_directory); |
645 |
free(path); |
free(path); |
646 |
Coverage_delete(coverage); |
Coverage_delete(coverage); |
647 |
send_response(exchange, 500, "Could not merge with existing coverage data\n"); |
send_response(exchange, 500, "Could not merge with existing coverage data\n"); |
653 |
free(path); |
free(path); |
654 |
Coverage_delete(coverage); |
Coverage_delete(coverage); |
655 |
if (result != 0) { |
if (result != 0) { |
656 |
|
free(current_report_directory); |
657 |
send_response(exchange, 500, "Could not write coverage data\n"); |
send_response(exchange, 500, "Could not write coverage data\n"); |
658 |
return; |
return; |
659 |
} |
} |
660 |
|
|
661 |
/* copy other files */ |
/* copy other files */ |
662 |
jscoverage_copy_resources(report_directory); |
jscoverage_copy_resources(current_report_directory); |
663 |
path = make_path(report_directory, "jscoverage.js"); |
path = make_path(current_report_directory, "jscoverage.js"); |
664 |
|
free(current_report_directory); |
665 |
FILE * f = fopen(path, "ab"); |
FILE * f = fopen(path, "ab"); |
666 |
free(path); |
free(path); |
667 |
if (f == NULL) { |
if (f == NULL) { |
718 |
} |
} |
719 |
} |
} |
720 |
|
|
721 |
static void instrument_js(const char * id, Stream * input_stream, Stream * output_stream) { |
static void instrument_js(const char * id, const uint16_t * characters, size_t num_characters, Stream * output_stream) { |
|
LOCK(&javascript_mutex); |
|
|
jscoverage_instrument_js(id, input_stream, output_stream); |
|
|
UNLOCK(&javascript_mutex); |
|
|
|
|
722 |
const struct Resource * resource = get_resource("report.js"); |
const struct Resource * resource = get_resource("report.js"); |
723 |
Stream_write(output_stream, resource->data, resource->length); |
Stream_write(output_stream, resource->data, resource->length); |
724 |
|
|
725 |
|
LOCK(&javascript_mutex); |
726 |
|
jscoverage_instrument_js(id, characters, num_characters, output_stream); |
727 |
|
UNLOCK(&javascript_mutex); |
728 |
} |
} |
729 |
|
|
730 |
static bool is_hop_by_hop_header(const char * h) { |
static bool is_hop_by_hop_header(const char * h) { |
842 |
} |
} |
843 |
|
|
844 |
const char * request_uri = HTTPExchange_get_request_uri(client_exchange); |
const char * request_uri = HTTPExchange_get_request_uri(client_exchange); |
845 |
|
char * encoding = HTTPMessage_get_charset(HTTPExchange_get_response_message(server_exchange)); |
846 |
|
if (encoding == NULL) { |
847 |
|
encoding = xstrdup(jscoverage_encoding); |
848 |
|
} |
849 |
|
uint16_t * characters; |
850 |
|
size_t num_characters; |
851 |
|
int result = jscoverage_bytes_to_characters(encoding, input_stream->data, input_stream->length, &characters, &num_characters); |
852 |
|
free(encoding); |
853 |
|
Stream_delete(input_stream); |
854 |
|
if (result == JSCOVERAGE_ERROR_ENCODING_NOT_SUPPORTED) { |
855 |
|
free(characters); |
856 |
|
send_response(client_exchange, 502, "Encoding not supported\n"); |
857 |
|
goto done; |
858 |
|
} |
859 |
|
else if (result == JSCOVERAGE_ERROR_INVALID_BYTE_SEQUENCE) { |
860 |
|
free(characters); |
861 |
|
send_response(client_exchange, 502, "Error decoding response\n"); |
862 |
|
goto done; |
863 |
|
} |
864 |
|
|
865 |
Stream * output_stream = Stream_new(0); |
Stream * output_stream = Stream_new(0); |
866 |
instrument_js(request_uri, input_stream, output_stream); |
instrument_js(request_uri, characters, num_characters, output_stream); |
867 |
|
|
868 |
/* send the headers to the client */ |
/* send the headers to the client */ |
869 |
for (const HTTPHeader * h = HTTPExchange_get_response_headers(server_exchange); h != NULL; h = h->next) { |
for (const HTTPHeader * h = HTTPExchange_get_response_headers(server_exchange); h != NULL; h = h->next) { |
880 |
HTTPServer_log_err("Warning: error writing to client\n"); |
HTTPServer_log_err("Warning: error writing to client\n"); |
881 |
} |
} |
882 |
|
|
883 |
/* input_stream goes on the cache */ |
/* characters go on the cache */ |
884 |
/* |
/* |
885 |
Stream_delete(input_stream); |
free(characters); |
886 |
*/ |
*/ |
887 |
Stream_delete(output_stream); |
Stream_delete(output_stream); |
888 |
add_cached_source(request_uri, input_stream); |
add_cached_source(request_uri, characters, num_characters); |
889 |
} |
} |
890 |
else { |
else { |
891 |
/* does not need instrumentation */ |
/* does not need instrumentation */ |
1007 |
HTTPExchange_set_response_header(exchange, HTTP_CONTENT_TYPE, content_type); |
HTTPExchange_set_response_header(exchange, HTTP_CONTENT_TYPE, content_type); |
1008 |
if (strcmp(content_type, "text/javascript") == 0 && ! is_no_instrument(abs_path)) { |
if (strcmp(content_type, "text/javascript") == 0 && ! is_no_instrument(abs_path)) { |
1009 |
Stream * input_stream = Stream_new(0); |
Stream * input_stream = Stream_new(0); |
|
Stream * output_stream = Stream_new(0); |
|
|
|
|
1010 |
Stream_write_file_contents(input_stream, f); |
Stream_write_file_contents(input_stream, f); |
1011 |
|
|
1012 |
instrument_js(abs_path, input_stream, output_stream); |
uint16_t * characters; |
1013 |
|
size_t num_characters; |
1014 |
|
int result = jscoverage_bytes_to_characters(jscoverage_encoding, input_stream->data, input_stream->length, &characters, &num_characters); |
1015 |
|
Stream_delete(input_stream); |
1016 |
|
|
1017 |
|
if (result == JSCOVERAGE_ERROR_ENCODING_NOT_SUPPORTED) { |
1018 |
|
free(characters); |
1019 |
|
send_response(exchange, 500, "Encoding not supported\n"); |
1020 |
|
goto done; |
1021 |
|
} |
1022 |
|
else if (result == JSCOVERAGE_ERROR_INVALID_BYTE_SEQUENCE) { |
1023 |
|
free(characters); |
1024 |
|
send_response(exchange, 500, "Error decoding JavaScript file\n"); |
1025 |
|
goto done; |
1026 |
|
} |
1027 |
|
|
1028 |
|
Stream * output_stream = Stream_new(0); |
1029 |
|
instrument_js(abs_path, characters, num_characters, output_stream); |
1030 |
|
free(characters); |
1031 |
|
|
1032 |
if (HTTPExchange_write_response(exchange, output_stream->data, output_stream->length) != 0) { |
if (HTTPExchange_write_response(exchange, output_stream->data, output_stream->length) != 0) { |
1033 |
HTTPServer_log_err("Warning: error writing to client\n"); |
HTTPServer_log_err("Warning: error writing to client\n"); |
1034 |
} |
} |
|
|
|
|
Stream_delete(input_stream); |
|
1035 |
Stream_delete(output_stream); |
Stream_delete(output_stream); |
1036 |
} |
} |
1037 |
else { |
else { |
1111 |
document_root = argv[i] + 16; |
document_root = argv[i] + 16; |
1112 |
} |
} |
1113 |
|
|
1114 |
|
else if (strcmp(argv[i], "--encoding") == 0) { |
1115 |
|
i++; |
1116 |
|
if (i == argc) { |
1117 |
|
fatal("--encoding: option requires an argument"); |
1118 |
|
} |
1119 |
|
jscoverage_encoding = argv[i]; |
1120 |
|
} |
1121 |
|
else if (strncmp(argv[i], "--encoding=", 11) == 0) { |
1122 |
|
jscoverage_encoding = argv[i] + 11; |
1123 |
|
} |
1124 |
|
|
1125 |
else if (strcmp(argv[i], "--ip-address") == 0) { |
else if (strcmp(argv[i], "--ip-address") == 0) { |
1126 |
i++; |
i++; |
1127 |
if (i == argc) { |
if (i == argc) { |
1133 |
ip_address = argv[i] + 13; |
ip_address = argv[i] + 13; |
1134 |
} |
} |
1135 |
|
|
1136 |
|
else if (strcmp(argv[i], "--no-highlight") == 0) { |
1137 |
|
jscoverage_highlight = false; |
1138 |
|
} |
1139 |
|
|
1140 |
else if (strcmp(argv[i], "--no-instrument") == 0) { |
else if (strcmp(argv[i], "--no-instrument") == 0) { |
1141 |
i++; |
i++; |
1142 |
if (i == argc) { |
if (i == argc) { |
1254 |
SourceCache * p = source_cache; |
SourceCache * p = source_cache; |
1255 |
source_cache = source_cache->next; |
source_cache = source_cache->next; |
1256 |
free(p->url); |
free(p->url); |
1257 |
Stream_delete(p->source); |
free(p->characters); |
1258 |
free(p); |
free(p); |
1259 |
} |
} |
1260 |
UNLOCK(&source_cache_mutex); |
UNLOCK(&source_cache_mutex); |