25 |
#include <string.h> |
#include <string.h> |
26 |
|
|
27 |
#include <dirent.h> |
#include <dirent.h> |
28 |
|
#ifdef HAVE_PTHREAD_H |
29 |
#include <pthread.h> |
#include <pthread.h> |
30 |
|
#endif |
31 |
|
|
32 |
|
#include "global.h" |
33 |
#include "http-server.h" |
#include "http-server.h" |
34 |
#include "instrument-js.h" |
#include "instrument-js.h" |
35 |
#include "resource-manager.h" |
#include "resource-manager.h" |
36 |
#include "stream.h" |
#include "stream.h" |
37 |
#include "util.h" |
#include "util.h" |
38 |
|
|
39 |
|
const char * jscoverage_encoding = "ISO-8859-1"; |
40 |
|
|
41 |
typedef struct SourceCache { |
typedef struct SourceCache { |
42 |
char * url; |
char * url; |
43 |
Stream * source; |
Stream * source; |
69 |
static const char ** no_instrument; |
static const char ** no_instrument; |
70 |
static size_t num_no_instrument = 0; |
static size_t num_no_instrument = 0; |
71 |
|
|
72 |
|
#ifdef __MINGW32__ |
73 |
|
CRITICAL_SECTION javascript_mutex; |
74 |
|
CRITICAL_SECTION source_cache_mutex; |
75 |
|
#define LOCK EnterCriticalSection |
76 |
|
#define UNLOCK LeaveCriticalSection |
77 |
|
#else |
78 |
pthread_mutex_t javascript_mutex = PTHREAD_MUTEX_INITIALIZER; |
pthread_mutex_t javascript_mutex = PTHREAD_MUTEX_INITIALIZER; |
79 |
pthread_mutex_t source_cache_mutex = PTHREAD_MUTEX_INITIALIZER; |
pthread_mutex_t source_cache_mutex = PTHREAD_MUTEX_INITIALIZER; |
80 |
|
#define LOCK pthread_mutex_lock |
81 |
|
#define UNLOCK pthread_mutex_unlock |
82 |
|
#endif |
83 |
|
|
84 |
static Stream * find_cached_source(const char * url) { |
static Stream * find_cached_source(const char * url) { |
85 |
Stream * result = NULL; |
Stream * result = NULL; |
86 |
pthread_mutex_lock(&source_cache_mutex); |
LOCK(&source_cache_mutex); |
87 |
for (SourceCache * p = source_cache; p != NULL; p = p->next) { |
for (SourceCache * p = source_cache; p != NULL; p = p->next) { |
88 |
if (strcmp(url, p->url) == 0) { |
if (strcmp(url, p->url) == 0) { |
89 |
result = p->source; |
result = p->source; |
90 |
break; |
break; |
91 |
} |
} |
92 |
} |
} |
93 |
pthread_mutex_unlock(&source_cache_mutex); |
UNLOCK(&source_cache_mutex); |
94 |
return result; |
return result; |
95 |
} |
} |
96 |
|
|
98 |
SourceCache * new_source_cache = xmalloc(sizeof(SourceCache)); |
SourceCache * new_source_cache = xmalloc(sizeof(SourceCache)); |
99 |
new_source_cache->url = xstrdup(url); |
new_source_cache->url = xstrdup(url); |
100 |
new_source_cache->source = source; |
new_source_cache->source = source; |
101 |
pthread_mutex_lock(&source_cache_mutex); |
LOCK(&source_cache_mutex); |
102 |
new_source_cache->next = source_cache; |
new_source_cache->next = source_cache; |
103 |
source_cache = new_source_cache; |
source_cache = new_source_cache; |
104 |
pthread_mutex_unlock(&source_cache_mutex); |
UNLOCK(&source_cache_mutex); |
105 |
} |
} |
106 |
|
|
107 |
static int get(const char * url, Stream * stream) __attribute__((warn_unused_result)); |
static int get(const char * url, Stream * stream) __attribute__((warn_unused_result)); |
352 |
return true; |
return true; |
353 |
} |
} |
354 |
|
|
355 |
static int merge(Coverage * coverage, const char * path, size_t size) __attribute__((warn_unused_result)); |
static int merge(Coverage * coverage, FILE * f) __attribute__((warn_unused_result)); |
356 |
|
|
357 |
static int merge(Coverage * coverage, const char * path, size_t size) { |
static int merge(Coverage * coverage, FILE * f) { |
358 |
FILE * f = fopen(path, "r"); |
Stream * stream = Stream_new(0); |
359 |
if (f == NULL) { |
Stream_write_file_contents(stream, f); |
|
return -1; |
|
|
} |
|
|
uint8_t * buffer = xmalloc(size); |
|
|
if (fread(buffer, 1, size, f) != size) { |
|
|
fclose(f); |
|
|
free(buffer); |
|
|
return -1; |
|
|
} |
|
|
fclose(f); |
|
360 |
|
|
361 |
pthread_mutex_lock(&javascript_mutex); |
LOCK(&javascript_mutex); |
362 |
int result = jscoverage_parse_json(coverage, buffer, size); |
int result = jscoverage_parse_json(coverage, stream->data, stream->length); |
363 |
pthread_mutex_unlock(&javascript_mutex); |
UNLOCK(&javascript_mutex); |
364 |
|
|
365 |
free(buffer); |
Stream_delete(stream); |
366 |
return result; |
return result; |
367 |
} |
} |
368 |
|
|
449 |
/* check that the path begins with / */ |
/* check that the path begins with / */ |
450 |
if (file_coverage->id[0] == '/') { |
if (file_coverage->id[0] == '/') { |
451 |
char * source_path = make_path(document_root, file_coverage->id + 1); |
char * source_path = make_path(document_root, file_coverage->id + 1); |
452 |
FILE * source_file = fopen(source_path, "r"); |
FILE * source_file = fopen(source_path, "rb"); |
453 |
free(source_path); |
free(source_path); |
454 |
if (source_file == NULL) { |
if (source_file == NULL) { |
455 |
fputs("\"\"", f); |
fputs("\"\"", f); |
516 |
} |
} |
517 |
|
|
518 |
Coverage * coverage = Coverage_new(); |
Coverage * coverage = Coverage_new(); |
519 |
pthread_mutex_lock(&javascript_mutex); |
LOCK(&javascript_mutex); |
520 |
int result = jscoverage_parse_json(coverage, json->data, json->length); |
int result = jscoverage_parse_json(coverage, json->data, json->length); |
521 |
pthread_mutex_unlock(&javascript_mutex); |
UNLOCK(&javascript_mutex); |
522 |
Stream_delete(json); |
Stream_delete(json); |
523 |
|
|
524 |
if (result != 0) { |
if (result != 0) { |
529 |
|
|
530 |
mkdir_if_necessary(report_directory); |
mkdir_if_necessary(report_directory); |
531 |
char * path = make_path(report_directory, "jscoverage.json"); |
char * path = make_path(report_directory, "jscoverage.json"); |
532 |
|
|
533 |
|
/* check if the JSON file exists */ |
534 |
struct stat buf; |
struct stat buf; |
535 |
if (stat(path, &buf) == 0) { |
if (stat(path, &buf) == 0) { |
536 |
/* it exists: merge */ |
/* it exists: merge */ |
537 |
result = merge(coverage, path, buf.st_size); |
FILE * f = fopen(path, "r"); |
538 |
|
if (f == NULL) { |
539 |
|
result = 1; |
540 |
|
} |
541 |
|
else { |
542 |
|
result = merge(coverage, f); |
543 |
|
if (fclose(f) == EOF) { |
544 |
|
result = 1; |
545 |
|
} |
546 |
|
} |
547 |
if (result != 0) { |
if (result != 0) { |
548 |
free(path); |
free(path); |
549 |
Coverage_delete(coverage); |
Coverage_delete(coverage); |
619 |
} |
} |
620 |
} |
} |
621 |
|
|
622 |
static void instrument_js(const char * id, Stream * input_stream, Stream * output_stream) { |
static void instrument_js(const char * id, const char * encoding, Stream * input_stream, Stream * output_stream) { |
623 |
pthread_mutex_lock(&javascript_mutex); |
LOCK(&javascript_mutex); |
624 |
jscoverage_instrument_js(id, input_stream, output_stream); |
jscoverage_instrument_js(id, encoding, input_stream, output_stream); |
625 |
pthread_mutex_unlock(&javascript_mutex); |
UNLOCK(&javascript_mutex); |
626 |
|
|
627 |
const struct Resource * resource = get_resource("report.js"); |
const struct Resource * resource = get_resource("report.js"); |
628 |
Stream_write(output_stream, resource->data, resource->length); |
Stream_write(output_stream, resource->data, resource->length); |
744 |
|
|
745 |
const char * request_uri = HTTPExchange_get_request_uri(client_exchange); |
const char * request_uri = HTTPExchange_get_request_uri(client_exchange); |
746 |
Stream * output_stream = Stream_new(0); |
Stream * output_stream = Stream_new(0); |
747 |
instrument_js(request_uri, input_stream, output_stream); |
char * encoding = HTTPMessage_get_charset(HTTPExchange_get_response_message(server_exchange)); |
748 |
|
if (encoding == NULL) { |
749 |
|
instrument_js(request_uri, jscoverage_encoding, input_stream, output_stream); |
750 |
|
} |
751 |
|
else { |
752 |
|
instrument_js(request_uri, encoding, input_stream, output_stream); |
753 |
|
free(encoding); |
754 |
|
} |
755 |
|
|
756 |
/* send the headers to the client */ |
/* send the headers to the client */ |
757 |
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) { |
837 |
} |
} |
838 |
|
|
839 |
filesystem_path = make_path(document_root, abs_path + 1); |
filesystem_path = make_path(document_root, abs_path + 1); |
840 |
|
size_t filesystem_path_length = strlen(filesystem_path); |
841 |
|
if (filesystem_path_length > 0 && filesystem_path[filesystem_path_length - 1] == '/') { |
842 |
|
/* stat on Windows doesn't work with trailing slash */ |
843 |
|
filesystem_path[filesystem_path_length - 1] = '\0'; |
844 |
|
} |
845 |
|
|
846 |
struct stat buf; |
struct stat buf; |
847 |
if (stat(filesystem_path, &buf) == -1) { |
if (stat(filesystem_path, &buf) == -1) { |
850 |
} |
} |
851 |
|
|
852 |
if (S_ISDIR(buf.st_mode)) { |
if (S_ISDIR(buf.st_mode)) { |
853 |
if (filesystem_path[strlen(filesystem_path) - 1] != '/') { |
if (abs_path[strlen(abs_path) - 1] != '/') { |
854 |
const char * request_uri = HTTPExchange_get_request_uri(exchange); |
const char * request_uri = HTTPExchange_get_request_uri(exchange); |
855 |
char * uri = xmalloc(strlen(request_uri) + 2); |
char * uri = xmalloc(strlen(request_uri) + 2); |
856 |
strcpy(uri, request_uri); |
strcpy(uri, request_uri); |
885 |
closedir(d); |
closedir(d); |
886 |
} |
} |
887 |
else if (S_ISREG(buf.st_mode)) { |
else if (S_ISREG(buf.st_mode)) { |
888 |
FILE * f = fopen(filesystem_path, "r"); |
FILE * f = fopen(filesystem_path, "rb"); |
889 |
if (f == NULL) { |
if (f == NULL) { |
890 |
send_response(exchange, 404, "Not found\n"); |
send_response(exchange, 404, "Not found\n"); |
891 |
goto done; |
goto done; |
893 |
|
|
894 |
const char * content_type = get_content_type(filesystem_path); |
const char * content_type = get_content_type(filesystem_path); |
895 |
HTTPExchange_set_response_header(exchange, HTTP_CONTENT_TYPE, content_type); |
HTTPExchange_set_response_header(exchange, HTTP_CONTENT_TYPE, content_type); |
896 |
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)) { |
|
897 |
Stream * input_stream = Stream_new(0); |
Stream * input_stream = Stream_new(0); |
898 |
Stream * output_stream = Stream_new(0); |
Stream * output_stream = Stream_new(0); |
899 |
|
|
900 |
Stream_write_file_contents(input_stream, f); |
Stream_write_file_contents(input_stream, f); |
901 |
|
|
902 |
instrument_js(request_uri, input_stream, output_stream); |
instrument_js(abs_path, jscoverage_encoding, input_stream, output_stream); |
903 |
|
|
904 |
if (HTTPExchange_write_response(exchange, output_stream->data, output_stream->length) != 0) { |
if (HTTPExchange_write_response(exchange, output_stream->data, output_stream->length) != 0) { |
905 |
HTTPServer_log_err("Warning: error writing to client\n"); |
HTTPServer_log_err("Warning: error writing to client\n"); |
985 |
document_root = argv[i] + 16; |
document_root = argv[i] + 16; |
986 |
} |
} |
987 |
|
|
988 |
|
else if (strcmp(argv[i], "--encoding") == 0) { |
989 |
|
i++; |
990 |
|
if (i == argc) { |
991 |
|
fatal("--encoding: option requires an argument"); |
992 |
|
} |
993 |
|
jscoverage_encoding = argv[i]; |
994 |
|
} |
995 |
|
else if (strncmp(argv[i], "--encoding=", 11) == 0) { |
996 |
|
jscoverage_encoding = argv[i] + 11; |
997 |
|
} |
998 |
|
|
999 |
else if (strcmp(argv[i], "--ip-address") == 0) { |
else if (strcmp(argv[i], "--ip-address") == 0) { |
1000 |
i++; |
i++; |
1001 |
if (i == argc) { |
if (i == argc) { |
1059 |
|
|
1060 |
/* is this a shutdown? */ |
/* is this a shutdown? */ |
1061 |
if (shutdown) { |
if (shutdown) { |
1062 |
|
#ifdef __MINGW32__ |
1063 |
|
WSADATA data; |
1064 |
|
if (WSAStartup(MAKEWORD(1, 1), &data) != 0) { |
1065 |
|
fatal("could not start Winsock"); |
1066 |
|
} |
1067 |
|
#endif |
1068 |
|
|
1069 |
/* INADDR_LOOPBACK */ |
/* INADDR_LOOPBACK */ |
1070 |
HTTPConnection * connection = HTTPConnection_new_client("127.0.0.1", numeric_port); |
HTTPConnection * connection = HTTPConnection_new_client("127.0.0.1", numeric_port); |
1071 |
if (connection == NULL) { |
if (connection == NULL) { |
1095 |
|
|
1096 |
jscoverage_init(); |
jscoverage_init(); |
1097 |
|
|
1098 |
|
#ifndef __MINGW32__ |
1099 |
/* handle broken pipe */ |
/* handle broken pipe */ |
1100 |
signal(SIGPIPE, SIG_IGN); |
signal(SIGPIPE, SIG_IGN); |
1101 |
|
#endif |
1102 |
|
|
1103 |
|
#ifdef __MINGW32__ |
1104 |
|
InitializeCriticalSection(&javascript_mutex); |
1105 |
|
InitializeCriticalSection(&source_cache_mutex); |
1106 |
|
#endif |
1107 |
|
|
1108 |
if (verbose) { |
if (verbose) { |
1109 |
printf("Starting HTTP server on %s:%lu\n", ip_address, numeric_port); |
printf("Starting HTTP server on %s:%lu\n", ip_address, numeric_port); |
1110 |
|
fflush(stdout); |
1111 |
} |
} |
1112 |
HTTPServer_run(ip_address, (uint16_t) numeric_port, handler); |
HTTPServer_run(ip_address, (uint16_t) numeric_port, handler); |
1113 |
if (verbose) { |
if (verbose) { |
1114 |
printf("Stopping HTTP server\n"); |
printf("Stopping HTTP server\n"); |
1115 |
|
fflush(stdout); |
1116 |
} |
} |
1117 |
|
|
1118 |
jscoverage_cleanup(); |
jscoverage_cleanup(); |
1119 |
|
|
1120 |
free(no_instrument); |
free(no_instrument); |
1121 |
|
|
1122 |
pthread_mutex_lock(&source_cache_mutex); |
LOCK(&source_cache_mutex); |
1123 |
while (source_cache != NULL) { |
while (source_cache != NULL) { |
1124 |
SourceCache * p = source_cache; |
SourceCache * p = source_cache; |
1125 |
source_cache = source_cache->next; |
source_cache = source_cache->next; |
1127 |
Stream_delete(p->source); |
Stream_delete(p->source); |
1128 |
free(p); |
free(p); |
1129 |
} |
} |
1130 |
pthread_mutex_unlock(&source_cache_mutex); |
UNLOCK(&source_cache_mutex); |
1131 |
|
|
1132 |
return 0; |
return 0; |
1133 |
} |
} |