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

Annotation of /trunk/jscoverage-server.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 134 - (hide annotations)
Thu Jun 19 05:54:02 2008 UTC (11 years, 3 months ago) by siliconforks
File MIME type: text/plain
File size: 32920 byte(s)
Typo fixes.

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 133
530     /* check if the JSON file exists */
531     struct stat buf;
532     if (stat(path, &buf) == 0) {
533 siliconforks 116 /* it exists: merge */
534 siliconforks 133 FILE * f = fopen(path, "r");
535     if (f == NULL) {
536 siliconforks 119 result = 1;
537     }
538 siliconforks 133 else {
539     result = merge(coverage, f);
540     if (fclose(f) == EOF) {
541     result = 1;
542     }
543     }
544 siliconforks 116 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 siliconforks 133 FILE * f = fopen(path, "ab");
564 siliconforks 116 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 siliconforks 125 LOCK(&javascript_mutex);
621 siliconforks 116 jscoverage_instrument_js(id, input_stream, output_stream);
622 siliconforks 125 UNLOCK(&javascript_mutex);
623 siliconforks 116
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 siliconforks 125 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 siliconforks 116
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 siliconforks 125 if (abs_path[strlen(abs_path) - 1] != '/') {
844 siliconforks 116 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 siliconforks 125 FILE * f = fopen(filesystem_path, "rb");
879 siliconforks 116 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     const char * request_uri = HTTPExchange_get_request_uri(exchange);
887     if (strcmp(content_type, "text/javascript") == 0 && ! is_no_instrument(request_uri)) {
888     Stream * input_stream = Stream_new(0);
889     Stream * output_stream = Stream_new(0);
890    
891     Stream_write_file_contents(input_stream, f);
892    
893     instrument_js(request_uri, input_stream, output_stream);
894    
895     if (HTTPExchange_write_response(exchange, output_stream->data, output_stream->length) != 0) {
896     HTTPServer_log_err("Warning: error writing to client\n");
897     }
898    
899     Stream_delete(input_stream);
900     Stream_delete(output_stream);
901     }
902     else {
903     char buffer[8192];
904     size_t bytes_read;
905     while ((bytes_read = fread(buffer, 1, 8192, f)) > 0) {
906     if (HTTPExchange_write_response(exchange, buffer, bytes_read) != 0) {
907     HTTPServer_log_err("Warning: error writing to client\n");
908     }
909     }
910     }
911     fclose(f);
912     }
913     else {
914     send_response(exchange, 404, "Not found\n");
915     goto done;
916     }
917    
918     done:
919     free(filesystem_path);
920     }
921    
922     static void handler(HTTPExchange * exchange) {
923     if (verbose) {
924     HTTPServer_log_out("%s", HTTPExchange_get_request_line(exchange));
925     }
926    
927     if (proxy) {
928     handle_proxy_request(exchange);
929     }
930     else {
931     handle_local_request(exchange);
932     }
933     }
934    
935     int main(int argc, char ** argv) {
936     program = "jscoverage-server";
937    
938     const char * ip_address = "127.0.0.1";
939     const char * port = "8080";
940     int shutdown = 0;
941    
942     no_instrument = xnew(const char *, argc - 1);
943    
944     for (int i = 1; i < argc; i++) {
945     if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
946     copy_resource_to_stream("jscoverage-server-help.txt", stdout);
947     exit(EXIT_SUCCESS);
948     }
949     else if (strcmp(argv[i], "-V") == 0 || strcmp(argv[i], "--version") == 0) {
950     printf("jscoverage-server %s\n", VERSION);
951     exit(EXIT_SUCCESS);
952     }
953     else if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--verbose") == 0) {
954     verbose = 1;
955     }
956    
957     else if (strcmp(argv[i], "--report-dir") == 0) {
958     i++;
959     if (i == argc) {
960     fatal("--report-dir: option requires an argument");
961     }
962     report_directory = argv[i];
963     }
964     else if (strncmp(argv[i], "--report-dir=", 13) == 0) {
965     report_directory = argv[i] + 13;
966     }
967    
968     else if (strcmp(argv[i], "--document-root") == 0) {
969     i++;
970     if (i == argc) {
971     fatal("--document-root: option requires an argument");
972     }
973     document_root = argv[i];
974     }
975     else if (strncmp(argv[i], "--document-root=", 16) == 0) {
976     document_root = argv[i] + 16;
977     }
978    
979     else if (strcmp(argv[i], "--ip-address") == 0) {
980     i++;
981     if (i == argc) {
982     fatal("--ip-address: option requires an argument");
983     }
984     ip_address = argv[i];
985     }
986     else if (strncmp(argv[i], "--ip-address=", 13) == 0) {
987     ip_address = argv[i] + 13;
988     }
989    
990     else if (strcmp(argv[i], "--no-instrument") == 0) {
991     i++;
992     if (i == argc) {
993     fatal("--no-instrument: option requires an argument");
994     }
995     no_instrument[num_no_instrument] = argv[i];
996     num_no_instrument++;
997     }
998     else if (strncmp(argv[i], "--no-instrument=", 16) == 0) {
999     no_instrument[num_no_instrument] = argv[i] + 16;
1000     num_no_instrument++;
1001     }
1002    
1003     else if (strcmp(argv[i], "--port") == 0) {
1004     i++;
1005     if (i == argc) {
1006     fatal("--port: option requires an argument");
1007     }
1008     port = argv[i];
1009     }
1010     else if (strncmp(argv[i], "--port=", 7) == 0) {
1011     port = argv[i] + 7;
1012     }
1013    
1014     else if (strcmp(argv[i], "--proxy") == 0) {
1015     proxy = 1;
1016     }
1017    
1018     else if (strcmp(argv[i], "--shutdown") == 0) {
1019     shutdown = 1;
1020     }
1021    
1022     else if (strncmp(argv[i], "-", 1) == 0) {
1023     fatal("unrecognized option `%s'", argv[i]);
1024     }
1025     else {
1026     fatal("too many arguments");
1027     }
1028     }
1029    
1030     /* check the port */
1031     char * end;
1032     unsigned long numeric_port = strtoul(port, &end, 10);
1033     if (*end != '\0') {
1034     fatal("--port: option must be an integer");
1035     }
1036     if (numeric_port > UINT16_MAX) {
1037     fatal("--port: option must be 16 bits");
1038     }
1039    
1040     /* is this a shutdown? */
1041     if (shutdown) {
1042 siliconforks 125 #ifdef __MINGW32__
1043     WSADATA data;
1044     if (WSAStartup(MAKEWORD(1, 1), &data) != 0) {
1045 siliconforks 134 fatal("could not start Winsock");
1046 siliconforks 125 }
1047     #endif
1048    
1049 siliconforks 116 /* INADDR_LOOPBACK */
1050     HTTPConnection * connection = HTTPConnection_new_client("127.0.0.1", numeric_port);
1051     if (connection == NULL) {
1052     fatal("could not connect to server");
1053     }
1054     HTTPExchange * exchange = HTTPExchange_new(connection);
1055     HTTPExchange_set_method(exchange, "POST");
1056     HTTPExchange_set_request_uri(exchange, "/jscoverage-shutdown");
1057     if (HTTPExchange_write_request_headers(exchange) != 0) {
1058     fatal("could not write request headers to server");
1059     }
1060     if (HTTPExchange_read_response_headers(exchange) != 0) {
1061     fatal("could not read response headers from server");
1062     }
1063     Stream * stream = Stream_new(0);
1064     if (HTTPExchange_read_entire_response_entity_body(exchange, stream) != 0) {
1065     fatal("could not read response body from server");
1066     }
1067     fwrite(stream->data, 1, stream->length, stdout);
1068     Stream_delete(stream);
1069     HTTPExchange_delete(exchange);
1070     if (HTTPConnection_delete(connection) != 0) {
1071     fatal("could not close connection with server");
1072     }
1073     exit(EXIT_SUCCESS);
1074     }
1075    
1076     jscoverage_init();
1077    
1078 siliconforks 125 #ifndef __MINGW32__
1079 siliconforks 116 /* handle broken pipe */
1080     signal(SIGPIPE, SIG_IGN);
1081 siliconforks 125 #endif
1082 siliconforks 116
1083 siliconforks 125 #ifdef __MINGW32__
1084     InitializeCriticalSection(&javascript_mutex);
1085     InitializeCriticalSection(&source_cache_mutex);
1086     #endif
1087    
1088 siliconforks 116 if (verbose) {
1089     printf("Starting HTTP server on %s:%lu\n", ip_address, numeric_port);
1090 siliconforks 125 fflush(stdout);
1091 siliconforks 116 }
1092     HTTPServer_run(ip_address, (uint16_t) numeric_port, handler);
1093     if (verbose) {
1094     printf("Stopping HTTP server\n");
1095 siliconforks 125 fflush(stdout);
1096 siliconforks 116 }
1097    
1098     jscoverage_cleanup();
1099    
1100     free(no_instrument);
1101    
1102 siliconforks 125 LOCK(&source_cache_mutex);
1103 siliconforks 116 while (source_cache != NULL) {
1104     SourceCache * p = source_cache;
1105     source_cache = source_cache->next;
1106     free(p->url);
1107     Stream_delete(p->source);
1108     free(p);
1109     }
1110 siliconforks 125 UNLOCK(&source_cache_mutex);
1111 siliconforks 116
1112     return 0;
1113     }

  ViewVC Help
Powered by ViewVC 1.1.24