/[jscoverage]/trunk/jscoverage-server.c
ViewVC logotype

Contents of /trunk/jscoverage-server.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 146 - (show annotations)
Thu Jun 19 19:33:05 2008 UTC (11 years, 4 months ago) by siliconforks
File MIME type: text/plain
File size: 32843 byte(s)
For the local web server, use the abs_path as the ID of a JavaScript file instead of the entire URI.

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

  ViewVC Help
Powered by ViewVC 1.1.24