1 |
/* |
2 |
jscoverage-server.c - JSCoverage server main routine |
3 |
Copyright (C) 2008, 2009, 2010 siliconforks.com |
4 |
|
5 |
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 |
7 |
the Free Software Foundation; either version 2 of the License, or |
8 |
(at your option) any later version. |
9 |
|
10 |
This program is distributed in the hope that it will be useful, |
11 |
but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 |
GNU General Public License for more details. |
14 |
|
15 |
You should have received a copy of the GNU General Public License along |
16 |
with this program; if not, write to the Free Software Foundation, Inc., |
17 |
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
18 |
*/ |
19 |
|
20 |
#include <config.h> |
21 |
|
22 |
#include <assert.h> |
23 |
#include <ctype.h> |
24 |
#include <signal.h> |
25 |
#include <stdint.h> |
26 |
#include <string.h> |
27 |
|
28 |
#include <dirent.h> |
29 |
#ifdef HAVE_PTHREAD_H |
30 |
#include <pthread.h> |
31 |
#endif |
32 |
|
33 |
#include "encoding.h" |
34 |
#include "global.h" |
35 |
#include "http-server.h" |
36 |
#include "instrument-js.h" |
37 |
#include "resource-manager.h" |
38 |
#include "stream.h" |
39 |
#include "util.h" |
40 |
|
41 |
static const char * specified_encoding = NULL; |
42 |
const char * jscoverage_encoding = "ISO-8859-1"; |
43 |
bool jscoverage_highlight = true; |
44 |
|
45 |
struct in_addr jscoverage_ip_address; |
46 |
|
47 |
typedef struct SourceCache { |
48 |
char * url; |
49 |
uint16_t * characters; |
50 |
size_t num_characters; |
51 |
struct SourceCache * next; |
52 |
} SourceCache; |
53 |
|
54 |
static SourceCache * source_cache = NULL; |
55 |
|
56 |
static const struct { |
57 |
const char * extension; |
58 |
const char * mime_type; |
59 |
} mime_types[] = { |
60 |
{".gif", "image/gif"}, |
61 |
{".jpg", "image/jpeg"}, |
62 |
{".jpeg", "image/jpeg"}, |
63 |
{".png", "image/png"}, |
64 |
{".css", "text/css"}, |
65 |
{".html", "text/html"}, |
66 |
{".htm", "text/html"}, |
67 |
{".js", "text/javascript"}, |
68 |
{".txt", "text/plain"}, |
69 |
{".xml", "application/xml"}, |
70 |
}; |
71 |
|
72 |
static bool verbose = false; |
73 |
static const char * report_directory = "jscoverage-report"; |
74 |
static const char * document_root = "."; |
75 |
static bool proxy = false; |
76 |
static const char ** no_instrument; |
77 |
static size_t num_no_instrument = 0; |
78 |
|
79 |
#ifdef __MINGW32__ |
80 |
CRITICAL_SECTION javascript_mutex; |
81 |
CRITICAL_SECTION source_cache_mutex; |
82 |
#define LOCK EnterCriticalSection |
83 |
#define UNLOCK LeaveCriticalSection |
84 |
#else |
85 |
pthread_mutex_t javascript_mutex = PTHREAD_MUTEX_INITIALIZER; |
86 |
pthread_mutex_t source_cache_mutex = PTHREAD_MUTEX_INITIALIZER; |
87 |
#define LOCK pthread_mutex_lock |
88 |
#define UNLOCK pthread_mutex_unlock |
89 |
#endif |
90 |
|
91 |
static const SourceCache * find_cached_source(const char * url) { |
92 |
SourceCache * result = NULL; |
93 |
LOCK(&source_cache_mutex); |
94 |
for (SourceCache * p = source_cache; p != NULL; p = p->next) { |
95 |
if (strcmp(url, p->url) == 0) { |
96 |
result = p; |
97 |
break; |
98 |
} |
99 |
} |
100 |
UNLOCK(&source_cache_mutex); |
101 |
return result; |
102 |
} |
103 |
|
104 |
static void add_cached_source(const char * url, uint16_t * characters, size_t num_characters) { |
105 |
SourceCache * new_source_cache = xmalloc(sizeof(SourceCache)); |
106 |
new_source_cache->url = xstrdup(url); |
107 |
new_source_cache->characters = characters; |
108 |
new_source_cache->num_characters = num_characters; |
109 |
LOCK(&source_cache_mutex); |
110 |
new_source_cache->next = source_cache; |
111 |
source_cache = new_source_cache; |
112 |
UNLOCK(&source_cache_mutex); |
113 |
} |
114 |
|
115 |
static int get(const char * url, uint16_t ** characters, size_t * num_characters) __attribute__((warn_unused_result)); |
116 |
|
117 |
static int get(const char * url, uint16_t ** characters, size_t * num_characters) { |
118 |
char * host = NULL; |
119 |
uint16_t port; |
120 |
char * abs_path = NULL; |
121 |
char * query = NULL; |
122 |
HTTPConnection * connection = NULL; |
123 |
HTTPExchange * exchange = NULL; |
124 |
Stream * stream = NULL; |
125 |
|
126 |
int result = URL_parse(url, &host, &port, &abs_path, &query); |
127 |
if (result != 0) { |
128 |
goto done; |
129 |
} |
130 |
|
131 |
connection = HTTPConnection_new_client(host, port); |
132 |
if (connection == NULL) { |
133 |
result = -1; |
134 |
goto done; |
135 |
} |
136 |
|
137 |
exchange = HTTPExchange_new(connection); |
138 |
HTTPExchange_set_request_uri(exchange, url); |
139 |
result = HTTPExchange_write_request_headers(exchange); |
140 |
if (result != 0) { |
141 |
goto done; |
142 |
} |
143 |
|
144 |
result = HTTPExchange_read_response_headers(exchange); |
145 |
if (result != 0) { |
146 |
goto done; |
147 |
} |
148 |
|
149 |
stream = Stream_new(0); |
150 |
result = HTTPExchange_read_entire_response_entity_body(exchange, stream); |
151 |
if (result != 0) { |
152 |
goto done; |
153 |
} |
154 |
char * encoding = HTTPMessage_get_charset(HTTPExchange_get_response_message(exchange)); |
155 |
if (encoding == NULL) { |
156 |
encoding = xstrdup(jscoverage_encoding); |
157 |
} |
158 |
result = jscoverage_bytes_to_characters(encoding, stream->data, stream->length, characters, num_characters); |
159 |
free(encoding); |
160 |
if (result != 0) { |
161 |
goto done; |
162 |
} |
163 |
|
164 |
result = 0; |
165 |
|
166 |
done: |
167 |
if (stream != NULL) { |
168 |
Stream_delete(stream); |
169 |
} |
170 |
if (exchange != NULL) { |
171 |
HTTPExchange_delete(exchange); |
172 |
} |
173 |
if (connection != NULL) { |
174 |
if (HTTPConnection_delete(connection) != 0) { |
175 |
HTTPServer_log_err("Warning: error closing connection after retrieving URL: %s\n", url); |
176 |
} |
177 |
} |
178 |
free(host); |
179 |
free(abs_path); |
180 |
free(query); |
181 |
return result; |
182 |
} |
183 |
|
184 |
static void send_response(HTTPExchange * exchange, uint16_t status_code, const char * html) { |
185 |
HTTPExchange_set_status_code(exchange, status_code); |
186 |
if (HTTPExchange_write_response(exchange, html, strlen(html)) != 0) { |
187 |
HTTPServer_log_err("Warning: error writing to client\n"); |
188 |
} |
189 |
} |
190 |
|
191 |
/* |
192 |
RFC 2396, Appendix A: we are checking for `pchar' |
193 |
*/ |
194 |
static bool is_escaped(char c) { |
195 |
/* `pchar' */ |
196 |
if (strchr(":@&=+$,", c) != NULL) { |
197 |
return false; |
198 |
} |
199 |
|
200 |
if (isalnum((unsigned char) c)) { |
201 |
return false; |
202 |
} |
203 |
|
204 |
/* `mark' */ |
205 |
if (strchr("-_.!~*'()", c) != NULL) { |
206 |
return false; |
207 |
} |
208 |
|
209 |
return true; |
210 |
} |
211 |
|
212 |
static char * encode_uri_component(const char * s) { |
213 |
size_t length = 0; |
214 |
for (const char * p = s; *p != '\0'; p++) { |
215 |
if (is_escaped(*p)) { |
216 |
length = addst(length, 3); |
217 |
} |
218 |
else { |
219 |
length = addst(length, 1); |
220 |
} |
221 |
} |
222 |
|
223 |
length = addst(length, 1); |
224 |
char * result = xmalloc(length); |
225 |
size_t i = 0; |
226 |
for (const char * p = s; *p != '\0'; p++) { |
227 |
if (is_escaped(*p)) { |
228 |
result[i] = '%'; |
229 |
i++; |
230 |
snprintf(result + i, 3, "%02X", *p); |
231 |
i += 2; |
232 |
} |
233 |
else { |
234 |
result[i] = *p; |
235 |
i++; |
236 |
} |
237 |
} |
238 |
result[i] = '\0'; |
239 |
|
240 |
return result; |
241 |
} |
242 |
|
243 |
static unsigned int hex_value(char c) { |
244 |
if ('0' <= c && c <= '9') { |
245 |
return c - '0'; |
246 |
} |
247 |
else if ('A' <= c && c <= 'F') { |
248 |
return c - 'A' + 10; |
249 |
} |
250 |
else if ('a' <= c && c <= 'f') { |
251 |
return c - 'a' + 10; |
252 |
} |
253 |
else { |
254 |
return 0; |
255 |
} |
256 |
} |
257 |
|
258 |
static char * decode_uri_component(const char * s) { |
259 |
size_t length = strlen(s); |
260 |
char * result = xmalloc(length + 1); |
261 |
char * p = result; |
262 |
while (*s != '\0') { |
263 |
if (*s == '%') { |
264 |
if (s[1] == '\0' || s[2] == '\0') { |
265 |
*p = '\0'; |
266 |
return result; |
267 |
} |
268 |
*p = hex_value(s[1]) * 16 + hex_value(s[2]); |
269 |
s += 2; |
270 |
} |
271 |
else { |
272 |
*p = *s; |
273 |
} |
274 |
p++; |
275 |
s++; |
276 |
} |
277 |
*p = '\0'; |
278 |
return result; |
279 |
} |
280 |
|
281 |
static const char * get_entity(char c) { |
282 |
switch(c) { |
283 |
case '<': |
284 |
return "<"; |
285 |
case '>': |
286 |
return ">"; |
287 |
case '&': |
288 |
return "&"; |
289 |
case '\'': |
290 |
return "'"; |
291 |
case '"': |
292 |
return """; |
293 |
default: |
294 |
return NULL; |
295 |
} |
296 |
} |
297 |
|
298 |
static char * encode_html(const char * s) { |
299 |
size_t length = 0; |
300 |
for (const char * p = s; *p != '\0'; p++) { |
301 |
const char * entity = get_entity(*p); |
302 |
if (entity == NULL) { |
303 |
length = addst(length, 1); |
304 |
} |
305 |
else { |
306 |
length = addst(length, strlen(entity)); |
307 |
} |
308 |
} |
309 |
|
310 |
length = addst(length, 1); |
311 |
char * result = xmalloc(length); |
312 |
size_t i = 0; |
313 |
for (const char * p = s; *p != '\0'; p++) { |
314 |
const char * entity = get_entity(*p); |
315 |
if (entity == NULL) { |
316 |
result[i] = *p; |
317 |
i++; |
318 |
} |
319 |
else { |
320 |
strcpy(result + i, entity); |
321 |
i += strlen(entity); |
322 |
} |
323 |
} |
324 |
result[i] = '\0'; |
325 |
|
326 |
return result; |
327 |
} |
328 |
|
329 |
static const char * get_content_type(const char * path) { |
330 |
char * last_dot = strrchr(path, '.'); |
331 |
if (last_dot == NULL) { |
332 |
return "application/octet-stream"; |
333 |
} |
334 |
for (size_t i = 0; i < sizeof(mime_types) / sizeof(mime_types[0]); i++) { |
335 |
if (strcmp(last_dot, mime_types[i].extension) == 0) { |
336 |
return mime_types[i].mime_type; |
337 |
} |
338 |
} |
339 |
return "application/octet-stream"; |
340 |
} |
341 |
|
342 |
/** |
343 |
Checks whether a URI is on the no-instrument list. |
344 |
@param uri the HTTP "Request-URI"; must not be NULL, and must not be a zero-length string |
345 |
@return true if the URI is on the no-instrument list, false otherwise |
346 |
*/ |
347 |
static bool is_no_instrument(const char * uri) { |
348 |
assert(*uri != '\0'); |
349 |
|
350 |
for (size_t i = 0; i < num_no_instrument; i++) { |
351 |
if (str_starts_with(uri, no_instrument[i])) { |
352 |
return true; |
353 |
} |
354 |
|
355 |
/* |
356 |
For a local URL, accept "/foo/bar" and "foo/bar" on the no-instrument list. |
357 |
*/ |
358 |
if (! proxy && str_starts_with(uri + 1, no_instrument[i])) { |
359 |
return true; |
360 |
} |
361 |
} |
362 |
|
363 |
return false; |
364 |
} |
365 |
|
366 |
static bool is_javascript(HTTPExchange * exchange) { |
367 |
const char * header = HTTPExchange_find_response_header(exchange, HTTP_CONTENT_TYPE); |
368 |
if (header == NULL) { |
369 |
/* guess based on extension */ |
370 |
return str_ends_with(HTTPExchange_get_request_uri(exchange), ".js"); |
371 |
} |
372 |
else { |
373 |
char * semicolon = strchr(header, ';'); |
374 |
char * content_type; |
375 |
if (semicolon == NULL) { |
376 |
content_type = xstrdup(header); |
377 |
} |
378 |
else { |
379 |
content_type = xstrndup(header, semicolon - header); |
380 |
} |
381 |
/* RFC 4329 */ |
382 |
bool result = strcmp(content_type, "text/javascript") == 0 || |
383 |
strcmp(content_type, "text/ecmascript") == 0 || |
384 |
strcmp(content_type, "text/javascript1.0") == 0 || |
385 |
strcmp(content_type, "text/javascript1.1") == 0 || |
386 |
strcmp(content_type, "text/javascript1.2") == 0 || |
387 |
strcmp(content_type, "text/javascript1.3") == 0 || |
388 |
strcmp(content_type, "text/javascript1.4") == 0 || |
389 |
strcmp(content_type, "text/javascript1.5") == 0 || |
390 |
strcmp(content_type, "text/jscript") == 0 || |
391 |
strcmp(content_type, "text/livescript") == 0 || |
392 |
strcmp(content_type, "text/x-javascript") == 0 || |
393 |
strcmp(content_type, "text/x-ecmascript") == 0 || |
394 |
strcmp(content_type, "application/x-javascript") == 0 || |
395 |
strcmp(content_type, "application/x-ecmascript") == 0 || |
396 |
strcmp(content_type, "application/javascript") == 0 || |
397 |
strcmp(content_type, "application/ecmascript") == 0; |
398 |
free(content_type); |
399 |
return result; |
400 |
} |
401 |
} |
402 |
|
403 |
static bool should_instrument_request(HTTPExchange * exchange, const char * uri) { |
404 |
if (! is_javascript(exchange)) { |
405 |
return false; |
406 |
} |
407 |
|
408 |
if (is_no_instrument(uri)) { |
409 |
return false; |
410 |
} |
411 |
|
412 |
return true; |
413 |
} |
414 |
|
415 |
static int merge(Coverage * coverage, FILE * f) __attribute__((warn_unused_result)); |
416 |
|
417 |
static int merge(Coverage * coverage, FILE * f) { |
418 |
Stream * stream = Stream_new(0); |
419 |
Stream_write_file_contents(stream, f); |
420 |
|
421 |
LOCK(&javascript_mutex); |
422 |
int result = jscoverage_parse_json(coverage, stream->data, stream->length); |
423 |
UNLOCK(&javascript_mutex); |
424 |
|
425 |
Stream_delete(stream); |
426 |
return result; |
427 |
} |
428 |
|
429 |
static void write_js_quoted_string(FILE * f, char * data, size_t length) { |
430 |
putc('"', f); |
431 |
for (size_t i = 0; i < length; i++) { |
432 |
char c = data[i]; |
433 |
switch (c) { |
434 |
case '\b': |
435 |
fputs("\\b", f); |
436 |
break; |
437 |
case '\f': |
438 |
fputs("\\f", f); |
439 |
break; |
440 |
case '\n': |
441 |
fputs("\\n", f); |
442 |
break; |
443 |
case '\r': |
444 |
fputs("\\r", f); |
445 |
break; |
446 |
case '\t': |
447 |
fputs("\\t", f); |
448 |
break; |
449 |
/* IE doesn't support this */ |
450 |
/* |
451 |
case '\v': |
452 |
fputs("\\v", f); |
453 |
break; |
454 |
*/ |
455 |
case '"': |
456 |
fputs("\\\"", f); |
457 |
break; |
458 |
case '\\': |
459 |
fputs("\\\\", f); |
460 |
break; |
461 |
default: |
462 |
putc(c, f); |
463 |
break; |
464 |
} |
465 |
} |
466 |
putc('"', f); |
467 |
} |
468 |
|
469 |
static void write_source(const char * id, const uint16_t * characters, size_t num_characters, FILE * f) { |
470 |
Stream * output = Stream_new(num_characters); |
471 |
jscoverage_write_source(id, characters, num_characters, output); |
472 |
fwrite(output->data, 1, output->length, f); |
473 |
Stream_delete(output); |
474 |
} |
475 |
|
476 |
static void write_json_for_file(const FileCoverage * file_coverage, int i, void * p) { |
477 |
FILE * f = p; |
478 |
|
479 |
if (i > 0) { |
480 |
putc(',', f); |
481 |
} |
482 |
|
483 |
write_js_quoted_string(f, file_coverage->id, strlen(file_coverage->id)); |
484 |
|
485 |
fputs(":{\"coverage\":[", f); |
486 |
for (uint32_t i = 0; i < file_coverage->num_coverage_lines; i++) { |
487 |
if (i > 0) { |
488 |
putc(',', f); |
489 |
} |
490 |
int timesExecuted = file_coverage->coverage_lines[i]; |
491 |
if (timesExecuted < 0) { |
492 |
fputs("null", f); |
493 |
} |
494 |
else { |
495 |
fprintf(f, "%d", timesExecuted); |
496 |
} |
497 |
} |
498 |
fputs("],\"source\":", f); |
499 |
if (file_coverage->source_lines == NULL) { |
500 |
if (proxy) { |
501 |
const SourceCache * cached = find_cached_source(file_coverage->id); |
502 |
if (cached == NULL) { |
503 |
uint16_t * characters; |
504 |
size_t num_characters; |
505 |
if (get(file_coverage->id, &characters, &num_characters) == 0) { |
506 |
write_source(file_coverage->id, characters, num_characters, f); |
507 |
add_cached_source(file_coverage->id, characters, num_characters); |
508 |
} |
509 |
else { |
510 |
fputs("[]", f); |
511 |
HTTPServer_log_err("Warning: cannot retrieve URL: %s\n", file_coverage->id); |
512 |
} |
513 |
} |
514 |
else { |
515 |
write_source(file_coverage->id, cached->characters, cached->num_characters, f); |
516 |
} |
517 |
} |
518 |
else { |
519 |
/* check that the path begins with / */ |
520 |
if (file_coverage->id[0] == '/') { |
521 |
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"); |
531 |
free(source_path); |
532 |
if (source_file == NULL) { |
533 |
fputs("[]", f); |
534 |
HTTPServer_log_err("Warning: cannot open file: %s\n", file_coverage->id); |
535 |
} |
536 |
else { |
537 |
Stream * stream = Stream_new(0); |
538 |
Stream_write_file_contents(stream, source_file); |
539 |
fclose(source_file); |
540 |
uint16_t * characters; |
541 |
size_t num_characters; |
542 |
int result = jscoverage_bytes_to_characters(jscoverage_encoding, stream->data, stream->length, &characters, &num_characters); |
543 |
Stream_delete(stream); |
544 |
if (result == JSCOVERAGE_ERROR_ENCODING_NOT_SUPPORTED) { |
545 |
fputs("[]", f); |
546 |
HTTPServer_log_err("Warning: encoding %s not supported\n", jscoverage_encoding); |
547 |
} |
548 |
else if (result == JSCOVERAGE_ERROR_INVALID_BYTE_SEQUENCE) { |
549 |
fputs("[]", f); |
550 |
HTTPServer_log_err("Warning: error decoding %s in file %s\n", jscoverage_encoding, file_coverage->id); |
551 |
} |
552 |
else { |
553 |
write_source(file_coverage->id, characters, num_characters, f); |
554 |
free(characters); |
555 |
} |
556 |
} |
557 |
} |
558 |
else { |
559 |
/* path does not begin with / */ |
560 |
fputs("[]", f); |
561 |
HTTPServer_log_err("Warning: invalid source path: %s\n", file_coverage->id); |
562 |
} |
563 |
} |
564 |
} |
565 |
else { |
566 |
fputc('[', f); |
567 |
for (uint32_t i = 0; i < file_coverage->num_source_lines; i++) { |
568 |
if (i > 0) { |
569 |
fputc(',', f); |
570 |
} |
571 |
char * source_line = file_coverage->source_lines[i]; |
572 |
write_js_quoted_string(f, source_line, strlen(source_line)); |
573 |
} |
574 |
fputc(']', f); |
575 |
} |
576 |
done: |
577 |
fputc('}', f); |
578 |
} |
579 |
|
580 |
static int write_json(Coverage * coverage, const char * path) __attribute__((warn_unused_result)); |
581 |
|
582 |
static int write_json(Coverage * coverage, const char * path) { |
583 |
/* write the JSON */ |
584 |
FILE * f = fopen(path, "wb"); |
585 |
if (f == NULL) { |
586 |
return -1; |
587 |
} |
588 |
putc('{', f); |
589 |
Coverage_foreach_file(coverage, write_json_for_file, f); |
590 |
putc('}', f); |
591 |
if (fclose(f) == EOF) { |
592 |
return -1; |
593 |
} |
594 |
return 0; |
595 |
} |
596 |
|
597 |
static void handle_jscoverage_request(HTTPExchange * exchange) { |
598 |
/* set the `Server' response-header (RFC 2616 14.38, 3.8) */ |
599 |
HTTPExchange_set_response_header(exchange, HTTP_SERVER, "jscoverage-server/" VERSION); |
600 |
|
601 |
const char * abs_path = HTTPExchange_get_abs_path(exchange); |
602 |
assert(*abs_path != '\0'); |
603 |
if (str_starts_with(abs_path, "/jscoverage-store")) { |
604 |
if (strcmp(HTTPExchange_get_method(exchange), "POST") != 0) { |
605 |
HTTPExchange_set_response_header(exchange, HTTP_ALLOW, "POST"); |
606 |
send_response(exchange, 405, "Method not allowed\n"); |
607 |
return; |
608 |
} |
609 |
|
610 |
Stream * json = Stream_new(0); |
611 |
|
612 |
/* read the POST body */ |
613 |
if (HTTPExchange_read_entire_request_entity_body(exchange, json) != 0) { |
614 |
Stream_delete(json); |
615 |
send_response(exchange, 400, "Could not read request body\n"); |
616 |
return; |
617 |
} |
618 |
|
619 |
Coverage * coverage = Coverage_new(); |
620 |
LOCK(&javascript_mutex); |
621 |
int result = jscoverage_parse_json(coverage, json->data, json->length); |
622 |
UNLOCK(&javascript_mutex); |
623 |
Stream_delete(json); |
624 |
|
625 |
if (result != 0) { |
626 |
Coverage_delete(coverage); |
627 |
send_response(exchange, 400, "Could not parse coverage data\n"); |
628 |
return; |
629 |
} |
630 |
|
631 |
mkdir_if_necessary(report_directory); |
632 |
char * current_report_directory; |
633 |
if (str_starts_with(abs_path, "/jscoverage-store/") && abs_path[18] != '\0') { |
634 |
char * dir = decode_uri_component(abs_path + 18); |
635 |
current_report_directory = make_path(report_directory, dir); |
636 |
free(dir); |
637 |
} |
638 |
else { |
639 |
current_report_directory = xstrdup(report_directory); |
640 |
} |
641 |
mkdir_if_necessary(current_report_directory); |
642 |
char * path = make_path(current_report_directory, "jscoverage.json"); |
643 |
|
644 |
/* check if the JSON file exists */ |
645 |
struct stat buf; |
646 |
if (stat(path, &buf) == 0) { |
647 |
/* it exists: merge */ |
648 |
FILE * f = fopen(path, "rb"); |
649 |
if (f == NULL) { |
650 |
result = 1; |
651 |
} |
652 |
else { |
653 |
result = merge(coverage, f); |
654 |
if (fclose(f) == EOF) { |
655 |
result = 1; |
656 |
} |
657 |
} |
658 |
if (result != 0) { |
659 |
free(current_report_directory); |
660 |
free(path); |
661 |
Coverage_delete(coverage); |
662 |
send_response(exchange, 500, "Could not merge with existing coverage data\n"); |
663 |
return; |
664 |
} |
665 |
} |
666 |
|
667 |
result = write_json(coverage, path); |
668 |
free(path); |
669 |
Coverage_delete(coverage); |
670 |
if (result != 0) { |
671 |
free(current_report_directory); |
672 |
send_response(exchange, 500, "Could not write coverage data\n"); |
673 |
return; |
674 |
} |
675 |
|
676 |
/* copy other files */ |
677 |
jscoverage_copy_resources(current_report_directory); |
678 |
path = make_path(current_report_directory, "jscoverage.js"); |
679 |
free(current_report_directory); |
680 |
FILE * f = fopen(path, "ab"); |
681 |
free(path); |
682 |
if (f == NULL) { |
683 |
send_response(exchange, 500, "Could not write to file: jscoverage.js\n"); |
684 |
return; |
685 |
} |
686 |
fputs("jscoverage_isReport = true;\r\n", f); |
687 |
if (fclose(f) == EOF) { |
688 |
send_response(exchange, 500, "Could not write to file: jscoverage.js\n"); |
689 |
return; |
690 |
} |
691 |
|
692 |
send_response(exchange, 200, "Coverage data stored\n"); |
693 |
} |
694 |
else if (str_starts_with(abs_path, "/jscoverage-shutdown")) { |
695 |
if (strcmp(HTTPExchange_get_method(exchange), "POST") != 0) { |
696 |
HTTPExchange_set_response_header(exchange, HTTP_ALLOW, "POST"); |
697 |
send_response(exchange, 405, "Method not allowed\n"); |
698 |
return; |
699 |
} |
700 |
|
701 |
/* check client IP address */ |
702 |
struct sockaddr_in client; |
703 |
if (HTTPExchange_get_peer(exchange, &client) != 0) { |
704 |
send_response(exchange, 500, "Cannot get client address\n"); |
705 |
return; |
706 |
} |
707 |
|
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"); |
728 |
return; |
729 |
} |
730 |
|
731 |
send_response(exchange, 200, "The server will now shut down\n"); |
732 |
HTTPServer_shutdown(); |
733 |
} |
734 |
else { |
735 |
const char * path = abs_path + 1; |
736 |
const struct Resource * resource = get_resource(path); |
737 |
if (resource == NULL) { |
738 |
send_response(exchange, 404, "Not found\n"); |
739 |
return; |
740 |
} |
741 |
HTTPExchange_set_response_header(exchange, HTTP_CONTENT_TYPE, get_content_type(path)); |
742 |
if (HTTPExchange_write_response(exchange, resource->data, resource->length) != 0) { |
743 |
HTTPServer_log_err("Warning: error writing to client\n"); |
744 |
return; |
745 |
} |
746 |
if (strcmp(abs_path, "/jscoverage.js") == 0) { |
747 |
const char * s = "jscoverage_isServer = true;\r\n"; |
748 |
if (HTTPExchange_write_response(exchange, s, strlen(s)) != 0) { |
749 |
HTTPServer_log_err("Warning: error writing to client\n"); |
750 |
} |
751 |
} |
752 |
} |
753 |
} |
754 |
|
755 |
static void instrument_js(const char * id, const uint16_t * characters, size_t num_characters, Stream * output_stream) { |
756 |
const struct Resource * resource = get_resource("report.js"); |
757 |
Stream_write(output_stream, resource->data, resource->length); |
758 |
|
759 |
LOCK(&javascript_mutex); |
760 |
jscoverage_instrument_js(id, characters, num_characters, output_stream); |
761 |
UNLOCK(&javascript_mutex); |
762 |
} |
763 |
|
764 |
static bool is_hop_by_hop_header(const char * h) { |
765 |
/* hop-by-hop headers (RFC 2616 13.5.1) */ |
766 |
return strcasecmp(h, HTTP_CONNECTION) == 0 || |
767 |
strcasecmp(h, "Keep-Alive") == 0 || |
768 |
strcasecmp(h, HTTP_PROXY_AUTHENTICATE) == 0 || |
769 |
strcasecmp(h, HTTP_PROXY_AUTHORIZATION) == 0 || |
770 |
strcasecmp(h, HTTP_TE) == 0 || |
771 |
strcasecmp(h, HTTP_TRAILER) == 0 || |
772 |
strcasecmp(h, HTTP_TRANSFER_ENCODING) == 0 || |
773 |
strcasecmp(h, HTTP_UPGRADE) == 0; |
774 |
} |
775 |
|
776 |
static void add_via_header(HTTPMessage * message, const char * version) { |
777 |
char * value; |
778 |
xasprintf(&value, "%s jscoverage-server", version); |
779 |
HTTPMessage_add_header(message, HTTP_VIA, value); |
780 |
free(value); |
781 |
} |
782 |
|
783 |
static int copy_http_message_body(HTTPMessage * from, HTTPMessage * to) __attribute__((warn_unused_result)); |
784 |
|
785 |
static int copy_http_message_body(HTTPMessage * from, HTTPMessage * to) { |
786 |
uint8_t * buffer[8192]; |
787 |
for (;;) { |
788 |
size_t bytes_read; |
789 |
int result = HTTPMessage_read_message_body(from, buffer, 8192, &bytes_read); |
790 |
if (result != 0) { |
791 |
return result; |
792 |
} |
793 |
if (bytes_read == 0) { |
794 |
return 0; |
795 |
} |
796 |
result = HTTPMessage_write(to, buffer, bytes_read); |
797 |
if (result != 0) { |
798 |
return result; |
799 |
} |
800 |
} |
801 |
} |
802 |
|
803 |
static void handle_proxy_request(HTTPExchange * client_exchange) { |
804 |
HTTPConnection * server_connection = NULL; |
805 |
HTTPExchange * server_exchange = NULL; |
806 |
|
807 |
const char * abs_path = HTTPExchange_get_abs_path(client_exchange); |
808 |
if (str_starts_with(abs_path, "/jscoverage")) { |
809 |
handle_jscoverage_request(client_exchange); |
810 |
return; |
811 |
} |
812 |
|
813 |
const char * host = HTTPExchange_get_host(client_exchange); |
814 |
uint16_t port = HTTPExchange_get_port(client_exchange); |
815 |
|
816 |
/* create a new connection */ |
817 |
server_connection = HTTPConnection_new_client(host, port); |
818 |
if (server_connection == NULL) { |
819 |
send_response(client_exchange, 504, "Could not connect to server\n"); |
820 |
goto done; |
821 |
} |
822 |
|
823 |
/* create a new exchange */ |
824 |
server_exchange = HTTPExchange_new(server_connection); |
825 |
|
826 |
HTTPExchange_set_method(server_exchange, HTTPExchange_get_method(client_exchange)); |
827 |
|
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) { |
848 |
if (strcasecmp(h->name, HTTP_TRAILER) == 0 || strcasecmp(h->name, HTTP_TRANSFER_ENCODING) == 0) { |
849 |
/* do nothing: we want to keep this header */ |
850 |
} |
851 |
else if (is_hop_by_hop_header(h->name) || |
852 |
strcasecmp(h->name, HTTP_ACCEPT_ENCODING) == 0 || |
853 |
strcasecmp(h->name, HTTP_RANGE) == 0) { |
854 |
continue; |
855 |
} |
856 |
HTTPExchange_add_request_header(server_exchange, h->name, h->value); |
857 |
} |
858 |
add_via_header(HTTPExchange_get_request_message(server_exchange), HTTPExchange_get_request_http_version(client_exchange)); |
859 |
|
860 |
/* send the request */ |
861 |
if (HTTPExchange_write_request_headers(server_exchange) != 0) { |
862 |
send_response(client_exchange, 502, "Could not write to server\n"); |
863 |
goto done; |
864 |
} |
865 |
|
866 |
/* handle POST or PUT */ |
867 |
if (HTTPExchange_request_has_body(client_exchange)) { |
868 |
HTTPMessage * client_request = HTTPExchange_get_request_message(client_exchange); |
869 |
HTTPMessage * server_request = HTTPExchange_get_request_message(server_exchange); |
870 |
if (copy_http_message_body(client_request, server_request) != 0) { |
871 |
send_response(client_exchange, 400, "Error copying request body from client to server\n"); |
872 |
goto done; |
873 |
} |
874 |
} |
875 |
|
876 |
if (HTTPExchange_flush_request(server_exchange) != 0) { |
877 |
send_response(client_exchange, 502, "Could not write to server\n"); |
878 |
goto done; |
879 |
} |
880 |
|
881 |
/* receive the response */ |
882 |
if (HTTPExchange_read_response_headers(server_exchange) != 0) { |
883 |
send_response(client_exchange, 502, "Could not read headers from server\n"); |
884 |
goto done; |
885 |
} |
886 |
|
887 |
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, HTTPExchange_get_request_uri(client_exchange))) { |
890 |
/* needs instrumentation */ |
891 |
Stream * input_stream = Stream_new(0); |
892 |
if (HTTPExchange_read_entire_response_entity_body(server_exchange, input_stream) != 0) { |
893 |
Stream_delete(input_stream); |
894 |
send_response(client_exchange, 502, "Could not read body from server\n"); |
895 |
goto done; |
896 |
} |
897 |
|
898 |
const char * request_uri = HTTPExchange_get_request_uri(client_exchange); |
899 |
char * encoding = HTTPMessage_get_charset(HTTPExchange_get_response_message(server_exchange)); |
900 |
if (encoding == NULL) { |
901 |
encoding = xstrdup(jscoverage_encoding); |
902 |
} |
903 |
uint16_t * characters; |
904 |
size_t num_characters; |
905 |
int result = jscoverage_bytes_to_characters(encoding, input_stream->data, input_stream->length, &characters, &num_characters); |
906 |
free(encoding); |
907 |
Stream_delete(input_stream); |
908 |
if (result == JSCOVERAGE_ERROR_ENCODING_NOT_SUPPORTED) { |
909 |
send_response(client_exchange, 500, "Encoding not supported\n"); |
910 |
goto done; |
911 |
} |
912 |
else if (result == JSCOVERAGE_ERROR_INVALID_BYTE_SEQUENCE) { |
913 |
send_response(client_exchange, 502, "Error decoding response\n"); |
914 |
goto done; |
915 |
} |
916 |
|
917 |
Stream * output_stream = Stream_new(0); |
918 |
instrument_js(request_uri, characters, num_characters, output_stream); |
919 |
|
920 |
/* send the headers to the client */ |
921 |
for (const HTTPHeader * h = HTTPExchange_get_response_headers(server_exchange); h != NULL; h = h->next) { |
922 |
if (is_hop_by_hop_header(h->name) || strcasecmp(h->name, HTTP_CONTENT_LENGTH) == 0) { |
923 |
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); |
930 |
} |
931 |
add_via_header(HTTPExchange_get_response_message(client_exchange), HTTPExchange_get_response_http_version(server_exchange)); |
932 |
HTTPExchange_set_response_content_length(client_exchange, output_stream->length); |
933 |
|
934 |
/* send the instrumented code to the client */ |
935 |
if (HTTPExchange_write_response(client_exchange, output_stream->data, output_stream->length) != 0) { |
936 |
HTTPServer_log_err("Warning: error writing to client\n"); |
937 |
} |
938 |
|
939 |
/* characters go on the cache */ |
940 |
/* |
941 |
free(characters); |
942 |
*/ |
943 |
Stream_delete(output_stream); |
944 |
add_cached_source(request_uri, characters, num_characters); |
945 |
} |
946 |
else { |
947 |
/* does not need instrumentation */ |
948 |
|
949 |
/* send the headers to the client */ |
950 |
for (const HTTPHeader * h = HTTPExchange_get_response_headers(server_exchange); h != NULL; h = h->next) { |
951 |
if (strcasecmp(h->name, HTTP_TRAILER) == 0 || strcasecmp(h->name, HTTP_TRANSFER_ENCODING) == 0) { |
952 |
/* do nothing: we want to keep this header */ |
953 |
} |
954 |
else if (is_hop_by_hop_header(h->name)) { |
955 |
continue; |
956 |
} |
957 |
HTTPExchange_add_response_header(client_exchange, h->name, h->value); |
958 |
} |
959 |
add_via_header(HTTPExchange_get_response_message(client_exchange), HTTPExchange_get_response_http_version(server_exchange)); |
960 |
|
961 |
if (HTTPExchange_write_response_headers(client_exchange) != 0) { |
962 |
HTTPServer_log_err("Warning: error writing to client\n"); |
963 |
goto done; |
964 |
} |
965 |
|
966 |
if (HTTPExchange_response_has_body(server_exchange)) { |
967 |
/* read the body from the server and send it to the client */ |
968 |
HTTPMessage * client_response = HTTPExchange_get_response_message(client_exchange); |
969 |
HTTPMessage * server_response = HTTPExchange_get_response_message(server_exchange); |
970 |
if (copy_http_message_body(server_response, client_response) != 0) { |
971 |
HTTPServer_log_err("Warning: error copying response body from server to client\n"); |
972 |
goto done; |
973 |
} |
974 |
} |
975 |
} |
976 |
|
977 |
done: |
978 |
if (server_exchange != NULL) { |
979 |
HTTPExchange_delete(server_exchange); |
980 |
} |
981 |
if (server_connection != NULL) { |
982 |
if (HTTPConnection_delete(server_connection) != 0) { |
983 |
HTTPServer_log_err("Warning: error closing connection to server\n"); |
984 |
} |
985 |
} |
986 |
} |
987 |
|
988 |
static void handle_local_request(HTTPExchange * exchange) { |
989 |
/* add the `Server' response-header (RFC 2616 14.38, 3.8) */ |
990 |
HTTPExchange_add_response_header(exchange, HTTP_SERVER, "jscoverage-server/" VERSION); |
991 |
|
992 |
char * decoded_path = NULL; |
993 |
char * filesystem_path = NULL; |
994 |
|
995 |
const char * abs_path = HTTPExchange_get_abs_path(exchange); |
996 |
assert(*abs_path != '\0'); |
997 |
|
998 |
decoded_path = decode_uri_component(abs_path); |
999 |
|
1000 |
if (str_starts_with(decoded_path, "/jscoverage")) { |
1001 |
handle_jscoverage_request(exchange); |
1002 |
goto done; |
1003 |
} |
1004 |
|
1005 |
if (strstr(decoded_path, "..") != NULL) { |
1006 |
send_response(exchange, 403, "Forbidden\n"); |
1007 |
goto done; |
1008 |
} |
1009 |
|
1010 |
filesystem_path = make_path(document_root, decoded_path + 1); |
1011 |
size_t filesystem_path_length = strlen(filesystem_path); |
1012 |
if (filesystem_path_length > 0 && filesystem_path[filesystem_path_length - 1] == '/') { |
1013 |
/* stat on Windows doesn't work with trailing slash */ |
1014 |
filesystem_path[filesystem_path_length - 1] = '\0'; |
1015 |
} |
1016 |
|
1017 |
struct stat buf; |
1018 |
if (stat(filesystem_path, &buf) == -1) { |
1019 |
send_response(exchange, 404, "Not found\n"); |
1020 |
goto done; |
1021 |
} |
1022 |
|
1023 |
if (S_ISDIR(buf.st_mode)) { |
1024 |
if (abs_path[strlen(abs_path) - 1] != '/') { |
1025 |
const char * request_uri = HTTPExchange_get_request_uri(exchange); |
1026 |
char * uri = xmalloc(strlen(request_uri) + 2); |
1027 |
strcpy(uri, request_uri); |
1028 |
strcat(uri, "/"); |
1029 |
HTTPExchange_add_response_header(exchange, "Location", uri); |
1030 |
free(uri); |
1031 |
send_response(exchange, 301, "Moved permanently\n"); |
1032 |
goto done; |
1033 |
} |
1034 |
|
1035 |
DIR * d = opendir(filesystem_path); |
1036 |
if (d == NULL) { |
1037 |
send_response(exchange, 404, "Not found\n"); |
1038 |
goto done; |
1039 |
} |
1040 |
|
1041 |
struct dirent * entry; |
1042 |
while ((entry = readdir(d)) != NULL) { |
1043 |
char * href = encode_uri_component(entry->d_name); |
1044 |
char * html_href = encode_html(href); |
1045 |
char * link = encode_html(entry->d_name); |
1046 |
char * directory_entry; |
1047 |
xasprintf(&directory_entry, "<a href=\"%s\">%s</a><br>\n", html_href, link); |
1048 |
if (HTTPExchange_write_response(exchange, directory_entry, strlen(directory_entry)) != 0) { |
1049 |
HTTPServer_log_err("Warning: error writing to client\n"); |
1050 |
} |
1051 |
free(directory_entry); |
1052 |
free(href); |
1053 |
free(html_href); |
1054 |
free(link); |
1055 |
} |
1056 |
closedir(d); |
1057 |
} |
1058 |
else if (S_ISREG(buf.st_mode)) { |
1059 |
FILE * f = fopen(filesystem_path, "rb"); |
1060 |
if (f == NULL) { |
1061 |
send_response(exchange, 404, "Not found\n"); |
1062 |
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); |
1078 |
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); |
1082 |
Stream_write_file_contents(input_stream, f); |
1083 |
|
1084 |
uint16_t * characters; |
1085 |
size_t num_characters; |
1086 |
int result = jscoverage_bytes_to_characters(jscoverage_encoding, input_stream->data, input_stream->length, &characters, &num_characters); |
1087 |
Stream_delete(input_stream); |
1088 |
|
1089 |
if (result == JSCOVERAGE_ERROR_ENCODING_NOT_SUPPORTED) { |
1090 |
send_response(exchange, 500, "Encoding not supported\n"); |
1091 |
goto done; |
1092 |
} |
1093 |
else if (result == JSCOVERAGE_ERROR_INVALID_BYTE_SEQUENCE) { |
1094 |
send_response(exchange, 500, "Error decoding JavaScript file\n"); |
1095 |
goto done; |
1096 |
} |
1097 |
|
1098 |
Stream * output_stream = Stream_new(0); |
1099 |
instrument_js(abs_path, characters, num_characters, output_stream); |
1100 |
free(characters); |
1101 |
|
1102 |
if (HTTPExchange_write_response(exchange, output_stream->data, output_stream->length) != 0) { |
1103 |
HTTPServer_log_err("Warning: error writing to client\n"); |
1104 |
} |
1105 |
Stream_delete(output_stream); |
1106 |
} |
1107 |
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]; |
1120 |
size_t bytes_read; |
1121 |
while ((bytes_read = fread(buffer, 1, 8192, f)) > 0) { |
1122 |
if (HTTPExchange_write_response(exchange, buffer, bytes_read) != 0) { |
1123 |
HTTPServer_log_err("Warning: error writing to client\n"); |
1124 |
} |
1125 |
} |
1126 |
} |
1127 |
fclose(f); |
1128 |
} |
1129 |
else { |
1130 |
send_response(exchange, 404, "Not found\n"); |
1131 |
goto done; |
1132 |
} |
1133 |
|
1134 |
done: |
1135 |
free(filesystem_path); |
1136 |
free(decoded_path); |
1137 |
} |
1138 |
|
1139 |
static void handler(HTTPExchange * exchange) { |
1140 |
if (verbose) { |
1141 |
HTTPServer_log_out("%s", HTTPExchange_get_request_line(exchange)); |
1142 |
} |
1143 |
|
1144 |
if (proxy) { |
1145 |
handle_proxy_request(exchange); |
1146 |
} |
1147 |
else { |
1148 |
handle_local_request(exchange); |
1149 |
} |
1150 |
} |
1151 |
|
1152 |
int main(int argc, char ** argv) { |
1153 |
program = "jscoverage-server"; |
1154 |
|
1155 |
const char * ip_address = "127.0.0.1"; |
1156 |
const char * port = "8080"; |
1157 |
int shutdown = 0; |
1158 |
|
1159 |
no_instrument = xnew(const char *, argc - 1); |
1160 |
|
1161 |
for (int i = 1; i < argc; i++) { |
1162 |
if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { |
1163 |
copy_resource_to_stream("jscoverage-server-help.txt", stdout); |
1164 |
exit(EXIT_SUCCESS); |
1165 |
} |
1166 |
else if (strcmp(argv[i], "-V") == 0 || strcmp(argv[i], "--version") == 0) { |
1167 |
version(); |
1168 |
} |
1169 |
else if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--verbose") == 0) { |
1170 |
verbose = 1; |
1171 |
} |
1172 |
|
1173 |
else if (strcmp(argv[i], "--report-dir") == 0) { |
1174 |
i++; |
1175 |
if (i == argc) { |
1176 |
fatal_command_line("--report-dir: option requires an argument"); |
1177 |
} |
1178 |
report_directory = argv[i]; |
1179 |
} |
1180 |
else if (strncmp(argv[i], "--report-dir=", 13) == 0) { |
1181 |
report_directory = argv[i] + 13; |
1182 |
} |
1183 |
|
1184 |
else if (strcmp(argv[i], "--document-root") == 0) { |
1185 |
i++; |
1186 |
if (i == argc) { |
1187 |
fatal_command_line("--document-root: option requires an argument"); |
1188 |
} |
1189 |
document_root = argv[i]; |
1190 |
} |
1191 |
else if (strncmp(argv[i], "--document-root=", 16) == 0) { |
1192 |
document_root = argv[i] + 16; |
1193 |
} |
1194 |
|
1195 |
else if (strcmp(argv[i], "--encoding") == 0) { |
1196 |
i++; |
1197 |
if (i == argc) { |
1198 |
fatal_command_line("--encoding: option requires an argument"); |
1199 |
} |
1200 |
jscoverage_encoding = argv[i]; |
1201 |
specified_encoding = jscoverage_encoding; |
1202 |
} |
1203 |
else if (strncmp(argv[i], "--encoding=", 11) == 0) { |
1204 |
jscoverage_encoding = argv[i] + 11; |
1205 |
specified_encoding = jscoverage_encoding; |
1206 |
} |
1207 |
|
1208 |
else if (strcmp(argv[i], "--ip-address") == 0) { |
1209 |
i++; |
1210 |
if (i == argc) { |
1211 |
fatal_command_line("--ip-address: option requires an argument"); |
1212 |
} |
1213 |
ip_address = argv[i]; |
1214 |
} |
1215 |
else if (strncmp(argv[i], "--ip-address=", 13) == 0) { |
1216 |
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) { |
1231 |
jscoverage_highlight = false; |
1232 |
} |
1233 |
|
1234 |
else if (strcmp(argv[i], "--no-instrument") == 0) { |
1235 |
i++; |
1236 |
if (i == argc) { |
1237 |
fatal_command_line("--no-instrument: option requires an argument"); |
1238 |
} |
1239 |
no_instrument[num_no_instrument] = argv[i]; |
1240 |
num_no_instrument++; |
1241 |
} |
1242 |
else if (strncmp(argv[i], "--no-instrument=", 16) == 0) { |
1243 |
no_instrument[num_no_instrument] = argv[i] + 16; |
1244 |
num_no_instrument++; |
1245 |
} |
1246 |
|
1247 |
else if (strcmp(argv[i], "--port") == 0) { |
1248 |
i++; |
1249 |
if (i == argc) { |
1250 |
fatal_command_line("--port: option requires an argument"); |
1251 |
} |
1252 |
port = argv[i]; |
1253 |
} |
1254 |
else if (strncmp(argv[i], "--port=", 7) == 0) { |
1255 |
port = argv[i] + 7; |
1256 |
} |
1257 |
|
1258 |
else if (strcmp(argv[i], "--proxy") == 0) { |
1259 |
proxy = 1; |
1260 |
} |
1261 |
|
1262 |
else if (strcmp(argv[i], "--shutdown") == 0) { |
1263 |
shutdown = 1; |
1264 |
} |
1265 |
|
1266 |
else if (strncmp(argv[i], "-", 1) == 0) { |
1267 |
fatal_command_line("unrecognized option `%s'", argv[i]); |
1268 |
} |
1269 |
else { |
1270 |
fatal_command_line("too many arguments"); |
1271 |
} |
1272 |
} |
1273 |
|
1274 |
/* check the port */ |
1275 |
char * end; |
1276 |
unsigned long numeric_port = strtoul(port, &end, 10); |
1277 |
if (*end != '\0') { |
1278 |
fatal_command_line("--port: option must be an integer"); |
1279 |
} |
1280 |
if (numeric_port > UINT16_MAX) { |
1281 |
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? */ |
1292 |
if (shutdown) { |
1293 |
#ifdef __MINGW32__ |
1294 |
WSADATA data; |
1295 |
if (WSAStartup(MAKEWORD(1, 1), &data) != 0) { |
1296 |
fatal("could not start Winsock"); |
1297 |
} |
1298 |
#endif |
1299 |
|
1300 |
HTTPConnection * connection = HTTPConnection_new_client(ip_address, numeric_port); |
1301 |
if (connection == NULL) { |
1302 |
fatal("could not connect to server"); |
1303 |
} |
1304 |
HTTPExchange * exchange = HTTPExchange_new(connection); |
1305 |
HTTPExchange_set_method(exchange, "POST"); |
1306 |
HTTPExchange_set_request_uri(exchange, "/jscoverage-shutdown"); |
1307 |
if (HTTPExchange_write_request_headers(exchange) != 0) { |
1308 |
fatal("could not write request headers to server"); |
1309 |
} |
1310 |
if (HTTPExchange_read_response_headers(exchange) != 0) { |
1311 |
fatal("could not read response headers from server"); |
1312 |
} |
1313 |
Stream * stream = Stream_new(0); |
1314 |
if (HTTPExchange_read_entire_response_entity_body(exchange, stream) != 0) { |
1315 |
fatal("could not read response body from server"); |
1316 |
} |
1317 |
fwrite(stream->data, 1, stream->length, stdout); |
1318 |
Stream_delete(stream); |
1319 |
HTTPExchange_delete(exchange); |
1320 |
if (HTTPConnection_delete(connection) != 0) { |
1321 |
fatal("could not close connection with server"); |
1322 |
} |
1323 |
exit(EXIT_SUCCESS); |
1324 |
} |
1325 |
|
1326 |
jscoverage_init(); |
1327 |
|
1328 |
#ifndef __MINGW32__ |
1329 |
/* handle broken pipe */ |
1330 |
signal(SIGPIPE, SIG_IGN); |
1331 |
#endif |
1332 |
|
1333 |
#ifdef __MINGW32__ |
1334 |
InitializeCriticalSection(&javascript_mutex); |
1335 |
InitializeCriticalSection(&source_cache_mutex); |
1336 |
#endif |
1337 |
|
1338 |
if (verbose) { |
1339 |
printf("Starting HTTP server on %s:%lu\n", ip_address, numeric_port); |
1340 |
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); |
1349 |
if (verbose) { |
1350 |
printf("Stopping HTTP server\n"); |
1351 |
fflush(stdout); |
1352 |
} |
1353 |
|
1354 |
jscoverage_cleanup(); |
1355 |
|
1356 |
free(no_instrument); |
1357 |
|
1358 |
LOCK(&source_cache_mutex); |
1359 |
while (source_cache != NULL) { |
1360 |
SourceCache * p = source_cache; |
1361 |
source_cache = source_cache->next; |
1362 |
free(p->url); |
1363 |
free(p->characters); |
1364 |
free(p); |
1365 |
} |
1366 |
UNLOCK(&source_cache_mutex); |
1367 |
|
1368 |
return 0; |
1369 |
} |