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

Annotation of /trunk/jscoverage-server.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 179 - (hide annotations)
Sun Sep 21 18:35:21 2008 UTC (11 years, 2 months ago) by siliconforks
File MIME type: text/plain
File size: 36762 byte(s)
Do source code highlighting during instrumentation.

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

  ViewVC Help
Powered by ViewVC 1.1.24