/[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 116 - (show annotations)
Sat May 31 21:42:36 2008 UTC (11 years, 3 months ago) by siliconforks
Original Path: trunk/jscoverage-server.c
File MIME type: text/plain
File size: 32190 byte(s)
Add jscoverage-server.

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, const char * path, size_t size) __attribute__((warn_unused_result));
342
343 static int merge(Coverage * coverage, const char * path, size_t size) {
344 FILE * f = fopen(path, "r");
345 if (f == NULL) {
346 return -1;
347 }
348 uint8_t * buffer = xmalloc(size);
349 if (fread(buffer, 1, size, f) != size) {
350 fclose(f);
351 free(buffer);
352 return -1;
353 }
354 fclose(f);
355
356 pthread_mutex_lock(&javascript_mutex);
357 int result = jscoverage_parse_json(coverage, buffer, size);
358 pthread_mutex_unlock(&javascript_mutex);
359
360 free(buffer);
361 return result;
362 }
363
364 static void write_js_quoted_string(FILE * f, char * data, size_t length) {
365 putc('"', f);
366 for (size_t i = 0; i < length; i++) {
367 char c = data[i];
368 switch (c) {
369 case '\b':
370 fputs("\\b", f);
371 break;
372 case '\f':
373 fputs("\\f", f);
374 break;
375 case '\n':
376 fputs("\\n", f);
377 break;
378 case '\r':
379 fputs("\\r", f);
380 break;
381 case '\t':
382 fputs("\\t", f);
383 break;
384 case '\v':
385 fputs("\\v", f);
386 break;
387 case '"':
388 fputs("\\\"", f);
389 break;
390 case '\\':
391 fputs("\\\\", f);
392 break;
393 default:
394 putc(c, f);
395 break;
396 }
397 }
398 putc('"', f);
399 }
400
401 static void write_json_for_file(const FileCoverage * file_coverage, int i, void * p) {
402 FILE * f = p;
403
404 if (i > 0) {
405 putc(',', f);
406 }
407
408 write_js_quoted_string(f, file_coverage->id, strlen(file_coverage->id));
409
410 fputs(":{\"coverage\":[", f);
411 for (uint32_t i = 0; i <= file_coverage->num_lines; i++) {
412 if (i > 0) {
413 putc(',', f);
414 }
415 int timesExecuted = file_coverage->lines[i];
416 if (timesExecuted < 0) {
417 fputs("null", f);
418 }
419 else {
420 fprintf(f, "%d", timesExecuted);
421 }
422 }
423 fputs("],\"source\":", f);
424 if (file_coverage->source == NULL) {
425 if (proxy) {
426 Stream * stream = find_cached_source(file_coverage->id);
427 if (stream == NULL) {
428 stream = Stream_new(0);
429 if (get(file_coverage->id, stream) == 0) {
430 write_js_quoted_string(f, stream->data, stream->length);
431 add_cached_source(file_coverage->id, stream);
432 }
433 else {
434 fputs("\"\"", f);
435 HTTPServer_log_err("Warning: cannot retrieve URL: %s\n", file_coverage->id);
436 Stream_delete(stream);
437 }
438 }
439 else {
440 write_js_quoted_string(f, stream->data, stream->length);
441 }
442 }
443 else {
444 /* check that the path begins with / */
445 if (file_coverage->id[0] == '/') {
446 char * source_path = make_path(document_root, file_coverage->id + 1);
447 FILE * source_file = fopen(source_path, "r");
448 free(source_path);
449 if (source_file == NULL) {
450 fputs("\"\"", f);
451 HTTPServer_log_err("Warning: cannot open file: %s\n", file_coverage->id);
452 }
453 else {
454 Stream * stream = Stream_new(0);
455 Stream_write_file_contents(stream, source_file);
456 fclose(source_file);
457 write_js_quoted_string(f, stream->data, stream->length);
458 Stream_delete(stream);
459 }
460 }
461 else {
462 /* path does not begin with / */
463 fputs("\"\"", f);
464 HTTPServer_log_err("Warning: invalid source path: %s\n", file_coverage->id);
465 }
466 }
467 }
468 else {
469 write_js_quoted_string(f, file_coverage->source, strlen(file_coverage->source));
470 }
471 fputc('}', f);
472 }
473
474 static int write_json(Coverage * coverage, const char * path) __attribute__((warn_unused_result));
475
476 static int write_json(Coverage * coverage, const char * path) {
477 /* write the JSON */
478 FILE * f = fopen(path, "w");
479 if (f == NULL) {
480 return -1;
481 }
482 putc('{', f);
483 Coverage_foreach_file(coverage, write_json_for_file, f);
484 putc('}', f);
485 if (fclose(f) == EOF) {
486 return -1;
487 }
488 return 0;
489 }
490
491 static void handle_jscoverage_request(HTTPExchange * exchange) {
492 /* set the `Server' response-header (RFC 2616 14.38, 3.8) */
493 HTTPExchange_set_response_header(exchange, HTTP_SERVER, "jscoverage-server/" VERSION);
494
495 const char * abs_path = HTTPExchange_get_abs_path(exchange);
496 assert(*abs_path != '\0');
497 if (str_starts_with(abs_path, "/jscoverage-store")) {
498 if (strcmp(HTTPExchange_get_method(exchange), "POST") != 0) {
499 HTTPExchange_set_response_header(exchange, HTTP_ALLOW, "POST");
500 send_response(exchange, 405, "Method not allowed\n");
501 return;
502 }
503
504 Stream * json = Stream_new(0);
505
506 /* read the POST body */
507 if (HTTPExchange_read_entire_request_entity_body(exchange, json) != 0) {
508 Stream_delete(json);
509 send_response(exchange, 400, "Could not read request body\n");
510 return;
511 }
512
513 Coverage * coverage = Coverage_new();
514 pthread_mutex_lock(&javascript_mutex);
515 int result = jscoverage_parse_json(coverage, json->data, json->length);
516 pthread_mutex_unlock(&javascript_mutex);
517 Stream_delete(json);
518
519 if (result != 0) {
520 Coverage_delete(coverage);
521 send_response(exchange, 400, "Could not parse coverage data\n");
522 return;
523 }
524
525 mkdir_if_necessary(report_directory);
526 char * path = make_path(report_directory, "jscoverage.json");
527 struct stat buf;
528 if (stat(path, &buf) == 0) {
529 /* it exists: merge */
530 result = merge(coverage, path, buf.st_size);
531 if (result != 0) {
532 free(path);
533 Coverage_delete(coverage);
534 send_response(exchange, 500, "Could not merge with existing coverage data\n");
535 return;
536 }
537 }
538
539 result = write_json(coverage, path);
540 free(path);
541 Coverage_delete(coverage);
542 if (result != 0) {
543 send_response(exchange, 500, "Could not write coverage data\n");
544 return;
545 }
546
547 /* copy other files */
548 jscoverage_copy_resources(report_directory);
549 path = make_path(report_directory, "jscoverage.js");
550 FILE * f = fopen(path, "ab");
551 free(path);
552 if (f == NULL) {
553 send_response(exchange, 500, "Could not write to file: jscoverage.js\n");
554 return;
555 }
556 fputs("jscoverage_isReport = true;\r\n", f);
557 if (fclose(f) == EOF) {
558 send_response(exchange, 500, "Could not write to file: jscoverage.js\n");
559 return;
560 }
561
562 send_response(exchange, 200, "Coverage data stored\n");
563 }
564 else if (str_starts_with(abs_path, "/jscoverage-shutdown")) {
565 if (strcmp(HTTPExchange_get_method(exchange), "POST") != 0) {
566 HTTPExchange_set_response_header(exchange, HTTP_ALLOW, "POST");
567 send_response(exchange, 405, "Method not allowed\n");
568 return;
569 }
570
571 /* allow only from localhost */
572 struct sockaddr_in client;
573 if (HTTPExchange_get_peer(exchange, &client) != 0) {
574 send_response(exchange, 500, "Cannot get client address\n");
575 return;
576 }
577 if (client.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) {
578 send_response(exchange, 403, "This operation can be performed only by localhost\n");
579 return;
580 }
581
582 send_response(exchange, 200, "The server will now shut down\n");
583 HTTPServer_shutdown();
584 }
585 else {
586 const char * path = abs_path + 1;
587 const struct Resource * resource = get_resource(path);
588 if (resource == NULL) {
589 send_response(exchange, 404, "Not found\n");
590 return;
591 }
592 HTTPExchange_set_response_header(exchange, HTTP_CONTENT_TYPE, get_content_type(path));
593 if (HTTPExchange_write_response(exchange, resource->data, resource->length) != 0) {
594 HTTPServer_log_err("Warning: error writing to client\n");
595 return;
596 }
597 if (strcmp(abs_path, "/jscoverage.js") == 0) {
598 const char * s = "jscoverage_isServer = true;\r\n";
599 if (HTTPExchange_write_response(exchange, s, strlen(s)) != 0) {
600 HTTPServer_log_err("Warning: error writing to client\n");
601 }
602 }
603 }
604 }
605
606 static void instrument_js(const char * id, Stream * input_stream, Stream * output_stream) {
607 pthread_mutex_lock(&javascript_mutex);
608 jscoverage_instrument_js(id, input_stream, output_stream);
609 pthread_mutex_unlock(&javascript_mutex);
610
611 const struct Resource * resource = get_resource("report.js");
612 Stream_write(output_stream, resource->data, resource->length);
613 }
614
615 static bool is_hop_by_hop_header(const char * h) {
616 /* hop-by-hop headers (RFC 2616 13.5.1) */
617 return strcasecmp(h, HTTP_CONNECTION) == 0 ||
618 strcasecmp(h, "Keep-Alive") == 0 ||
619 strcasecmp(h, HTTP_PROXY_AUTHENTICATE) == 0 ||
620 strcasecmp(h, HTTP_PROXY_AUTHORIZATION) == 0 ||
621 strcasecmp(h, HTTP_TE) == 0 ||
622 strcasecmp(h, HTTP_TRAILER) == 0 ||
623 strcasecmp(h, HTTP_TRANSFER_ENCODING) == 0 ||
624 strcasecmp(h, HTTP_UPGRADE) == 0;
625 }
626
627 static void add_via_header(HTTPMessage * message, const char * version) {
628 char * value;
629 xasprintf(&value, "%s jscoverage-server", version);
630 HTTPMessage_add_header(message, HTTP_VIA, value);
631 free(value);
632 }
633
634 static int copy_http_message_body(HTTPMessage * from, HTTPMessage * to) __attribute__((warn_unused_result));
635
636 static int copy_http_message_body(HTTPMessage * from, HTTPMessage * to) {
637 uint8_t * buffer[8192];
638 for (;;) {
639 size_t bytes_read;
640 int result = HTTPMessage_read_message_body(from, buffer, 8192, &bytes_read);
641 if (result != 0) {
642 return result;
643 }
644 if (bytes_read == 0) {
645 return 0;
646 }
647 result = HTTPMessage_write(to, buffer, bytes_read);
648 if (result != 0) {
649 return result;
650 }
651 }
652 }
653
654 static void handle_proxy_request(HTTPExchange * client_exchange) {
655 HTTPConnection * server_connection = NULL;
656 HTTPExchange * server_exchange = NULL;
657
658 const char * abs_path = HTTPExchange_get_abs_path(client_exchange);
659 if (str_starts_with(abs_path, "/jscoverage")) {
660 handle_jscoverage_request(client_exchange);
661 return;
662 }
663
664 const char * host = HTTPExchange_get_host(client_exchange);
665 uint16_t port = HTTPExchange_get_port(client_exchange);
666
667 /* create a new connection */
668 server_connection = HTTPConnection_new_client(host, port);
669 if (server_connection == NULL) {
670 send_response(client_exchange, 504, "Could not connect to server\n");
671 goto done;
672 }
673
674 /* create a new exchange */
675 server_exchange = HTTPExchange_new(server_connection);
676 HTTPExchange_set_method(server_exchange, HTTPExchange_get_method(client_exchange));
677 HTTPExchange_set_request_uri(server_exchange, HTTPExchange_get_request_uri(client_exchange));
678 for (const HTTPHeader * h = HTTPExchange_get_request_headers(client_exchange); h != NULL; h = h->next) {
679 if (strcasecmp(h->name, HTTP_TRAILER) == 0 || strcasecmp(h->name, HTTP_TRANSFER_ENCODING) == 0) {
680 /* do nothing: we want to keep this header */
681 }
682 else if (is_hop_by_hop_header(h->name) ||
683 strcasecmp(h->name, HTTP_ACCEPT_ENCODING) == 0 ||
684 strcasecmp(h->name, HTTP_RANGE) == 0) {
685 continue;
686 }
687 HTTPExchange_add_request_header(server_exchange, h->name, h->value);
688 }
689 add_via_header(HTTPExchange_get_request_message(server_exchange), HTTPExchange_get_request_http_version(client_exchange));
690
691 /* send the request */
692 if (HTTPExchange_write_request_headers(server_exchange) != 0) {
693 send_response(client_exchange, 502, "Could not write to server\n");
694 goto done;
695 }
696
697 /* handle POST or PUT */
698 if (HTTPExchange_request_has_body(client_exchange)) {
699 HTTPMessage * client_request = HTTPExchange_get_request_message(client_exchange);
700 HTTPMessage * server_request = HTTPExchange_get_request_message(server_exchange);
701 if (copy_http_message_body(client_request, server_request) != 0) {
702 send_response(client_exchange, 400, "Error copying request body from client to server\n");
703 goto done;
704 }
705 }
706
707 if (HTTPExchange_flush_request(server_exchange) != 0) {
708 send_response(client_exchange, 502, "Could not write to server\n");
709 goto done;
710 }
711
712 /* receive the response */
713 if (HTTPExchange_read_response_headers(server_exchange) != 0) {
714 send_response(client_exchange, 502, "Could not read headers from server\n");
715 goto done;
716 }
717
718 HTTPExchange_set_status_code(client_exchange, HTTPExchange_get_status_code(server_exchange));
719
720 if (HTTPExchange_response_has_body(server_exchange) && should_instrument_request(server_exchange)) {
721 /* needs instrumentation */
722 Stream * input_stream = Stream_new(0);
723 if (HTTPExchange_read_entire_response_entity_body(server_exchange, input_stream) != 0) {
724 Stream_delete(input_stream);
725 send_response(client_exchange, 502, "Could not read body from server\n");
726 goto done;
727 }
728
729 const char * request_uri = HTTPExchange_get_request_uri(client_exchange);
730 Stream * output_stream = Stream_new(0);
731 instrument_js(request_uri, input_stream, output_stream);
732
733 /* send the headers to the client */
734 for (const HTTPHeader * h = HTTPExchange_get_response_headers(server_exchange); h != NULL; h = h->next) {
735 if (is_hop_by_hop_header(h->name) || strcasecmp(h->name, HTTP_CONTENT_LENGTH) == 0) {
736 continue;
737 }
738 HTTPExchange_add_response_header(client_exchange, h->name, h->value);
739 }
740 add_via_header(HTTPExchange_get_response_message(client_exchange), HTTPExchange_get_response_http_version(server_exchange));
741 HTTPExchange_set_response_content_length(client_exchange, output_stream->length);
742
743 /* send the instrumented code to the client */
744 if (HTTPExchange_write_response(client_exchange, output_stream->data, output_stream->length) != 0) {
745 HTTPServer_log_err("Warning: error writing to client\n");
746 }
747
748 /* input_stream goes on the cache */
749 /*
750 Stream_delete(input_stream);
751 */
752 Stream_delete(output_stream);
753 add_cached_source(request_uri, input_stream);
754 }
755 else {
756 /* does not need instrumentation */
757
758 /* send the headers to the client */
759 for (const HTTPHeader * h = HTTPExchange_get_response_headers(server_exchange); h != NULL; h = h->next) {
760 if (strcasecmp(h->name, HTTP_TRAILER) == 0 || strcasecmp(h->name, HTTP_TRANSFER_ENCODING) == 0) {
761 /* do nothing: we want to keep this header */
762 }
763 else if (is_hop_by_hop_header(h->name)) {
764 continue;
765 }
766 HTTPExchange_add_response_header(client_exchange, h->name, h->value);
767 }
768 add_via_header(HTTPExchange_get_response_message(client_exchange), HTTPExchange_get_response_http_version(server_exchange));
769
770 if (HTTPExchange_write_response_headers(client_exchange) != 0) {
771 HTTPServer_log_err("Warning: error writing to client\n");
772 goto done;
773 }
774
775 if (HTTPExchange_response_has_body(server_exchange)) {
776 /* read the body from the server and send it to the client */
777 HTTPMessage * client_response = HTTPExchange_get_response_message(client_exchange);
778 HTTPMessage * server_response = HTTPExchange_get_response_message(server_exchange);
779 if (copy_http_message_body(server_response, client_response) != 0) {
780 HTTPServer_log_err("Warning: error copying response body from server to client\n");
781 goto done;
782 }
783 }
784 }
785
786 done:
787 if (server_exchange != NULL) {
788 HTTPExchange_delete(server_exchange);
789 }
790 if (server_connection != NULL) {
791 if (HTTPConnection_delete(server_connection) != 0) {
792 HTTPServer_log_err("Warning: error closing connection to server\n");
793 }
794 }
795 }
796
797 static void handle_local_request(HTTPExchange * exchange) {
798 /* add the `Server' response-header (RFC 2616 14.38, 3.8) */
799 HTTPExchange_add_response_header(exchange, HTTP_SERVER, "jscoverage-server/" VERSION);
800
801 char * filesystem_path = NULL;
802
803 const char * abs_path = HTTPExchange_get_abs_path(exchange);
804 assert(*abs_path != '\0');
805
806 if (str_starts_with(abs_path, "/jscoverage")) {
807 handle_jscoverage_request(exchange);
808 goto done;
809 }
810
811 if (strstr(abs_path, "..") != NULL) {
812 send_response(exchange, 403, "Forbidden\n");
813 goto done;
814 }
815
816 filesystem_path = make_path(document_root, abs_path + 1);
817
818 struct stat buf;
819 if (stat(filesystem_path, &buf) == -1) {
820 send_response(exchange, 404, "Not found\n");
821 goto done;
822 }
823
824 if (S_ISDIR(buf.st_mode)) {
825 if (filesystem_path[strlen(filesystem_path) - 1] != '/') {
826 const char * request_uri = HTTPExchange_get_request_uri(exchange);
827 char * uri = xmalloc(strlen(request_uri) + 2);
828 strcpy(uri, request_uri);
829 strcat(uri, "/");
830 HTTPExchange_add_response_header(exchange, "Location", uri);
831 free(uri);
832 send_response(exchange, 301, "Moved permanently\n");
833 goto done;
834 }
835
836 DIR * d = opendir(filesystem_path);
837 if (d == NULL) {
838 send_response(exchange, 404, "Not found\n");
839 goto done;
840 }
841
842 struct dirent * entry;
843 while ((entry = readdir(d)) != NULL) {
844 char * href = encode_uri_component(entry->d_name);
845 char * html_href = encode_html(href);
846 char * link = encode_html(entry->d_name);
847 char * directory_entry;
848 xasprintf(&directory_entry, "<a href=\"%s\">%s</a><br>\n", html_href, link);
849 if (HTTPExchange_write_response(exchange, directory_entry, strlen(directory_entry)) != 0) {
850 HTTPServer_log_err("Warning: error writing to client\n");
851 }
852 free(directory_entry);
853 free(href);
854 free(html_href);
855 free(link);
856 }
857 closedir(d);
858 }
859 else if (S_ISREG(buf.st_mode)) {
860 FILE * f = fopen(filesystem_path, "r");
861 if (f == NULL) {
862 send_response(exchange, 404, "Not found\n");
863 goto done;
864 }
865
866 const char * content_type = get_content_type(filesystem_path);
867 HTTPExchange_set_response_header(exchange, HTTP_CONTENT_TYPE, content_type);
868 const char * request_uri = HTTPExchange_get_request_uri(exchange);
869 if (strcmp(content_type, "text/javascript") == 0 && ! is_no_instrument(request_uri)) {
870 Stream * input_stream = Stream_new(0);
871 Stream * output_stream = Stream_new(0);
872
873 Stream_write_file_contents(input_stream, f);
874
875 instrument_js(request_uri, input_stream, output_stream);
876
877 if (HTTPExchange_write_response(exchange, output_stream->data, output_stream->length) != 0) {
878 HTTPServer_log_err("Warning: error writing to client\n");
879 }
880
881 Stream_delete(input_stream);
882 Stream_delete(output_stream);
883 }
884 else {
885 char buffer[8192];
886 size_t bytes_read;
887 while ((bytes_read = fread(buffer, 1, 8192, f)) > 0) {
888 if (HTTPExchange_write_response(exchange, buffer, bytes_read) != 0) {
889 HTTPServer_log_err("Warning: error writing to client\n");
890 }
891 }
892 }
893 fclose(f);
894 }
895 else {
896 send_response(exchange, 404, "Not found\n");
897 goto done;
898 }
899
900 done:
901 free(filesystem_path);
902 }
903
904 static void handler(HTTPExchange * exchange) {
905 if (verbose) {
906 HTTPServer_log_out("%s", HTTPExchange_get_request_line(exchange));
907 }
908
909 if (proxy) {
910 handle_proxy_request(exchange);
911 }
912 else {
913 handle_local_request(exchange);
914 }
915 }
916
917 int main(int argc, char ** argv) {
918 program = "jscoverage-server";
919
920 const char * ip_address = "127.0.0.1";
921 const char * port = "8080";
922 int shutdown = 0;
923
924 no_instrument = xnew(const char *, argc - 1);
925
926 for (int i = 1; i < argc; i++) {
927 if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
928 copy_resource_to_stream("jscoverage-server-help.txt", stdout);
929 exit(EXIT_SUCCESS);
930 }
931 else if (strcmp(argv[i], "-V") == 0 || strcmp(argv[i], "--version") == 0) {
932 printf("jscoverage-server %s\n", VERSION);
933 exit(EXIT_SUCCESS);
934 }
935 else if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--verbose") == 0) {
936 verbose = 1;
937 }
938
939 else if (strcmp(argv[i], "--report-dir") == 0) {
940 i++;
941 if (i == argc) {
942 fatal("--report-dir: option requires an argument");
943 }
944 report_directory = argv[i];
945 }
946 else if (strncmp(argv[i], "--report-dir=", 13) == 0) {
947 report_directory = argv[i] + 13;
948 }
949
950 else if (strcmp(argv[i], "--document-root") == 0) {
951 i++;
952 if (i == argc) {
953 fatal("--document-root: option requires an argument");
954 }
955 document_root = argv[i];
956 }
957 else if (strncmp(argv[i], "--document-root=", 16) == 0) {
958 document_root = argv[i] + 16;
959 }
960
961 else if (strcmp(argv[i], "--ip-address") == 0) {
962 i++;
963 if (i == argc) {
964 fatal("--ip-address: option requires an argument");
965 }
966 ip_address = argv[i];
967 }
968 else if (strncmp(argv[i], "--ip-address=", 13) == 0) {
969 ip_address = argv[i] + 13;
970 }
971
972 else if (strcmp(argv[i], "--no-instrument") == 0) {
973 i++;
974 if (i == argc) {
975 fatal("--no-instrument: option requires an argument");
976 }
977 no_instrument[num_no_instrument] = argv[i];
978 num_no_instrument++;
979 }
980 else if (strncmp(argv[i], "--no-instrument=", 16) == 0) {
981 no_instrument[num_no_instrument] = argv[i] + 16;
982 num_no_instrument++;
983 }
984
985 else if (strcmp(argv[i], "--port") == 0) {
986 i++;
987 if (i == argc) {
988 fatal("--port: option requires an argument");
989 }
990 port = argv[i];
991 }
992 else if (strncmp(argv[i], "--port=", 7) == 0) {
993 port = argv[i] + 7;
994 }
995
996 else if (strcmp(argv[i], "--proxy") == 0) {
997 proxy = 1;
998 }
999
1000 else if (strcmp(argv[i], "--shutdown") == 0) {
1001 shutdown = 1;
1002 }
1003
1004 else if (strncmp(argv[i], "-", 1) == 0) {
1005 fatal("unrecognized option `%s'", argv[i]);
1006 }
1007 else {
1008 fatal("too many arguments");
1009 }
1010 }
1011
1012 /* check the port */
1013 char * end;
1014 unsigned long numeric_port = strtoul(port, &end, 10);
1015 if (*end != '\0') {
1016 fatal("--port: option must be an integer");
1017 }
1018 if (numeric_port > UINT16_MAX) {
1019 fatal("--port: option must be 16 bits");
1020 }
1021
1022 /* is this a shutdown? */
1023 if (shutdown) {
1024 /* INADDR_LOOPBACK */
1025 HTTPConnection * connection = HTTPConnection_new_client("127.0.0.1", numeric_port);
1026 if (connection == NULL) {
1027 fatal("could not connect to server");
1028 }
1029 HTTPExchange * exchange = HTTPExchange_new(connection);
1030 HTTPExchange_set_method(exchange, "POST");
1031 HTTPExchange_set_request_uri(exchange, "/jscoverage-shutdown");
1032 if (HTTPExchange_write_request_headers(exchange) != 0) {
1033 fatal("could not write request headers to server");
1034 }
1035 if (HTTPExchange_read_response_headers(exchange) != 0) {
1036 fatal("could not read response headers from server");
1037 }
1038 Stream * stream = Stream_new(0);
1039 if (HTTPExchange_read_entire_response_entity_body(exchange, stream) != 0) {
1040 fatal("could not read response body from server");
1041 }
1042 fwrite(stream->data, 1, stream->length, stdout);
1043 Stream_delete(stream);
1044 HTTPExchange_delete(exchange);
1045 if (HTTPConnection_delete(connection) != 0) {
1046 fatal("could not close connection with server");
1047 }
1048 exit(EXIT_SUCCESS);
1049 }
1050
1051 jscoverage_init();
1052
1053 /* handle broken pipe */
1054 signal(SIGPIPE, SIG_IGN);
1055
1056 if (verbose) {
1057 printf("Starting HTTP server on %s:%lu\n", ip_address, numeric_port);
1058 }
1059 HTTPServer_run(ip_address, (uint16_t) numeric_port, handler);
1060 if (verbose) {
1061 printf("Stopping HTTP server\n");
1062 }
1063
1064 jscoverage_cleanup();
1065
1066 free(no_instrument);
1067
1068 pthread_mutex_lock(&source_cache_mutex);
1069 while (source_cache != NULL) {
1070 SourceCache * p = source_cache;
1071 source_cache = source_cache->next;
1072 free(p->url);
1073 Stream_delete(p->source);
1074 free(p);
1075 }
1076 pthread_mutex_unlock(&source_cache_mutex);
1077
1078 return 0;
1079 }

  ViewVC Help
Powered by ViewVC 1.1.24