/[jscoverage]/tags/jscoverage-0.4/jscoverage-server.c
ViewVC logotype

Contents of /tags/jscoverage-0.4/jscoverage-server.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 119 - (show annotations)
Sun Jun 1 14:05:47 2008 UTC (11 years ago) by siliconforks
Original Path: trunk/jscoverage-server.c
File MIME type: text/plain
File size: 32069 byte(s)
Remove call to `stat'.

1 /*
2 jscoverage-server.c - JSCoverage server main routine
3 Copyright (C) 2008 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 <string.h>
26
27 #include <dirent.h>
28 #include <pthread.h>
29
30 #include "http-server.h"
31 #include "instrument-js.h"
32 #include "resource-manager.h"
33 #include "stream.h"
34 #include "util.h"
35
36 typedef struct SourceCache {
37 char * url;
38 Stream * source;
39 struct SourceCache * next;
40 } SourceCache;
41
42 static SourceCache * source_cache = NULL;
43
44 static const struct {
45 const char * extension;
46 const char * mime_type;
47 } mime_types[] = {
48 {".gif", "image/gif"},
49 {".jpg", "image/jpeg"},
50 {".jpeg", "image/jpeg"},
51 {".png", "image/png"},
52 {".css", "text/css"},
53 {".html", "text/html"},
54 {".htm", "text/html"},
55 {".js", "text/javascript"},
56 {".txt", "text/plain"},
57 {".xml", "application/xml"},
58 };
59
60 static bool verbose = false;
61 static const char * report_directory = "jscoverage-report";
62 static const char * document_root = ".";
63 static bool proxy = false;
64 static const char ** no_instrument;
65 static size_t num_no_instrument = 0;
66
67 pthread_mutex_t javascript_mutex = PTHREAD_MUTEX_INITIALIZER;
68 pthread_mutex_t source_cache_mutex = PTHREAD_MUTEX_INITIALIZER;
69
70 static Stream * find_cached_source(const char * url) {
71 Stream * result = NULL;
72 pthread_mutex_lock(&source_cache_mutex);
73 for (SourceCache * p = source_cache; p != NULL; p = p->next) {
74 if (strcmp(url, p->url) == 0) {
75 result = p->source;
76 break;
77 }
78 }
79 pthread_mutex_unlock(&source_cache_mutex);
80 return result;
81 }
82
83 static void add_cached_source(const char * url, Stream * source) {
84 SourceCache * new_source_cache = xmalloc(sizeof(SourceCache));
85 new_source_cache->url = xstrdup(url);
86 new_source_cache->source = source;
87 pthread_mutex_lock(&source_cache_mutex);
88 new_source_cache->next = source_cache;
89 source_cache = new_source_cache;
90 pthread_mutex_unlock(&source_cache_mutex);
91 }
92
93 static int get(const char * url, Stream * stream) __attribute__((warn_unused_result));
94
95 static int get(const char * url, Stream * stream) {
96 char * host = NULL;
97 uint16_t port;
98 char * abs_path = NULL;
99 char * query = NULL;
100 HTTPConnection * connection = NULL;
101 HTTPExchange * exchange = NULL;
102
103 int result = URL_parse(url, &host, &port, &abs_path, &query);
104 if (result != 0) {
105 goto done;
106 }
107
108 connection = HTTPConnection_new_client(host, port);
109 if (connection == NULL) {
110 result = -1;
111 goto done;
112 }
113
114 exchange = HTTPExchange_new(connection);
115 HTTPExchange_set_request_uri(exchange, url);
116 result = HTTPExchange_write_request_headers(exchange);
117 if (result != 0) {
118 goto done;
119 }
120
121 result = HTTPExchange_read_response_headers(exchange);
122 if (result != 0) {
123 goto done;
124 }
125
126 result = HTTPExchange_read_entire_response_entity_body(exchange, stream);
127 if (result != 0) {
128 goto done;
129 }
130
131 result = 0;
132
133 done:
134 if (exchange != NULL) {
135 HTTPExchange_delete(exchange);
136 }
137 if (connection != NULL) {
138 if (HTTPConnection_delete(connection) != 0) {
139 HTTPServer_log_err("Warning: error closing connection after retrieving URL: %s\n", url);
140 }
141 }
142 free(host);
143 free(abs_path);
144 free(query);
145 return result;
146 }
147
148 static void send_response(HTTPExchange * exchange, uint16_t status_code, const char * html) {
149 HTTPExchange_set_status_code(exchange, status_code);
150 if (HTTPExchange_write_response(exchange, html, strlen(html)) != 0) {
151 HTTPServer_log_err("Warning: error writing to client\n");
152 }
153 }
154
155 /*
156 RFC 2396, Appendix A: we are checking for `pchar'
157 */
158 static bool is_escaped(char c) {
159 /* `pchar' */
160 if (strchr(":@&=+$,", c) != NULL) {
161 return false;
162 }
163
164 if (isalnum((unsigned char) c)) {
165 return false;
166 }
167
168 /* `mark' */
169 if (strchr("-_.!~*'()", c) != NULL) {
170 return false;
171 }
172
173 return true;
174 }
175
176 static char * encode_uri_component(const char * s) {
177 size_t length = 0;
178 for (const char * p = s; *p != '\0'; p++) {
179 if (is_escaped(*p)) {
180 length = addst(length, 3);
181 }
182 else {
183 length = addst(length, 1);
184 }
185 }
186
187 length = addst(length, 1);
188 char * result = xmalloc(length);
189 size_t i = 0;
190 for (const char * p = s; *p != '\0'; p++) {
191 if (is_escaped(*p)) {
192 result[i] = '%';
193 i++;
194 snprintf(result + i, 3, "%02X", *p);
195 i += 2;
196 }
197 else {
198 result[i] = *p;
199 i++;
200 }
201 }
202 result[i] = '\0';
203
204 return result;
205 }
206
207 static const char * get_entity(char c) {
208 switch(c) {
209 case '<':
210 return "&lt;";
211 case '>':
212 return "&gt;";
213 case '&':
214 return "&amp;";
215 case '\'':
216 return "&apos;";
217 case '"':
218 return "&quot;";
219 default:
220 return NULL;
221 }
222 }
223
224 static char * encode_html(const char * s) {
225 size_t length = 0;
226 for (const char * p = s; *p != '\0'; p++) {
227 const char * entity = get_entity(*p);
228 if (entity == NULL) {
229 length = addst(length, 1);
230 }
231 else {
232 length = addst(length, strlen(entity));
233 }
234 }
235
236 length = addst(length, 1);
237 char * result = xmalloc(length);
238 size_t i = 0;
239 for (const char * p = s; *p != '\0'; p++) {
240 const char * entity = get_entity(*p);
241 if (entity == NULL) {
242 result[i] = *p;
243 i++;
244 }
245 else {
246 strcpy(result + i, entity);
247 i += strlen(entity);
248 }
249 }
250 result[i] = '\0';
251
252 return result;
253 }
254
255 static const char * get_content_type(const char * path) {
256 char * last_dot = strrchr(path, '.');
257 if (last_dot == NULL) {
258 return "application/octet-stream";
259 }
260 for (size_t i = 0; i < sizeof(mime_types) / sizeof(mime_types[0]); i++) {
261 if (strcmp(last_dot, mime_types[i].extension) == 0) {
262 return mime_types[i].mime_type;
263 }
264 }
265 return "application/octet-stream";
266 }
267
268 /**
269 Checks whether a URI is on the no-instrument list.
270 @param uri the HTTP "Request-URI"; must not be NULL, and must not be a zero-length string
271 @return true if the URI is on the no-instrument list, false otherwise
272 */
273 static bool is_no_instrument(const char * uri) {
274 assert(*uri != '\0');
275
276 for (size_t i = 0; i < num_no_instrument; i++) {
277 if (str_starts_with(uri, no_instrument[i])) {
278 return true;
279 }
280
281 /*
282 For a local URL, accept "/foo/bar" and "foo/bar" on the no-instrument list.
283 */
284 if (! proxy && str_starts_with(uri + 1, no_instrument[i])) {
285 return true;
286 }
287 }
288
289 return false;
290 }
291
292 static bool is_javascript(HTTPExchange * exchange) {
293 const char * header = HTTPExchange_find_response_header(exchange, HTTP_CONTENT_TYPE);
294 if (header == NULL) {
295 /* guess based on extension */
296 return str_ends_with(HTTPExchange_get_request_uri(exchange), ".js");
297 }
298 else {
299 char * semicolon = strchr(header, ';');
300 char * content_type;
301 if (semicolon == NULL) {
302 content_type = xstrdup(header);
303 }
304 else {
305 content_type = xstrndup(header, semicolon - header);
306 }
307 /* RFC 4329 */
308 bool result = strcmp(content_type, "text/javascript") == 0 ||
309 strcmp(content_type, "text/ecmascript") == 0 ||
310 strcmp(content_type, "text/javascript1.0") == 0 ||
311 strcmp(content_type, "text/javascript1.1") == 0 ||
312 strcmp(content_type, "text/javascript1.2") == 0 ||
313 strcmp(content_type, "text/javascript1.3") == 0 ||
314 strcmp(content_type, "text/javascript1.4") == 0 ||
315 strcmp(content_type, "text/javascript1.5") == 0 ||
316 strcmp(content_type, "text/jscript") == 0 ||
317 strcmp(content_type, "text/livescript") == 0 ||
318 strcmp(content_type, "text/x-javascript") == 0 ||
319 strcmp(content_type, "text/x-ecmascript") == 0 ||
320 strcmp(content_type, "application/x-javascript") == 0 ||
321 strcmp(content_type, "application/x-ecmascript") == 0 ||
322 strcmp(content_type, "application/javascript") == 0 ||
323 strcmp(content_type, "application/ecmascript") == 0;
324 free(content_type);
325 return result;
326 }
327 }
328
329 static bool should_instrument_request(HTTPExchange * exchange) {
330 if (! is_javascript(exchange)) {
331 return false;
332 }
333
334 if (is_no_instrument(HTTPExchange_get_request_uri(exchange))) {
335 return false;
336 }
337
338 return true;
339 }
340
341 static int merge(Coverage * coverage, FILE * f) __attribute__((warn_unused_result));
342
343 static int merge(Coverage * coverage, FILE * f) {
344 Stream * stream = Stream_new(0);
345 Stream_write_file_contents(stream, f);
346
347 pthread_mutex_lock(&javascript_mutex);
348 int result = jscoverage_parse_json(coverage, stream->data, stream->length);
349 pthread_mutex_unlock(&javascript_mutex);
350
351 Stream_delete(stream);
352 return result;
353 }
354
355 static void write_js_quoted_string(FILE * f, char * data, size_t length) {
356 putc('"', f);
357 for (size_t i = 0; i < length; i++) {
358 char c = data[i];
359 switch (c) {
360 case '\b':
361 fputs("\\b", f);
362 break;
363 case '\f':
364 fputs("\\f", f);
365 break;
366 case '\n':
367 fputs("\\n", f);
368 break;
369 case '\r':
370 fputs("\\r", f);
371 break;
372 case '\t':
373 fputs("\\t", f);
374 break;
375 case '\v':
376 fputs("\\v", f);
377 break;
378 case '"':
379 fputs("\\\"", f);
380 break;
381 case '\\':
382 fputs("\\\\", f);
383 break;
384 default:
385 putc(c, f);
386 break;
387 }
388 }
389 putc('"', f);
390 }
391
392 static void write_json_for_file(const FileCoverage * file_coverage, int i, void * p) {
393 FILE * f = p;
394
395 if (i > 0) {
396 putc(',', f);
397 }
398
399 write_js_quoted_string(f, file_coverage->id, strlen(file_coverage->id));
400
401 fputs(":{\"coverage\":[", f);
402 for (uint32_t i = 0; i <= file_coverage->num_lines; i++) {
403 if (i > 0) {
404 putc(',', f);
405 }
406 int timesExecuted = file_coverage->lines[i];
407 if (timesExecuted < 0) {
408 fputs("null", f);
409 }
410 else {
411 fprintf(f, "%d", timesExecuted);
412 }
413 }
414 fputs("],\"source\":", f);
415 if (file_coverage->source == NULL) {
416 if (proxy) {
417 Stream * stream = find_cached_source(file_coverage->id);
418 if (stream == NULL) {
419 stream = Stream_new(0);
420 if (get(file_coverage->id, stream) == 0) {
421 write_js_quoted_string(f, stream->data, stream->length);
422 add_cached_source(file_coverage->id, stream);
423 }
424 else {
425 fputs("\"\"", f);
426 HTTPServer_log_err("Warning: cannot retrieve URL: %s\n", file_coverage->id);
427 Stream_delete(stream);
428 }
429 }
430 else {
431 write_js_quoted_string(f, stream->data, stream->length);
432 }
433 }
434 else {
435 /* check that the path begins with / */
436 if (file_coverage->id[0] == '/') {
437 char * source_path = make_path(document_root, file_coverage->id + 1);
438 FILE * source_file = fopen(source_path, "r");
439 free(source_path);
440 if (source_file == NULL) {
441 fputs("\"\"", f);
442 HTTPServer_log_err("Warning: cannot open file: %s\n", file_coverage->id);
443 }
444 else {
445 Stream * stream = Stream_new(0);
446 Stream_write_file_contents(stream, source_file);
447 fclose(source_file);
448 write_js_quoted_string(f, stream->data, stream->length);
449 Stream_delete(stream);
450 }
451 }
452 else {
453 /* path does not begin with / */
454 fputs("\"\"", f);
455 HTTPServer_log_err("Warning: invalid source path: %s\n", file_coverage->id);
456 }
457 }
458 }
459 else {
460 write_js_quoted_string(f, file_coverage->source, strlen(file_coverage->source));
461 }
462 fputc('}', f);
463 }
464
465 static int write_json(Coverage * coverage, const char * path) __attribute__((warn_unused_result));
466
467 static int write_json(Coverage * coverage, const char * path) {
468 /* write the JSON */
469 FILE * f = fopen(path, "w");
470 if (f == NULL) {
471 return -1;
472 }
473 putc('{', f);
474 Coverage_foreach_file(coverage, write_json_for_file, f);
475 putc('}', f);
476 if (fclose(f) == EOF) {
477 return -1;
478 }
479 return 0;
480 }
481
482 static void handle_jscoverage_request(HTTPExchange * exchange) {
483 /* set the `Server' response-header (RFC 2616 14.38, 3.8) */
484 HTTPExchange_set_response_header(exchange, HTTP_SERVER, "jscoverage-server/" VERSION);
485
486 const char * abs_path = HTTPExchange_get_abs_path(exchange);
487 assert(*abs_path != '\0');
488 if (str_starts_with(abs_path, "/jscoverage-store")) {
489 if (strcmp(HTTPExchange_get_method(exchange), "POST") != 0) {
490 HTTPExchange_set_response_header(exchange, HTTP_ALLOW, "POST");
491 send_response(exchange, 405, "Method not allowed\n");
492 return;
493 }
494
495 Stream * json = Stream_new(0);
496
497 /* read the POST body */
498 if (HTTPExchange_read_entire_request_entity_body(exchange, json) != 0) {
499 Stream_delete(json);
500 send_response(exchange, 400, "Could not read request body\n");
501 return;
502 }
503
504 Coverage * coverage = Coverage_new();
505 pthread_mutex_lock(&javascript_mutex);
506 int result = jscoverage_parse_json(coverage, json->data, json->length);
507 pthread_mutex_unlock(&javascript_mutex);
508 Stream_delete(json);
509
510 if (result != 0) {
511 Coverage_delete(coverage);
512 send_response(exchange, 400, "Could not parse coverage data\n");
513 return;
514 }
515
516 mkdir_if_necessary(report_directory);
517 char * path = make_path(report_directory, "jscoverage.json");
518 FILE * f = fopen(path, "r");
519 if (f != NULL) {
520 /* it exists: merge */
521 result = merge(coverage, f);
522 if (fclose(f) == EOF) {
523 result = 1;
524 }
525 if (result != 0) {
526 free(path);
527 Coverage_delete(coverage);
528 send_response(exchange, 500, "Could not merge with existing coverage data\n");
529 return;
530 }
531 }
532
533 result = write_json(coverage, path);
534 free(path);
535 Coverage_delete(coverage);
536 if (result != 0) {
537 send_response(exchange, 500, "Could not write coverage data\n");
538 return;
539 }
540
541 /* copy other files */
542 jscoverage_copy_resources(report_directory);
543 path = make_path(report_directory, "jscoverage.js");
544 f = fopen(path, "ab");
545 free(path);
546 if (f == NULL) {
547 send_response(exchange, 500, "Could not write to file: jscoverage.js\n");
548 return;
549 }
550 fputs("jscoverage_isReport = true;\r\n", f);
551 if (fclose(f) == EOF) {
552 send_response(exchange, 500, "Could not write to file: jscoverage.js\n");
553 return;
554 }
555
556 send_response(exchange, 200, "Coverage data stored\n");
557 }
558 else if (str_starts_with(abs_path, "/jscoverage-shutdown")) {
559 if (strcmp(HTTPExchange_get_method(exchange), "POST") != 0) {
560 HTTPExchange_set_response_header(exchange, HTTP_ALLOW, "POST");
561 send_response(exchange, 405, "Method not allowed\n");
562 return;
563 }
564
565 /* allow only from localhost */
566 struct sockaddr_in client;
567 if (HTTPExchange_get_peer(exchange, &client) != 0) {
568 send_response(exchange, 500, "Cannot get client address\n");
569 return;
570 }
571 if (client.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) {
572 send_response(exchange, 403, "This operation can be performed only by localhost\n");
573 return;
574 }
575
576 send_response(exchange, 200, "The server will now shut down\n");
577 HTTPServer_shutdown();
578 }
579 else {
580 const char * path = abs_path + 1;
581 const struct Resource * resource = get_resource(path);
582 if (resource == NULL) {
583 send_response(exchange, 404, "Not found\n");
584 return;
585 }
586 HTTPExchange_set_response_header(exchange, HTTP_CONTENT_TYPE, get_content_type(path));
587 if (HTTPExchange_write_response(exchange, resource->data, resource->length) != 0) {
588 HTTPServer_log_err("Warning: error writing to client\n");
589 return;
590 }
591 if (strcmp(abs_path, "/jscoverage.js") == 0) {
592 const char * s = "jscoverage_isServer = true;\r\n";
593 if (HTTPExchange_write_response(exchange, s, strlen(s)) != 0) {
594 HTTPServer_log_err("Warning: error writing to client\n");
595 }
596 }
597 }
598 }
599
600 static void instrument_js(const char * id, Stream * input_stream, Stream * output_stream) {
601 pthread_mutex_lock(&javascript_mutex);
602 jscoverage_instrument_js(id, input_stream, output_stream);
603 pthread_mutex_unlock(&javascript_mutex);
604
605 const struct Resource * resource = get_resource("report.js");
606 Stream_write(output_stream, resource->data, resource->length);
607 }
608
609 static bool is_hop_by_hop_header(const char * h) {
610 /* hop-by-hop headers (RFC 2616 13.5.1) */
611 return strcasecmp(h, HTTP_CONNECTION) == 0 ||
612 strcasecmp(h, "Keep-Alive") == 0 ||
613 strcasecmp(h, HTTP_PROXY_AUTHENTICATE) == 0 ||
614 strcasecmp(h, HTTP_PROXY_AUTHORIZATION) == 0 ||
615 strcasecmp(h, HTTP_TE) == 0 ||
616 strcasecmp(h, HTTP_TRAILER) == 0 ||
617 strcasecmp(h, HTTP_TRANSFER_ENCODING) == 0 ||
618 strcasecmp(h, HTTP_UPGRADE) == 0;
619 }
620
621 static void add_via_header(HTTPMessage * message, const char * version) {
622 char * value;
623 xasprintf(&value, "%s jscoverage-server", version);
624 HTTPMessage_add_header(message, HTTP_VIA, value);
625 free(value);
626 }
627
628 static int copy_http_message_body(HTTPMessage * from, HTTPMessage * to) __attribute__((warn_unused_result));
629
630 static int copy_http_message_body(HTTPMessage * from, HTTPMessage * to) {
631 uint8_t * buffer[8192];
632 for (;;) {
633 size_t bytes_read;
634 int result = HTTPMessage_read_message_body(from, buffer, 8192, &bytes_read);
635 if (result != 0) {
636 return result;
637 }
638 if (bytes_read == 0) {
639 return 0;
640 }
641 result = HTTPMessage_write(to, buffer, bytes_read);
642 if (result != 0) {
643 return result;
644 }
645 }
646 }
647
648 static void handle_proxy_request(HTTPExchange * client_exchange) {
649 HTTPConnection * server_connection = NULL;
650 HTTPExchange * server_exchange = NULL;
651
652 const char * abs_path = HTTPExchange_get_abs_path(client_exchange);
653 if (str_starts_with(abs_path, "/jscoverage")) {
654 handle_jscoverage_request(client_exchange);
655 return;
656 }
657
658 const char * host = HTTPExchange_get_host(client_exchange);
659 uint16_t port = HTTPExchange_get_port(client_exchange);
660
661 /* create a new connection */
662 server_connection = HTTPConnection_new_client(host, port);
663 if (server_connection == NULL) {
664 send_response(client_exchange, 504, "Could not connect to server\n");
665 goto done;
666 }
667
668 /* create a new exchange */
669 server_exchange = HTTPExchange_new(server_connection);
670 HTTPExchange_set_method(server_exchange, HTTPExchange_get_method(client_exchange));
671 HTTPExchange_set_request_uri(server_exchange, HTTPExchange_get_request_uri(client_exchange));
672 for (const HTTPHeader * h = HTTPExchange_get_request_headers(client_exchange); h != NULL; h = h->next) {
673 if (strcasecmp(h->name, HTTP_TRAILER) == 0 || strcasecmp(h->name, HTTP_TRANSFER_ENCODING) == 0) {
674 /* do nothing: we want to keep this header */
675 }
676 else if (is_hop_by_hop_header(h->name) ||
677 strcasecmp(h->name, HTTP_ACCEPT_ENCODING) == 0 ||
678 strcasecmp(h->name, HTTP_RANGE) == 0) {
679 continue;
680 }
681 HTTPExchange_add_request_header(server_exchange, h->name, h->value);
682 }
683 add_via_header(HTTPExchange_get_request_message(server_exchange), HTTPExchange_get_request_http_version(client_exchange));
684
685 /* send the request */
686 if (HTTPExchange_write_request_headers(server_exchange) != 0) {
687 send_response(client_exchange, 502, "Could not write to server\n");
688 goto done;
689 }
690
691 /* handle POST or PUT */
692 if (HTTPExchange_request_has_body(client_exchange)) {
693 HTTPMessage * client_request = HTTPExchange_get_request_message(client_exchange);
694 HTTPMessage * server_request = HTTPExchange_get_request_message(server_exchange);
695 if (copy_http_message_body(client_request, server_request) != 0) {
696 send_response(client_exchange, 400, "Error copying request body from client to server\n");
697 goto done;
698 }
699 }
700
701 if (HTTPExchange_flush_request(server_exchange) != 0) {
702 send_response(client_exchange, 502, "Could not write to server\n");
703 goto done;
704 }
705
706 /* receive the response */
707 if (HTTPExchange_read_response_headers(server_exchange) != 0) {
708 send_response(client_exchange, 502, "Could not read headers from server\n");
709 goto done;
710 }
711
712 HTTPExchange_set_status_code(client_exchange, HTTPExchange_get_status_code(server_exchange));
713
714 if (HTTPExchange_response_has_body(server_exchange) && should_instrument_request(server_exchange)) {
715 /* needs instrumentation */
716 Stream * input_stream = Stream_new(0);
717 if (HTTPExchange_read_entire_response_entity_body(server_exchange, input_stream) != 0) {
718 Stream_delete(input_stream);
719 send_response(client_exchange, 502, "Could not read body from server\n");
720 goto done;
721 }
722
723 const char * request_uri = HTTPExchange_get_request_uri(client_exchange);
724 Stream * output_stream = Stream_new(0);
725 instrument_js(request_uri, input_stream, output_stream);
726
727 /* send the headers to the client */
728 for (const HTTPHeader * h = HTTPExchange_get_response_headers(server_exchange); h != NULL; h = h->next) {
729 if (is_hop_by_hop_header(h->name) || strcasecmp(h->name, HTTP_CONTENT_LENGTH) == 0) {
730 continue;
731 }
732 HTTPExchange_add_response_header(client_exchange, h->name, h->value);
733 }
734 add_via_header(HTTPExchange_get_response_message(client_exchange), HTTPExchange_get_response_http_version(server_exchange));
735 HTTPExchange_set_response_content_length(client_exchange, output_stream->length);
736
737 /* send the instrumented code to the client */
738 if (HTTPExchange_write_response(client_exchange, output_stream->data, output_stream->length) != 0) {
739 HTTPServer_log_err("Warning: error writing to client\n");
740 }
741
742 /* input_stream goes on the cache */
743 /*
744 Stream_delete(input_stream);
745 */
746 Stream_delete(output_stream);
747 add_cached_source(request_uri, input_stream);
748 }
749 else {
750 /* does not need instrumentation */
751
752 /* send the headers to the client */
753 for (const HTTPHeader * h = HTTPExchange_get_response_headers(server_exchange); h != NULL; h = h->next) {
754 if (strcasecmp(h->name, HTTP_TRAILER) == 0 || strcasecmp(h->name, HTTP_TRANSFER_ENCODING) == 0) {
755 /* do nothing: we want to keep this header */
756 }
757 else if (is_hop_by_hop_header(h->name)) {
758 continue;
759 }
760 HTTPExchange_add_response_header(client_exchange, h->name, h->value);
761 }
762 add_via_header(HTTPExchange_get_response_message(client_exchange), HTTPExchange_get_response_http_version(server_exchange));
763
764 if (HTTPExchange_write_response_headers(client_exchange) != 0) {
765 HTTPServer_log_err("Warning: error writing to client\n");
766 goto done;
767 }
768
769 if (HTTPExchange_response_has_body(server_exchange)) {
770 /* read the body from the server and send it to the client */
771 HTTPMessage * client_response = HTTPExchange_get_response_message(client_exchange);
772 HTTPMessage * server_response = HTTPExchange_get_response_message(server_exchange);
773 if (copy_http_message_body(server_response, client_response) != 0) {
774 HTTPServer_log_err("Warning: error copying response body from server to client\n");
775 goto done;
776 }
777 }
778 }
779
780 done:
781 if (server_exchange != NULL) {
782 HTTPExchange_delete(server_exchange);
783 }
784 if (server_connection != NULL) {
785 if (HTTPConnection_delete(server_connection) != 0) {
786 HTTPServer_log_err("Warning: error closing connection to server\n");
787 }
788 }
789 }
790
791 static void handle_local_request(HTTPExchange * exchange) {
792 /* add the `Server' response-header (RFC 2616 14.38, 3.8) */
793 HTTPExchange_add_response_header(exchange, HTTP_SERVER, "jscoverage-server/" VERSION);
794
795 char * filesystem_path = NULL;
796
797 const char * abs_path = HTTPExchange_get_abs_path(exchange);
798 assert(*abs_path != '\0');
799
800 if (str_starts_with(abs_path, "/jscoverage")) {
801 handle_jscoverage_request(exchange);
802 goto done;
803 }
804
805 if (strstr(abs_path, "..") != NULL) {
806 send_response(exchange, 403, "Forbidden\n");
807 goto done;
808 }
809
810 filesystem_path = make_path(document_root, abs_path + 1);
811
812 struct stat buf;
813 if (stat(filesystem_path, &buf) == -1) {
814 send_response(exchange, 404, "Not found\n");
815 goto done;
816 }
817
818 if (S_ISDIR(buf.st_mode)) {
819 if (filesystem_path[strlen(filesystem_path) - 1] != '/') {
820 const char * request_uri = HTTPExchange_get_request_uri(exchange);
821 char * uri = xmalloc(strlen(request_uri) + 2);
822 strcpy(uri, request_uri);
823 strcat(uri, "/");
824 HTTPExchange_add_response_header(exchange, "Location", uri);
825 free(uri);
826 send_response(exchange, 301, "Moved permanently\n");
827 goto done;
828 }
829
830 DIR * d = opendir(filesystem_path);
831 if (d == NULL) {
832 send_response(exchange, 404, "Not found\n");
833 goto done;
834 }
835
836 struct dirent * entry;
837 while ((entry = readdir(d)) != NULL) {
838 char * href = encode_uri_component(entry->d_name);
839 char * html_href = encode_html(href);
840 char * link = encode_html(entry->d_name);
841 char * directory_entry;
842 xasprintf(&directory_entry, "<a href=\"%s\">%s</a><br>\n", html_href, link);
843 if (HTTPExchange_write_response(exchange, directory_entry, strlen(directory_entry)) != 0) {
844 HTTPServer_log_err("Warning: error writing to client\n");
845 }
846 free(directory_entry);
847 free(href);
848 free(html_href);
849 free(link);
850 }
851 closedir(d);
852 }
853 else if (S_ISREG(buf.st_mode)) {
854 FILE * f = fopen(filesystem_path, "r");
855 if (f == NULL) {
856 send_response(exchange, 404, "Not found\n");
857 goto done;
858 }
859
860 const char * content_type = get_content_type(filesystem_path);
861 HTTPExchange_set_response_header(exchange, HTTP_CONTENT_TYPE, content_type);
862 const char * request_uri = HTTPExchange_get_request_uri(exchange);
863 if (strcmp(content_type, "text/javascript") == 0 && ! is_no_instrument(request_uri)) {
864 Stream * input_stream = Stream_new(0);
865 Stream * output_stream = Stream_new(0);
866
867 Stream_write_file_contents(input_stream, f);
868
869 instrument_js(request_uri, input_stream, output_stream);
870
871 if (HTTPExchange_write_response(exchange, output_stream->data, output_stream->length) != 0) {
872 HTTPServer_log_err("Warning: error writing to client\n");
873 }
874
875 Stream_delete(input_stream);
876 Stream_delete(output_stream);
877 }
878 else {
879 char buffer[8192];
880 size_t bytes_read;
881 while ((bytes_read = fread(buffer, 1, 8192, f)) > 0) {
882 if (HTTPExchange_write_response(exchange, buffer, bytes_read) != 0) {
883 HTTPServer_log_err("Warning: error writing to client\n");
884 }
885 }
886 }
887 fclose(f);
888 }
889 else {
890 send_response(exchange, 404, "Not found\n");
891 goto done;
892 }
893
894 done:
895 free(filesystem_path);
896 }
897
898 static void handler(HTTPExchange * exchange) {
899 if (verbose) {
900 HTTPServer_log_out("%s", HTTPExchange_get_request_line(exchange));
901 }
902
903 if (proxy) {
904 handle_proxy_request(exchange);
905 }
906 else {
907 handle_local_request(exchange);
908 }
909 }
910
911 int main(int argc, char ** argv) {
912 program = "jscoverage-server";
913
914 const char * ip_address = "127.0.0.1";
915 const char * port = "8080";
916 int shutdown = 0;
917
918 no_instrument = xnew(const char *, argc - 1);
919
920 for (int i = 1; i < argc; i++) {
921 if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
922 copy_resource_to_stream("jscoverage-server-help.txt", stdout);
923 exit(EXIT_SUCCESS);
924 }
925 else if (strcmp(argv[i], "-V") == 0 || strcmp(argv[i], "--version") == 0) {
926 printf("jscoverage-server %s\n", VERSION);
927 exit(EXIT_SUCCESS);
928 }
929 else if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--verbose") == 0) {
930 verbose = 1;
931 }
932
933 else if (strcmp(argv[i], "--report-dir") == 0) {
934 i++;
935 if (i == argc) {
936 fatal("--report-dir: option requires an argument");
937 }
938 report_directory = argv[i];
939 }
940 else if (strncmp(argv[i], "--report-dir=", 13) == 0) {
941 report_directory = argv[i] + 13;
942 }
943
944 else if (strcmp(argv[i], "--document-root") == 0) {
945 i++;
946 if (i == argc) {
947 fatal("--document-root: option requires an argument");
948 }
949 document_root = argv[i];
950 }
951 else if (strncmp(argv[i], "--document-root=", 16) == 0) {
952 document_root = argv[i] + 16;
953 }
954
955 else if (strcmp(argv[i], "--ip-address") == 0) {
956 i++;
957 if (i == argc) {
958 fatal("--ip-address: option requires an argument");
959 }
960 ip_address = argv[i];
961 }
962 else if (strncmp(argv[i], "--ip-address=", 13) == 0) {
963 ip_address = argv[i] + 13;
964 }
965
966 else if (strcmp(argv[i], "--no-instrument") == 0) {
967 i++;
968 if (i == argc) {
969 fatal("--no-instrument: option requires an argument");
970 }
971 no_instrument[num_no_instrument] = argv[i];
972 num_no_instrument++;
973 }
974 else if (strncmp(argv[i], "--no-instrument=", 16) == 0) {
975 no_instrument[num_no_instrument] = argv[i] + 16;
976 num_no_instrument++;
977 }
978
979 else if (strcmp(argv[i], "--port") == 0) {
980 i++;
981 if (i == argc) {
982 fatal("--port: option requires an argument");
983 }
984 port = argv[i];
985 }
986 else if (strncmp(argv[i], "--port=", 7) == 0) {
987 port = argv[i] + 7;
988 }
989
990 else if (strcmp(argv[i], "--proxy") == 0) {
991 proxy = 1;
992 }
993
994 else if (strcmp(argv[i], "--shutdown") == 0) {
995 shutdown = 1;
996 }
997
998 else if (strncmp(argv[i], "-", 1) == 0) {
999 fatal("unrecognized option `%s'", argv[i]);
1000 }
1001 else {
1002 fatal("too many arguments");
1003 }
1004 }
1005
1006 /* check the port */
1007 char * end;
1008 unsigned long numeric_port = strtoul(port, &end, 10);
1009 if (*end != '\0') {
1010 fatal("--port: option must be an integer");
1011 }
1012 if (numeric_port > UINT16_MAX) {
1013 fatal("--port: option must be 16 bits");
1014 }
1015
1016 /* is this a shutdown? */
1017 if (shutdown) {
1018 /* INADDR_LOOPBACK */
1019 HTTPConnection * connection = HTTPConnection_new_client("127.0.0.1", numeric_port);
1020 if (connection == NULL) {
1021 fatal("could not connect to server");
1022 }
1023 HTTPExchange * exchange = HTTPExchange_new(connection);
1024 HTTPExchange_set_method(exchange, "POST");
1025 HTTPExchange_set_request_uri(exchange, "/jscoverage-shutdown");
1026 if (HTTPExchange_write_request_headers(exchange) != 0) {
1027 fatal("could not write request headers to server");
1028 }
1029 if (HTTPExchange_read_response_headers(exchange) != 0) {
1030 fatal("could not read response headers from server");
1031 }
1032 Stream * stream = Stream_new(0);
1033 if (HTTPExchange_read_entire_response_entity_body(exchange, stream) != 0) {
1034 fatal("could not read response body from server");
1035 }
1036 fwrite(stream->data, 1, stream->length, stdout);
1037 Stream_delete(stream);
1038 HTTPExchange_delete(exchange);
1039 if (HTTPConnection_delete(connection) != 0) {
1040 fatal("could not close connection with server");
1041 }
1042 exit(EXIT_SUCCESS);
1043 }
1044
1045 jscoverage_init();
1046
1047 /* handle broken pipe */
1048 signal(SIGPIPE, SIG_IGN);
1049
1050 if (verbose) {
1051 printf("Starting HTTP server on %s:%lu\n", ip_address, numeric_port);
1052 }
1053 HTTPServer_run(ip_address, (uint16_t) numeric_port, handler);
1054 if (verbose) {
1055 printf("Stopping HTTP server\n");
1056 }
1057
1058 jscoverage_cleanup();
1059
1060 free(no_instrument);
1061
1062 pthread_mutex_lock(&source_cache_mutex);
1063 while (source_cache != NULL) {
1064 SourceCache * p = source_cache;
1065 source_cache = source_cache->next;
1066 free(p->url);
1067 Stream_delete(p->source);
1068 free(p);
1069 }
1070 pthread_mutex_unlock(&source_cache_mutex);
1071
1072 return 0;
1073 }

  ViewVC Help
Powered by ViewVC 1.1.24