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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 125 - (hide annotations)
Mon Jun 2 17:52:38 2008 UTC (11 years, 4 months ago) by siliconforks
Original Path: trunk/jscoverage-server.c
File MIME type: text/plain
File size: 32757 byte(s)
Fixes for compiling with MinGW.

1 siliconforks 116 /*
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 siliconforks 125 #ifdef HAVE_PTHREAD_H
29 siliconforks 116 #include <pthread.h>
30 siliconforks 125 #endif
31 siliconforks 116
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 siliconforks 125 #ifdef __MINGW32__
70     CRITICAL_SECTION javascript_mutex;
71     CRITICAL_SECTION source_cache_mutex;
72     #define LOCK EnterCriticalSection
73     #define UNLOCK LeaveCriticalSection
74     #else
75 siliconforks 116 pthread_mutex_t javascript_mutex = PTHREAD_MUTEX_INITIALIZER;
76     pthread_mutex_t source_cache_mutex = PTHREAD_MUTEX_INITIALIZER;
77 siliconforks 125 #define LOCK pthread_mutex_lock
78     #define UNLOCK pthread_mutex_unlock
79     #endif
80 siliconforks 116
81     static Stream * find_cached_source(const char * url) {
82     Stream * result = NULL;
83 siliconforks 125 LOCK(&source_cache_mutex);
84 siliconforks 116 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 siliconforks 125 UNLOCK(&source_cache_mutex);
91 siliconforks 116 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 siliconforks 125 LOCK(&source_cache_mutex);
99 siliconforks 116 new_source_cache->next = source_cache;
100     source_cache = new_source_cache;
101 siliconforks 125 UNLOCK(&source_cache_mutex);
102 siliconforks 116 }
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 siliconforks 119 static int merge(Coverage * coverage, FILE * f) __attribute__((warn_unused_result));
353 siliconforks 116
354 siliconforks 119 static int merge(Coverage * coverage, FILE * f) {
355     Stream * stream = Stream_new(0);
356     Stream_write_file_contents(stream, f);
357 siliconforks 116
358 siliconforks 125 LOCK(&javascript_mutex);
359 siliconforks 119 int result = jscoverage_parse_json(coverage, stream->data, stream->length);
360 siliconforks 125 UNLOCK(&javascript_mutex);
361 siliconforks 116
362 siliconforks 119 Stream_delete(stream);
363 siliconforks 116 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 siliconforks 125 FILE * source_file = fopen(source_path, "rb");
450 siliconforks 116 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 siliconforks 125 LOCK(&javascript_mutex);
517 siliconforks 116 int result = jscoverage_parse_json(coverage, json->data, json->length);
518 siliconforks 125 UNLOCK(&javascript_mutex);
519 siliconforks 116 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 siliconforks 119 FILE * f = fopen(path, "r");
530     if (f != NULL) {
531 siliconforks 116 /* it exists: merge */
532 siliconforks 119 result = merge(coverage, f);
533     if (fclose(f) == EOF) {
534     result = 1;
535     }
536 siliconforks 116 if (result != 0) {
537     free(path);
538     Coverage_delete(coverage);
539     send_response(exchange, 500, "Could not merge with existing coverage data\n");
540     return;
541     }
542     }
543    
544     result = write_json(coverage, path);
545     free(path);
546     Coverage_delete(coverage);
547     if (result != 0) {
548     send_response(exchange, 500, "Could not write coverage data\n");
549     return;
550     }
551    
552     /* copy other files */
553     jscoverage_copy_resources(report_directory);
554     path = make_path(report_directory, "jscoverage.js");
555 siliconforks 119 f = fopen(path, "ab");
556 siliconforks 116 free(path);
557     if (f == NULL) {
558     send_response(exchange, 500, "Could not write to file: jscoverage.js\n");
559     return;
560     }
561     fputs("jscoverage_isReport = true;\r\n", f);
562     if (fclose(f) == EOF) {
563     send_response(exchange, 500, "Could not write to file: jscoverage.js\n");
564     return;
565     }
566    
567     send_response(exchange, 200, "Coverage data stored\n");
568     }
569     else if (str_starts_with(abs_path, "/jscoverage-shutdown")) {
570     if (strcmp(HTTPExchange_get_method(exchange), "POST") != 0) {
571     HTTPExchange_set_response_header(exchange, HTTP_ALLOW, "POST");
572     send_response(exchange, 405, "Method not allowed\n");
573     return;
574     }
575    
576     /* allow only from localhost */
577     struct sockaddr_in client;
578     if (HTTPExchange_get_peer(exchange, &client) != 0) {
579     send_response(exchange, 500, "Cannot get client address\n");
580     return;
581     }
582     if (client.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) {
583     send_response(exchange, 403, "This operation can be performed only by localhost\n");
584     return;
585     }
586    
587     send_response(exchange, 200, "The server will now shut down\n");
588     HTTPServer_shutdown();
589     }
590     else {
591     const char * path = abs_path + 1;
592     const struct Resource * resource = get_resource(path);
593     if (resource == NULL) {
594     send_response(exchange, 404, "Not found\n");
595     return;
596     }
597     HTTPExchange_set_response_header(exchange, HTTP_CONTENT_TYPE, get_content_type(path));
598     if (HTTPExchange_write_response(exchange, resource->data, resource->length) != 0) {
599     HTTPServer_log_err("Warning: error writing to client\n");
600     return;
601     }
602     if (strcmp(abs_path, "/jscoverage.js") == 0) {
603     const char * s = "jscoverage_isServer = true;\r\n";
604     if (HTTPExchange_write_response(exchange, s, strlen(s)) != 0) {
605     HTTPServer_log_err("Warning: error writing to client\n");
606     }
607     }
608     }
609     }
610    
611     static void instrument_js(const char * id, Stream * input_stream, Stream * output_stream) {
612 siliconforks 125 LOCK(&javascript_mutex);
613 siliconforks 116 jscoverage_instrument_js(id, input_stream, output_stream);
614 siliconforks 125 UNLOCK(&javascript_mutex);
615 siliconforks 116
616     const struct Resource * resource = get_resource("report.js");
617     Stream_write(output_stream, resource->data, resource->length);
618     }
619    
620     static bool is_hop_by_hop_header(const char * h) {
621     /* hop-by-hop headers (RFC 2616 13.5.1) */
622     return strcasecmp(h, HTTP_CONNECTION) == 0 ||
623     strcasecmp(h, "Keep-Alive") == 0 ||
624     strcasecmp(h, HTTP_PROXY_AUTHENTICATE) == 0 ||
625     strcasecmp(h, HTTP_PROXY_AUTHORIZATION) == 0 ||
626     strcasecmp(h, HTTP_TE) == 0 ||
627     strcasecmp(h, HTTP_TRAILER) == 0 ||
628     strcasecmp(h, HTTP_TRANSFER_ENCODING) == 0 ||
629     strcasecmp(h, HTTP_UPGRADE) == 0;
630     }
631    
632     static void add_via_header(HTTPMessage * message, const char * version) {
633     char * value;
634     xasprintf(&value, "%s jscoverage-server", version);
635     HTTPMessage_add_header(message, HTTP_VIA, value);
636     free(value);
637     }
638    
639     static int copy_http_message_body(HTTPMessage * from, HTTPMessage * to) __attribute__((warn_unused_result));
640    
641     static int copy_http_message_body(HTTPMessage * from, HTTPMessage * to) {
642     uint8_t * buffer[8192];
643     for (;;) {
644     size_t bytes_read;
645     int result = HTTPMessage_read_message_body(from, buffer, 8192, &bytes_read);
646     if (result != 0) {
647     return result;
648     }
649     if (bytes_read == 0) {
650     return 0;
651     }
652     result = HTTPMessage_write(to, buffer, bytes_read);
653     if (result != 0) {
654     return result;
655     }
656     }
657     }
658    
659     static void handle_proxy_request(HTTPExchange * client_exchange) {
660     HTTPConnection * server_connection = NULL;
661     HTTPExchange * server_exchange = NULL;
662    
663     const char * abs_path = HTTPExchange_get_abs_path(client_exchange);
664     if (str_starts_with(abs_path, "/jscoverage")) {
665     handle_jscoverage_request(client_exchange);
666     return;
667     }
668    
669     const char * host = HTTPExchange_get_host(client_exchange);
670     uint16_t port = HTTPExchange_get_port(client_exchange);
671    
672     /* create a new connection */
673     server_connection = HTTPConnection_new_client(host, port);
674     if (server_connection == NULL) {
675     send_response(client_exchange, 504, "Could not connect to server\n");
676     goto done;
677     }
678    
679     /* create a new exchange */
680     server_exchange = HTTPExchange_new(server_connection);
681     HTTPExchange_set_method(server_exchange, HTTPExchange_get_method(client_exchange));
682     HTTPExchange_set_request_uri(server_exchange, HTTPExchange_get_request_uri(client_exchange));
683     for (const HTTPHeader * h = HTTPExchange_get_request_headers(client_exchange); h != NULL; h = h->next) {
684     if (strcasecmp(h->name, HTTP_TRAILER) == 0 || strcasecmp(h->name, HTTP_TRANSFER_ENCODING) == 0) {
685     /* do nothing: we want to keep this header */
686     }
687     else if (is_hop_by_hop_header(h->name) ||
688     strcasecmp(h->name, HTTP_ACCEPT_ENCODING) == 0 ||
689     strcasecmp(h->name, HTTP_RANGE) == 0) {
690     continue;
691     }
692     HTTPExchange_add_request_header(server_exchange, h->name, h->value);
693     }
694     add_via_header(HTTPExchange_get_request_message(server_exchange), HTTPExchange_get_request_http_version(client_exchange));
695    
696     /* send the request */
697     if (HTTPExchange_write_request_headers(server_exchange) != 0) {
698     send_response(client_exchange, 502, "Could not write to server\n");
699     goto done;
700     }
701    
702     /* handle POST or PUT */
703     if (HTTPExchange_request_has_body(client_exchange)) {
704     HTTPMessage * client_request = HTTPExchange_get_request_message(client_exchange);
705     HTTPMessage * server_request = HTTPExchange_get_request_message(server_exchange);
706     if (copy_http_message_body(client_request, server_request) != 0) {
707     send_response(client_exchange, 400, "Error copying request body from client to server\n");
708     goto done;
709     }
710     }
711    
712     if (HTTPExchange_flush_request(server_exchange) != 0) {
713     send_response(client_exchange, 502, "Could not write to server\n");
714     goto done;
715     }
716    
717     /* receive the response */
718     if (HTTPExchange_read_response_headers(server_exchange) != 0) {
719     send_response(client_exchange, 502, "Could not read headers from server\n");
720     goto done;
721     }
722    
723     HTTPExchange_set_status_code(client_exchange, HTTPExchange_get_status_code(server_exchange));
724    
725     if (HTTPExchange_response_has_body(server_exchange) && should_instrument_request(server_exchange)) {
726     /* needs instrumentation */
727     Stream * input_stream = Stream_new(0);
728     if (HTTPExchange_read_entire_response_entity_body(server_exchange, input_stream) != 0) {
729     Stream_delete(input_stream);
730     send_response(client_exchange, 502, "Could not read body from server\n");
731     goto done;
732     }
733    
734     const char * request_uri = HTTPExchange_get_request_uri(client_exchange);
735     Stream * output_stream = Stream_new(0);
736     instrument_js(request_uri, input_stream, output_stream);
737    
738     /* send the headers to the client */
739     for (const HTTPHeader * h = HTTPExchange_get_response_headers(server_exchange); h != NULL; h = h->next) {
740     if (is_hop_by_hop_header(h->name) || strcasecmp(h->name, HTTP_CONTENT_LENGTH) == 0) {
741     continue;
742     }
743     HTTPExchange_add_response_header(client_exchange, h->name, h->value);
744     }
745     add_via_header(HTTPExchange_get_response_message(client_exchange), HTTPExchange_get_response_http_version(server_exchange));
746     HTTPExchange_set_response_content_length(client_exchange, output_stream->length);
747    
748     /* send the instrumented code to the client */
749     if (HTTPExchange_write_response(client_exchange, output_stream->data, output_stream->length) != 0) {
750     HTTPServer_log_err("Warning: error writing to client\n");
751     }
752    
753     /* input_stream goes on the cache */
754     /*
755     Stream_delete(input_stream);
756     */
757     Stream_delete(output_stream);
758     add_cached_source(request_uri, input_stream);
759     }
760     else {
761     /* does not need instrumentation */
762    
763     /* send the headers to the client */
764     for (const HTTPHeader * h = HTTPExchange_get_response_headers(server_exchange); h != NULL; h = h->next) {
765     if (strcasecmp(h->name, HTTP_TRAILER) == 0 || strcasecmp(h->name, HTTP_TRANSFER_ENCODING) == 0) {
766     /* do nothing: we want to keep this header */
767     }
768     else if (is_hop_by_hop_header(h->name)) {
769     continue;
770     }
771     HTTPExchange_add_response_header(client_exchange, h->name, h->value);
772     }
773     add_via_header(HTTPExchange_get_response_message(client_exchange), HTTPExchange_get_response_http_version(server_exchange));
774    
775     if (HTTPExchange_write_response_headers(client_exchange) != 0) {
776     HTTPServer_log_err("Warning: error writing to client\n");
777     goto done;
778     }
779    
780     if (HTTPExchange_response_has_body(server_exchange)) {
781     /* read the body from the server and send it to the client */
782     HTTPMessage * client_response = HTTPExchange_get_response_message(client_exchange);
783     HTTPMessage * server_response = HTTPExchange_get_response_message(server_exchange);
784     if (copy_http_message_body(server_response, client_response) != 0) {
785     HTTPServer_log_err("Warning: error copying response body from server to client\n");
786     goto done;
787     }
788     }
789     }
790    
791     done:
792     if (server_exchange != NULL) {
793     HTTPExchange_delete(server_exchange);
794     }
795     if (server_connection != NULL) {
796     if (HTTPConnection_delete(server_connection) != 0) {
797     HTTPServer_log_err("Warning: error closing connection to server\n");
798     }
799     }
800     }
801    
802     static void handle_local_request(HTTPExchange * exchange) {
803     /* add the `Server' response-header (RFC 2616 14.38, 3.8) */
804     HTTPExchange_add_response_header(exchange, HTTP_SERVER, "jscoverage-server/" VERSION);
805    
806     char * filesystem_path = NULL;
807    
808     const char * abs_path = HTTPExchange_get_abs_path(exchange);
809     assert(*abs_path != '\0');
810    
811     if (str_starts_with(abs_path, "/jscoverage")) {
812     handle_jscoverage_request(exchange);
813     goto done;
814     }
815    
816     if (strstr(abs_path, "..") != NULL) {
817     send_response(exchange, 403, "Forbidden\n");
818     goto done;
819     }
820    
821     filesystem_path = make_path(document_root, abs_path + 1);
822 siliconforks 125 size_t filesystem_path_length = strlen(filesystem_path);
823     if (filesystem_path_length > 0 && filesystem_path[filesystem_path_length - 1] == '/') {
824     /* stat on Windows doesn't work with trailing slash */
825     filesystem_path[filesystem_path_length - 1] = '\0';
826     }
827 siliconforks 116
828     struct stat buf;
829     if (stat(filesystem_path, &buf) == -1) {
830     send_response(exchange, 404, "Not found\n");
831     goto done;
832     }
833    
834     if (S_ISDIR(buf.st_mode)) {
835 siliconforks 125 if (abs_path[strlen(abs_path) - 1] != '/') {
836 siliconforks 116 const char * request_uri = HTTPExchange_get_request_uri(exchange);
837     char * uri = xmalloc(strlen(request_uri) + 2);
838     strcpy(uri, request_uri);
839     strcat(uri, "/");
840     HTTPExchange_add_response_header(exchange, "Location", uri);
841     free(uri);
842     send_response(exchange, 301, "Moved permanently\n");
843     goto done;
844     }
845    
846     DIR * d = opendir(filesystem_path);
847     if (d == NULL) {
848     send_response(exchange, 404, "Not found\n");
849     goto done;
850     }
851    
852     struct dirent * entry;
853     while ((entry = readdir(d)) != NULL) {
854     char * href = encode_uri_component(entry->d_name);
855     char * html_href = encode_html(href);
856     char * link = encode_html(entry->d_name);
857     char * directory_entry;
858     xasprintf(&directory_entry, "<a href=\"%s\">%s</a><br>\n", html_href, link);
859     if (HTTPExchange_write_response(exchange, directory_entry, strlen(directory_entry)) != 0) {
860     HTTPServer_log_err("Warning: error writing to client\n");
861     }
862     free(directory_entry);
863     free(href);
864     free(html_href);
865     free(link);
866     }
867     closedir(d);
868     }
869     else if (S_ISREG(buf.st_mode)) {
870 siliconforks 125 FILE * f = fopen(filesystem_path, "rb");
871 siliconforks 116 if (f == NULL) {
872     send_response(exchange, 404, "Not found\n");
873     goto done;
874     }
875    
876     const char * content_type = get_content_type(filesystem_path);
877     HTTPExchange_set_response_header(exchange, HTTP_CONTENT_TYPE, content_type);
878     const char * request_uri = HTTPExchange_get_request_uri(exchange);
879     if (strcmp(content_type, "text/javascript") == 0 && ! is_no_instrument(request_uri)) {
880     Stream * input_stream = Stream_new(0);
881     Stream * output_stream = Stream_new(0);
882    
883     Stream_write_file_contents(input_stream, f);
884    
885     instrument_js(request_uri, input_stream, output_stream);
886    
887     if (HTTPExchange_write_response(exchange, output_stream->data, output_stream->length) != 0) {
888     HTTPServer_log_err("Warning: error writing to client\n");
889     }
890    
891     Stream_delete(input_stream);
892     Stream_delete(output_stream);
893     }
894     else {
895     char buffer[8192];
896     size_t bytes_read;
897     while ((bytes_read = fread(buffer, 1, 8192, f)) > 0) {
898     if (HTTPExchange_write_response(exchange, buffer, bytes_read) != 0) {
899     HTTPServer_log_err("Warning: error writing to client\n");
900     }
901     }
902     }
903     fclose(f);
904     }
905     else {
906     send_response(exchange, 404, "Not found\n");
907     goto done;
908     }
909    
910     done:
911     free(filesystem_path);
912     }
913    
914     static void handler(HTTPExchange * exchange) {
915     if (verbose) {
916     HTTPServer_log_out("%s", HTTPExchange_get_request_line(exchange));
917     }
918    
919     if (proxy) {
920     handle_proxy_request(exchange);
921     }
922     else {
923     handle_local_request(exchange);
924     }
925     }
926    
927     int main(int argc, char ** argv) {
928     program = "jscoverage-server";
929    
930     const char * ip_address = "127.0.0.1";
931     const char * port = "8080";
932     int shutdown = 0;
933    
934     no_instrument = xnew(const char *, argc - 1);
935    
936     for (int i = 1; i < argc; i++) {
937     if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
938     copy_resource_to_stream("jscoverage-server-help.txt", stdout);
939     exit(EXIT_SUCCESS);
940     }
941     else if (strcmp(argv[i], "-V") == 0 || strcmp(argv[i], "--version") == 0) {
942     printf("jscoverage-server %s\n", VERSION);
943     exit(EXIT_SUCCESS);
944     }
945     else if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--verbose") == 0) {
946     verbose = 1;
947     }
948    
949     else if (strcmp(argv[i], "--report-dir") == 0) {
950     i++;
951     if (i == argc) {
952     fatal("--report-dir: option requires an argument");
953     }
954     report_directory = argv[i];
955     }
956     else if (strncmp(argv[i], "--report-dir=", 13) == 0) {
957     report_directory = argv[i] + 13;
958     }
959    
960     else if (strcmp(argv[i], "--document-root") == 0) {
961     i++;
962     if (i == argc) {
963     fatal("--document-root: option requires an argument");
964     }
965     document_root = argv[i];
966     }
967     else if (strncmp(argv[i], "--document-root=", 16) == 0) {
968     document_root = argv[i] + 16;
969     }
970    
971     else if (strcmp(argv[i], "--ip-address") == 0) {
972     i++;
973     if (i == argc) {
974     fatal("--ip-address: option requires an argument");
975     }
976     ip_address = argv[i];
977     }
978     else if (strncmp(argv[i], "--ip-address=", 13) == 0) {
979     ip_address = argv[i] + 13;
980     }
981    
982     else if (strcmp(argv[i], "--no-instrument") == 0) {
983     i++;
984     if (i == argc) {
985     fatal("--no-instrument: option requires an argument");
986     }
987     no_instrument[num_no_instrument] = argv[i];
988     num_no_instrument++;
989     }
990     else if (strncmp(argv[i], "--no-instrument=", 16) == 0) {
991     no_instrument[num_no_instrument] = argv[i] + 16;
992     num_no_instrument++;
993     }
994    
995     else if (strcmp(argv[i], "--port") == 0) {
996     i++;
997     if (i == argc) {
998     fatal("--port: option requires an argument");
999     }
1000     port = argv[i];
1001     }
1002     else if (strncmp(argv[i], "--port=", 7) == 0) {
1003     port = argv[i] + 7;
1004     }
1005    
1006     else if (strcmp(argv[i], "--proxy") == 0) {
1007     proxy = 1;
1008     }
1009    
1010     else if (strcmp(argv[i], "--shutdown") == 0) {
1011     shutdown = 1;
1012     }
1013    
1014     else if (strncmp(argv[i], "-", 1) == 0) {
1015     fatal("unrecognized option `%s'", argv[i]);
1016     }
1017     else {
1018     fatal("too many arguments");
1019     }
1020     }
1021    
1022     /* check the port */
1023     char * end;
1024     unsigned long numeric_port = strtoul(port, &end, 10);
1025     if (*end != '\0') {
1026     fatal("--port: option must be an integer");
1027     }
1028     if (numeric_port > UINT16_MAX) {
1029     fatal("--port: option must be 16 bits");
1030     }
1031    
1032     /* is this a shutdown? */
1033     if (shutdown) {
1034 siliconforks 125 #ifdef __MINGW32__
1035     WSADATA data;
1036     if (WSAStartup(MAKEWORD(1, 1), &data) != 0) {
1037     fatal("Could not start Winsock");
1038     }
1039     #endif
1040    
1041 siliconforks 116 /* INADDR_LOOPBACK */
1042     HTTPConnection * connection = HTTPConnection_new_client("127.0.0.1", numeric_port);
1043     if (connection == NULL) {
1044     fatal("could not connect to server");
1045     }
1046     HTTPExchange * exchange = HTTPExchange_new(connection);
1047     HTTPExchange_set_method(exchange, "POST");
1048     HTTPExchange_set_request_uri(exchange, "/jscoverage-shutdown");
1049     if (HTTPExchange_write_request_headers(exchange) != 0) {
1050     fatal("could not write request headers to server");
1051     }
1052     if (HTTPExchange_read_response_headers(exchange) != 0) {
1053     fatal("could not read response headers from server");
1054     }
1055     Stream * stream = Stream_new(0);
1056     if (HTTPExchange_read_entire_response_entity_body(exchange, stream) != 0) {
1057     fatal("could not read response body from server");
1058     }
1059     fwrite(stream->data, 1, stream->length, stdout);
1060     Stream_delete(stream);
1061     HTTPExchange_delete(exchange);
1062     if (HTTPConnection_delete(connection) != 0) {
1063     fatal("could not close connection with server");
1064     }
1065     exit(EXIT_SUCCESS);
1066     }
1067    
1068     jscoverage_init();
1069    
1070 siliconforks 125 #ifndef __MINGW32__
1071 siliconforks 116 /* handle broken pipe */
1072     signal(SIGPIPE, SIG_IGN);
1073 siliconforks 125 #endif
1074 siliconforks 116
1075 siliconforks 125 #ifdef __MINGW32__
1076     InitializeCriticalSection(&javascript_mutex);
1077     InitializeCriticalSection(&source_cache_mutex);
1078     #endif
1079    
1080 siliconforks 116 if (verbose) {
1081     printf("Starting HTTP server on %s:%lu\n", ip_address, numeric_port);
1082 siliconforks 125 fflush(stdout);
1083 siliconforks 116 }
1084     HTTPServer_run(ip_address, (uint16_t) numeric_port, handler);
1085     if (verbose) {
1086     printf("Stopping HTTP server\n");
1087 siliconforks 125 fflush(stdout);
1088 siliconforks 116 }
1089    
1090     jscoverage_cleanup();
1091    
1092     free(no_instrument);
1093    
1094 siliconforks 125 LOCK(&source_cache_mutex);
1095 siliconforks 116 while (source_cache != NULL) {
1096     SourceCache * p = source_cache;
1097     source_cache = source_cache->next;
1098     free(p->url);
1099     Stream_delete(p->source);
1100     free(p);
1101     }
1102 siliconforks 125 UNLOCK(&source_cache_mutex);
1103 siliconforks 116
1104     return 0;
1105     }

  ViewVC Help
Powered by ViewVC 1.1.24