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 |
} |
} |
422 |
putc('"', f); |
putc('"', f); |
423 |
} |
} |
424 |
|
|
425 |
|
static void write_source(const char * id, const uint16_t * characters, size_t num_characters, FILE * f) { |
426 |
|
Stream * output = Stream_new(num_characters); |
427 |
|
jscoverage_write_source(id, characters, num_characters, output); |
428 |
|
fwrite(output->data, 1, output->length, f); |
429 |
|
Stream_delete(output); |
430 |
|
} |
431 |
|
|
432 |
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) { |
433 |
FILE * f = p; |
FILE * f = p; |
434 |
|
|
439 |
write_js_quoted_string(f, file_coverage->id, strlen(file_coverage->id)); |
write_js_quoted_string(f, file_coverage->id, strlen(file_coverage->id)); |
440 |
|
|
441 |
fputs(":{\"coverage\":[", f); |
fputs(":{\"coverage\":[", f); |
442 |
for (uint32_t i = 0; i <= file_coverage->num_lines; i++) { |
for (uint32_t i = 0; i < file_coverage->num_coverage_lines; i++) { |
443 |
if (i > 0) { |
if (i > 0) { |
444 |
putc(',', f); |
putc(',', f); |
445 |
} |
} |
446 |
int timesExecuted = file_coverage->lines[i]; |
int timesExecuted = file_coverage->coverage_lines[i]; |
447 |
if (timesExecuted < 0) { |
if (timesExecuted < 0) { |
448 |
fputs("null", f); |
fputs("null", f); |
449 |
} |
} |
452 |
} |
} |
453 |
} |
} |
454 |
fputs("],\"source\":", f); |
fputs("],\"source\":", f); |
455 |
if (file_coverage->source == NULL) { |
if (file_coverage->source_lines == NULL) { |
456 |
if (proxy) { |
if (proxy) { |
457 |
Stream * stream = find_cached_source(file_coverage->id); |
const SourceCache * cached = find_cached_source(file_coverage->id); |
458 |
if (stream == NULL) { |
if (cached == NULL) { |
459 |
stream = Stream_new(0); |
uint16_t * characters; |
460 |
if (get(file_coverage->id, stream) == 0) { |
size_t num_characters; |
461 |
write_js_quoted_string(f, stream->data, stream->length); |
if (get(file_coverage->id, &characters, &num_characters) == 0) { |
462 |
add_cached_source(file_coverage->id, stream); |
write_source(file_coverage->id, characters, num_characters, f); |
463 |
|
add_cached_source(file_coverage->id, characters, num_characters); |
464 |
} |
} |
465 |
else { |
else { |
466 |
fputs("\"\"", f); |
fputs("[]", f); |
467 |
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); |
|
468 |
} |
} |
469 |
} |
} |
470 |
else { |
else { |
471 |
write_js_quoted_string(f, stream->data, stream->length); |
write_source(file_coverage->id, cached->characters, cached->num_characters, f); |
472 |
} |
} |
473 |
} |
} |
474 |
else { |
else { |
478 |
FILE * source_file = fopen(source_path, "rb"); |
FILE * source_file = fopen(source_path, "rb"); |
479 |
free(source_path); |
free(source_path); |
480 |
if (source_file == NULL) { |
if (source_file == NULL) { |
481 |
fputs("\"\"", f); |
fputs("[]", f); |
482 |
HTTPServer_log_err("Warning: cannot open file: %s\n", file_coverage->id); |
HTTPServer_log_err("Warning: cannot open file: %s\n", file_coverage->id); |
483 |
} |
} |
484 |
else { |
else { |
485 |
Stream * stream = Stream_new(0); |
Stream * stream = Stream_new(0); |
486 |
Stream_write_file_contents(stream, source_file); |
Stream_write_file_contents(stream, source_file); |
487 |
fclose(source_file); |
fclose(source_file); |
488 |
write_js_quoted_string(f, stream->data, stream->length); |
uint16_t * characters; |
489 |
|
size_t num_characters; |
490 |
|
int result = jscoverage_bytes_to_characters(jscoverage_encoding, stream->data, stream->length, &characters, &num_characters); |
491 |
Stream_delete(stream); |
Stream_delete(stream); |
492 |
|
if (result == JSCOVERAGE_ERROR_ENCODING_NOT_SUPPORTED) { |
493 |
|
fputs("[]", f); |
494 |
|
HTTPServer_log_err("Warning: encoding %s not supported\n", jscoverage_encoding); |
495 |
|
} |
496 |
|
else if (result == JSCOVERAGE_ERROR_INVALID_BYTE_SEQUENCE) { |
497 |
|
fputs("[]", f); |
498 |
|
HTTPServer_log_err("Warning: error decoding %s in file %s\n", jscoverage_encoding, file_coverage->id); |
499 |
|
} |
500 |
|
else { |
501 |
|
write_source(file_coverage->id, characters, num_characters, f); |
502 |
|
free(characters); |
503 |
|
} |
504 |
} |
} |
505 |
} |
} |
506 |
else { |
else { |
507 |
/* path does not begin with / */ |
/* path does not begin with / */ |
508 |
fputs("\"\"", f); |
fputs("[]", f); |
509 |
HTTPServer_log_err("Warning: invalid source path: %s\n", file_coverage->id); |
HTTPServer_log_err("Warning: invalid source path: %s\n", file_coverage->id); |
510 |
} |
} |
511 |
} |
} |
512 |
} |
} |
513 |
else { |
else { |
514 |
write_js_quoted_string(f, file_coverage->source, strlen(file_coverage->source)); |
fputc('[', f); |
515 |
|
for (uint32_t i = 0; i < file_coverage->num_source_lines; i++) { |
516 |
|
if (i > 0) { |
517 |
|
fputc(',', f); |
518 |
|
} |
519 |
|
char * source_line = file_coverage->source_lines[i]; |
520 |
|
write_js_quoted_string(f, source_line, strlen(source_line)); |
521 |
|
} |
522 |
|
fputc(']', f); |
523 |
} |
} |
524 |
fputc('}', f); |
fputc('}', f); |
525 |
} |
} |
577 |
|
|
578 |
mkdir_if_necessary(report_directory); |
mkdir_if_necessary(report_directory); |
579 |
char * path = make_path(report_directory, "jscoverage.json"); |
char * path = make_path(report_directory, "jscoverage.json"); |
580 |
FILE * f = fopen(path, "r"); |
|
581 |
if (f != NULL) { |
/* check if the JSON file exists */ |
582 |
|
struct stat buf; |
583 |
|
if (stat(path, &buf) == 0) { |
584 |
/* it exists: merge */ |
/* it exists: merge */ |
585 |
result = merge(coverage, f); |
FILE * f = fopen(path, "r"); |
586 |
if (fclose(f) == EOF) { |
if (f == NULL) { |
587 |
result = 1; |
result = 1; |
588 |
} |
} |
589 |
|
else { |
590 |
|
result = merge(coverage, f); |
591 |
|
if (fclose(f) == EOF) { |
592 |
|
result = 1; |
593 |
|
} |
594 |
|
} |
595 |
if (result != 0) { |
if (result != 0) { |
596 |
free(path); |
free(path); |
597 |
Coverage_delete(coverage); |
Coverage_delete(coverage); |
611 |
/* copy other files */ |
/* copy other files */ |
612 |
jscoverage_copy_resources(report_directory); |
jscoverage_copy_resources(report_directory); |
613 |
path = make_path(report_directory, "jscoverage.js"); |
path = make_path(report_directory, "jscoverage.js"); |
614 |
f = fopen(path, "ab"); |
FILE * f = fopen(path, "ab"); |
615 |
free(path); |
free(path); |
616 |
if (f == NULL) { |
if (f == NULL) { |
617 |
send_response(exchange, 500, "Could not write to file: jscoverage.js\n"); |
send_response(exchange, 500, "Could not write to file: jscoverage.js\n"); |
667 |
} |
} |
668 |
} |
} |
669 |
|
|
670 |
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) { |
671 |
LOCK(&javascript_mutex); |
LOCK(&javascript_mutex); |
672 |
jscoverage_instrument_js(id, input_stream, output_stream); |
jscoverage_instrument_js(id, characters, num_characters, output_stream); |
673 |
UNLOCK(&javascript_mutex); |
UNLOCK(&javascript_mutex); |
674 |
|
|
675 |
const struct Resource * resource = get_resource("report.js"); |
const struct Resource * resource = get_resource("report.js"); |
791 |
} |
} |
792 |
|
|
793 |
const char * request_uri = HTTPExchange_get_request_uri(client_exchange); |
const char * request_uri = HTTPExchange_get_request_uri(client_exchange); |
794 |
|
char * encoding = HTTPMessage_get_charset(HTTPExchange_get_response_message(server_exchange)); |
795 |
|
if (encoding == NULL) { |
796 |
|
encoding = xstrdup(jscoverage_encoding); |
797 |
|
} |
798 |
|
uint16_t * characters; |
799 |
|
size_t num_characters; |
800 |
|
int result = jscoverage_bytes_to_characters(encoding, input_stream->data, input_stream->length, &characters, &num_characters); |
801 |
|
free(encoding); |
802 |
|
Stream_delete(input_stream); |
803 |
|
if (result == JSCOVERAGE_ERROR_ENCODING_NOT_SUPPORTED) { |
804 |
|
free(characters); |
805 |
|
send_response(client_exchange, 502, "Encoding not supported\n"); |
806 |
|
goto done; |
807 |
|
} |
808 |
|
else if (result == JSCOVERAGE_ERROR_INVALID_BYTE_SEQUENCE) { |
809 |
|
free(characters); |
810 |
|
send_response(client_exchange, 502, "Error decoding response\n"); |
811 |
|
goto done; |
812 |
|
} |
813 |
|
|
814 |
Stream * output_stream = Stream_new(0); |
Stream * output_stream = Stream_new(0); |
815 |
instrument_js(request_uri, input_stream, output_stream); |
instrument_js(request_uri, characters, num_characters, output_stream); |
816 |
|
|
817 |
/* send the headers to the client */ |
/* send the headers to the client */ |
818 |
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) { |
829 |
HTTPServer_log_err("Warning: error writing to client\n"); |
HTTPServer_log_err("Warning: error writing to client\n"); |
830 |
} |
} |
831 |
|
|
832 |
/* input_stream goes on the cache */ |
/* characters go on the cache */ |
833 |
/* |
/* |
834 |
Stream_delete(input_stream); |
free(characters); |
835 |
*/ |
*/ |
836 |
Stream_delete(output_stream); |
Stream_delete(output_stream); |
837 |
add_cached_source(request_uri, input_stream); |
add_cached_source(request_uri, characters, num_characters); |
838 |
} |
} |
839 |
else { |
else { |
840 |
/* does not need instrumentation */ |
/* does not need instrumentation */ |
954 |
|
|
955 |
const char * content_type = get_content_type(filesystem_path); |
const char * content_type = get_content_type(filesystem_path); |
956 |
HTTPExchange_set_response_header(exchange, HTTP_CONTENT_TYPE, content_type); |
HTTPExchange_set_response_header(exchange, HTTP_CONTENT_TYPE, content_type); |
957 |
const char * request_uri = HTTPExchange_get_request_uri(exchange); |
if (strcmp(content_type, "text/javascript") == 0 && ! is_no_instrument(abs_path)) { |
|
if (strcmp(content_type, "text/javascript") == 0 && ! is_no_instrument(request_uri)) { |
|
958 |
Stream * input_stream = Stream_new(0); |
Stream * input_stream = Stream_new(0); |
|
Stream * output_stream = Stream_new(0); |
|
|
|
|
959 |
Stream_write_file_contents(input_stream, f); |
Stream_write_file_contents(input_stream, f); |
960 |
|
|
961 |
instrument_js(request_uri, input_stream, output_stream); |
uint16_t * characters; |
962 |
|
size_t num_characters; |
963 |
|
int result = jscoverage_bytes_to_characters(jscoverage_encoding, input_stream->data, input_stream->length, &characters, &num_characters); |
964 |
|
Stream_delete(input_stream); |
965 |
|
|
966 |
|
if (result == JSCOVERAGE_ERROR_ENCODING_NOT_SUPPORTED) { |
967 |
|
free(characters); |
968 |
|
send_response(exchange, 500, "Encoding not supported\n"); |
969 |
|
goto done; |
970 |
|
} |
971 |
|
else if (result == JSCOVERAGE_ERROR_INVALID_BYTE_SEQUENCE) { |
972 |
|
free(characters); |
973 |
|
send_response(exchange, 500, "Error decoding JavaScript file\n"); |
974 |
|
goto done; |
975 |
|
} |
976 |
|
|
977 |
|
Stream * output_stream = Stream_new(0); |
978 |
|
instrument_js(abs_path, characters, num_characters, output_stream); |
979 |
|
free(characters); |
980 |
|
|
981 |
if (HTTPExchange_write_response(exchange, output_stream->data, output_stream->length) != 0) { |
if (HTTPExchange_write_response(exchange, output_stream->data, output_stream->length) != 0) { |
982 |
HTTPServer_log_err("Warning: error writing to client\n"); |
HTTPServer_log_err("Warning: error writing to client\n"); |
983 |
} |
} |
|
|
|
|
Stream_delete(input_stream); |
|
984 |
Stream_delete(output_stream); |
Stream_delete(output_stream); |
985 |
} |
} |
986 |
else { |
else { |
1060 |
document_root = argv[i] + 16; |
document_root = argv[i] + 16; |
1061 |
} |
} |
1062 |
|
|
1063 |
|
else if (strcmp(argv[i], "--encoding") == 0) { |
1064 |
|
i++; |
1065 |
|
if (i == argc) { |
1066 |
|
fatal("--encoding: option requires an argument"); |
1067 |
|
} |
1068 |
|
jscoverage_encoding = argv[i]; |
1069 |
|
} |
1070 |
|
else if (strncmp(argv[i], "--encoding=", 11) == 0) { |
1071 |
|
jscoverage_encoding = argv[i] + 11; |
1072 |
|
} |
1073 |
|
|
1074 |
else if (strcmp(argv[i], "--ip-address") == 0) { |
else if (strcmp(argv[i], "--ip-address") == 0) { |
1075 |
i++; |
i++; |
1076 |
if (i == argc) { |
if (i == argc) { |
1082 |
ip_address = argv[i] + 13; |
ip_address = argv[i] + 13; |
1083 |
} |
} |
1084 |
|
|
1085 |
|
else if (strcmp(argv[i], "--no-highlight") == 0) { |
1086 |
|
jscoverage_highlight = false; |
1087 |
|
} |
1088 |
|
|
1089 |
else if (strcmp(argv[i], "--no-instrument") == 0) { |
else if (strcmp(argv[i], "--no-instrument") == 0) { |
1090 |
i++; |
i++; |
1091 |
if (i == argc) { |
if (i == argc) { |
1141 |
#ifdef __MINGW32__ |
#ifdef __MINGW32__ |
1142 |
WSADATA data; |
WSADATA data; |
1143 |
if (WSAStartup(MAKEWORD(1, 1), &data) != 0) { |
if (WSAStartup(MAKEWORD(1, 1), &data) != 0) { |
1144 |
fatal("Could not start Winsock"); |
fatal("could not start Winsock"); |
1145 |
} |
} |
1146 |
#endif |
#endif |
1147 |
|
|
1203 |
SourceCache * p = source_cache; |
SourceCache * p = source_cache; |
1204 |
source_cache = source_cache->next; |
source_cache = source_cache->next; |
1205 |
free(p->url); |
free(p->url); |
1206 |
Stream_delete(p->source); |
free(p->characters); |
1207 |
free(p); |
free(p); |
1208 |
} |
} |
1209 |
UNLOCK(&source_cache_mutex); |
UNLOCK(&source_cache_mutex); |