1 /*
2  * Javascript Compressor
3  *
4  * Copyright (c) 2008-2018 Fabrice Bellard
5  * Copyright (c) 2017-2018 Charlie Gordon
6  *
7  * Permission is hereby granted, free of charge, to any person obtaining a copy
8  * of this software and associated documentation files (the "Software"), to deal
9  * in the Software without restriction, including without limitation the rights
10  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11  * copies of the Software, and to permit persons to whom the Software is
12  * furnished to do so, subject to the following conditions:
13  *
14  * The above copyright notice and this permission notice shall be included in
15  * all copies or substantial portions of the Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
20  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23  * THE SOFTWARE.
24  */
25 #include <stdlib.h>
26 #include <stdio.h>
27 #include <getopt.h>
28 #include <stdarg.h>
29 #include <string.h>
30 #include <inttypes.h>
31 #include <unistd.h>
32 
33 #include "cutils.h"
34 
35 typedef struct JSToken {
36     int tok;
37     char buf[20];
38     char *str;
39     int len;
40     int size;
41     int line_num;  /* line number for start of token */
42     int lines;     /* number of embedded linefeeds in token */
43 } JSToken;
44 
45 enum {
46     TOK_EOF = 256,
47     TOK_IDENT,
48     TOK_STR1,
49     TOK_STR2,
50     TOK_STR3,
51     TOK_NUM,
52     TOK_COM,
53     TOK_LCOM,
54 };
55 
tok_reset(JSToken * tt)56 void tok_reset(JSToken *tt)
57 {
58     if (tt->str != tt->buf) {
59         free(tt->str);
60         tt->str = tt->buf;
61         tt->size = sizeof(tt->buf);
62     }
63     tt->len = 0;
64 }
65 
tok_add_ch(JSToken * tt,int c)66 void tok_add_ch(JSToken *tt, int c)
67 {
68     if (tt->len + 1 > tt->size) {
69         tt->size *= 2;
70         if (tt->str == tt->buf) {
71             tt->str = malloc(tt->size);
72             memcpy(tt->str, tt->buf, tt->len);
73         } else {
74             tt->str = realloc(tt->str, tt->size);
75         }
76     }
77     tt->str[tt->len++] = c;
78 }
79 
80 FILE *infile;
81 const char *filename;
82 int output_line_num;
83 int line_num;
84 int ch;
85 JSToken tokc;
86 
87 int skip_mask;
88 #define DEFINE_MAX 20
89 char *define_tab[DEFINE_MAX];
90 int define_len;
91 
error(const char * fmt,...)92 void error(const char *fmt, ...)
93 {
94     va_list ap;
95     va_start(ap, fmt);
96     if (filename) {
97         fprintf(stderr, "%s:%d: ", filename, line_num);
98     } else {
99         fprintf(stderr, "jscompress: ");
100     }
101     vfprintf(stderr, fmt, ap);
102     fprintf(stderr, "\n");
103     va_end(ap);
104     exit(1);
105 }
106 
define_symbol(const char * def)107 void define_symbol(const char *def)
108 {
109     int i;
110     for (i = 0; i < define_len; i++) {
111         if (!strcmp(tokc.str, define_tab[i]))
112             return;
113     }
114     if (define_len >= DEFINE_MAX)
115         error("too many defines");
116     define_tab[define_len++] = strdup(def);
117 }
118 
undefine_symbol(const char * def)119 void undefine_symbol(const char *def)
120 {
121     int i, j;
122     for (i = j = 0; i < define_len; i++) {
123         if (!strcmp(tokc.str, define_tab[i])) {
124             free(define_tab[i]);
125         } else {
126             define_tab[j++] = define_tab[i];
127         }
128     }
129     define_len = j;
130 }
131 
find_symbol(const char * def)132 const char *find_symbol(const char *def)
133 {
134     int i;
135     for (i = 0; i < define_len; i++) {
136         if (!strcmp(tokc.str, define_tab[i]))
137             return "1";
138     }
139     return NULL;
140 }
141 
142 void next(void);
143 
nextch(void)144 void nextch(void)
145 {
146     ch = fgetc(infile);
147     if (ch == '\n')
148         line_num++;
149 }
150 
skip_blanks(void)151 int skip_blanks(void)
152 {
153     for (;;) {
154         next();
155         if (tokc.tok != ' ' && tokc.tok != '\t' &&
156             tokc.tok != TOK_COM && tokc.tok != TOK_LCOM)
157             return tokc.tok;
158     }
159 }
160 
parse_directive(void)161 void parse_directive(void)
162 {
163     int ifdef, mask = skip_mask;
164     /* simplistic preprocessor:
165        #define / #undef / #ifdef / #ifndef / #else / #endif
166        no symbol substitution.
167      */
168     skip_mask = 0;  /* disable skipping to parse preprocessor line */
169     nextch();
170     if (skip_blanks() != TOK_IDENT)
171         error("expected preprocessing directive after #");
172 
173     if (!strcmp(tokc.str, "define")) {
174         if (skip_blanks() != TOK_IDENT)
175             error("expected identifier after #define");
176         define_symbol(tokc.str);
177     } else if (!strcmp(tokc.str, "undef")) {
178         if (skip_blanks() != TOK_IDENT)
179             error("expected identifier after #undef");
180         undefine_symbol(tokc.str);
181     } else if ((ifdef = 1, !strcmp(tokc.str, "ifdef")) ||
182                (ifdef = 0, !strcmp(tokc.str, "ifndef"))) {
183         if (skip_blanks() != TOK_IDENT)
184             error("expected identifier after #ifdef/#ifndef");
185         mask = (mask << 2) | 2 | ifdef;
186         if (find_symbol(tokc.str))
187             mask ^= 1;
188     } else if (!strcmp(tokc.str, "else")) {
189         if (!(mask & 2))
190             error("#else without a #if");
191         mask ^= 1;
192     } else if (!strcmp(tokc.str, "endif")) {
193         if (!(mask & 2))
194             error("#endif without a #if");
195         mask >>= 2;
196     } else {
197         error("unsupported preprocessing directive");
198     }
199     if (skip_blanks() != '\n')
200         error("extra characters on preprocessing line");
201     skip_mask = mask;
202 }
203 
204 /* return -1 if invalid char */
hex_to_num(int ch)205 static int hex_to_num(int ch)
206 {
207     if (ch >= 'a' && ch <= 'f')
208         return ch - 'a' + 10;
209     else if (ch >= 'A' && ch <= 'F')
210         return ch - 'A' + 10;
211     else if (ch >= '0' && ch <= '9')
212         return ch - '0';
213     else
214         return -1;
215 }
216 
next(void)217 void next(void)
218 {
219 again:
220     tok_reset(&tokc);
221     tokc.line_num = line_num;
222     tokc.lines = 0;
223     switch(ch) {
224     case EOF:
225         tokc.tok = TOK_EOF;
226         if (skip_mask)
227             error("missing #endif");
228         break;
229     case 'a' ... 'z':
230     case 'A' ... 'Z':
231     case '_':
232     case '$':
233         tok_add_ch(&tokc, ch);
234         nextch();
235         while ((ch >= 'a' && ch <= 'z') ||
236                (ch >= 'A' && ch <= 'Z') ||
237                (ch >= '0' && ch <= '9') ||
238                (ch == '_' || ch == '$')) {
239             tok_add_ch(&tokc, ch);
240             nextch();
241         }
242         tok_add_ch(&tokc, '\0');
243         tokc.tok = TOK_IDENT;
244         break;
245     case '.':
246         nextch();
247         if (ch >= '0' && ch <= '9') {
248             tok_add_ch(&tokc, '.');
249             goto has_dot;
250         }
251         tokc.tok = '.';
252         break;
253     case '0':
254         tok_add_ch(&tokc, ch);
255         nextch();
256         if (ch == 'x' || ch == 'X') {
257             /* hexa */
258             tok_add_ch(&tokc, ch);
259             nextch();
260             while ((ch >= 'a' && ch <= 'f') ||
261                    (ch >= 'A' && ch <= 'F') ||
262                    (ch >= '0' && ch <= '9')) {
263                 tok_add_ch(&tokc, ch);
264                 nextch();
265             }
266             tok_add_ch(&tokc, '\0');
267             tokc.tok = TOK_NUM;
268             break;
269         }
270         goto has_digit;
271 
272     case '1' ... '9':
273         tok_add_ch(&tokc, ch);
274         nextch();
275     has_digit:
276         /* decimal */
277         while (ch >= '0' && ch <= '9') {
278             tok_add_ch(&tokc, ch);
279             nextch();
280         }
281         if (ch == '.') {
282             tok_add_ch(&tokc, ch);
283             nextch();
284         has_dot:
285             while (ch >= '0' && ch <= '9') {
286                 tok_add_ch(&tokc, ch);
287                 nextch();
288             }
289         }
290         if (ch == 'e' || ch == 'E') {
291             tok_add_ch(&tokc, ch);
292             nextch();
293             if (ch == '+' || ch == '-') {
294                 tok_add_ch(&tokc, ch);
295                 nextch();
296             }
297             while (ch >= '0' && ch <= '9') {
298                 tok_add_ch(&tokc, ch);
299                 nextch();
300             }
301         }
302         tok_add_ch(&tokc, '\0');
303         tokc.tok = TOK_NUM;
304         break;
305     case '`':
306         {
307             nextch();
308             while (ch != '`' && ch != EOF) {
309                 if (ch == '\\') {
310                     tok_add_ch(&tokc, ch);
311                     nextch();
312                     if (ch == EOF) {
313                         error("unexpected char after '\\'");
314                     }
315                     tok_add_ch(&tokc, ch);
316                 } else {
317                     tok_add_ch(&tokc, ch);
318                     nextch();
319                 }
320             }
321             nextch();
322             tok_add_ch(&tokc, 0);
323             tokc.tok = TOK_STR3;
324         }
325         break;
326     case '\"':
327     case '\'':
328         {
329             int n, i, c, hex_digit_count;
330             int quote_ch;
331             quote_ch = ch;
332             nextch();
333             while (ch != quote_ch && ch != EOF) {
334                 if (ch == '\\') {
335                     nextch();
336                     switch(ch) {
337                     case 'n':
338                         tok_add_ch(&tokc, '\n');
339                         nextch();
340                         break;
341                     case 'r':
342                         tok_add_ch(&tokc, '\r');
343                         nextch();
344                         break;
345                     case 't':
346                         tok_add_ch(&tokc, '\t');
347                         nextch();
348                         break;
349                     case 'v':
350                         tok_add_ch(&tokc, '\v');
351                         nextch();
352                         break;
353                     case '\"':
354                     case '\'':
355                     case '\\':
356                         tok_add_ch(&tokc, ch);
357                         nextch();
358                         break;
359                     case '0' ... '7':
360                         n = 0;
361                         while (ch >= '0' && ch <= '7') {
362                             n = n * 8 + (ch - '0');
363                             nextch();
364                         }
365                         tok_add_ch(&tokc, n);
366                         break;
367                     case 'x':
368                     case 'u':
369                         if (ch == 'x')
370                             hex_digit_count = 2;
371                         else
372                             hex_digit_count = 4;
373                         nextch();
374                         n = 0;
375                         for(i = 0; i < hex_digit_count; i++) {
376                             c = hex_to_num(ch);
377                             if (c < 0)
378                                 error("unexpected char after '\\x'");
379                             n = n * 16 + c;
380                             nextch();
381                         }
382                         if (n >= 256)
383                             error("unicode is currently unsupported");
384                         tok_add_ch(&tokc, n);
385                         break;
386 
387                     default:
388                         error("unexpected char after '\\'");
389                     }
390                 } else {
391                     /* XXX: should refuse embedded newlines */
392                     tok_add_ch(&tokc, ch);
393                     nextch();
394                 }
395             }
396             nextch();
397             tok_add_ch(&tokc, 0);
398             if (quote_ch == '\'')
399                 tokc.tok = TOK_STR1;
400             else
401                 tokc.tok = TOK_STR2;
402         }
403         break;
404     case '/':
405         nextch();
406         if (ch == '/') {
407             tok_add_ch(&tokc, '/');
408             tok_add_ch(&tokc, ch);
409             nextch();
410             while (ch != '\n' && ch != EOF) {
411                 tok_add_ch(&tokc, ch);
412                 nextch();
413             }
414             tok_add_ch(&tokc, '\0');
415             tokc.tok = TOK_LCOM;
416         } else if (ch == '*') {
417             int last;
418             tok_add_ch(&tokc, '/');
419             tok_add_ch(&tokc, ch);
420             last = 0;
421             for(;;) {
422                 nextch();
423                 if (ch == EOF)
424                     error("unterminated comment");
425                 if (ch == '\n')
426                     tokc.lines++;
427                 tok_add_ch(&tokc, ch);
428                 if (last == '*' && ch == '/')
429                     break;
430                 last = ch;
431             }
432             nextch();
433             tok_add_ch(&tokc, '\0');
434             tokc.tok = TOK_COM;
435         } else {
436             tokc.tok = '/';
437         }
438         break;
439     case '#':
440         parse_directive();
441         goto again;
442     case '\n':
443         /* adjust line number */
444         tokc.line_num--;
445         tokc.lines++;
446         /* fall thru */
447     default:
448         tokc.tok = ch;
449         nextch();
450         break;
451     }
452     if (skip_mask & 1)
453         goto again;
454 }
455 
print_tok(FILE * f,JSToken * tt)456 void print_tok(FILE *f, JSToken *tt)
457 {
458     /* keep output lines in sync with input lines */
459     while (output_line_num < tt->line_num) {
460         putc('\n', f);
461         output_line_num++;
462     }
463 
464     switch(tt->tok) {
465     case TOK_IDENT:
466     case TOK_COM:
467     case TOK_LCOM:
468         fprintf(f, "%s", tt->str);
469         break;
470     case TOK_NUM:
471         {
472             unsigned long a;
473             char *p;
474             a = strtoul(tt->str, &p, 0);
475             if (*p == '\0' && a <= 0x7fffffff) {
476                 /* must be an integer */
477                 fprintf(f, "%d", (int)a);
478             } else {
479                 fprintf(f, "%s", tt->str);
480             }
481         }
482         break;
483     case TOK_STR3:
484         fprintf(f, "`%s`", tt->str);
485         break;
486     case TOK_STR1:
487     case TOK_STR2:
488         {
489             int i, c, quote_ch;
490             if (tt->tok == TOK_STR1)
491                 quote_ch = '\'';
492             else
493                 quote_ch = '\"';
494             fprintf(f, "%c", quote_ch);
495             for(i = 0; i < tt->len - 1; i++) {
496                 c = (uint8_t)tt->str[i];
497                 switch(c) {
498                 case '\r':
499                     fprintf(f, "\\r");
500                     break;
501                 case '\n':
502                     fprintf(f, "\\n");
503                     break;
504                 case '\t':
505                     fprintf(f, "\\t");
506                     break;
507                 case '\v':
508                     fprintf(f, "\\v");
509                     break;
510                 case '\"':
511                 case '\'':
512                     if (c == quote_ch)
513                         fprintf(f, "\\%c", c);
514                     else
515                         fprintf(f, "%c", c);
516                     break;
517                 case '\\':
518                     fprintf(f, "\\\\");
519                     break;
520                 default:
521                     /* XXX: no utf-8 support! */
522                     if (c >= 32 && c <= 255) {
523                         fprintf(f, "%c", c);
524                     } else if (c <= 255)
525                         fprintf(f, "\\x%02x", c);
526                     else
527                         fprintf(f, "\\u%04x", c);
528                     break;
529                 }
530             }
531             fprintf(f, "%c", quote_ch);
532         }
533         break;
534     default:
535         if (tokc.tok >= 256)
536             error("unsupported token in print_tok: %d", tt->tok);
537         fprintf(f, "%c", tt->tok);
538         break;
539     }
540     output_line_num += tt->lines;
541 }
542 
543 /* check if token pasting could occur */
compat_token(int c1,int c2)544 static BOOL compat_token(int c1, int c2)
545 {
546     if ((c1 == TOK_IDENT || c1 == TOK_NUM) &&
547         (c2 == TOK_IDENT || c2 == TOK_NUM))
548         return FALSE;
549 
550     if ((c1 == c2 && strchr("+-<>&|=*/.", c1))
551     ||  (c2 == '=' && strchr("+-<>&|!*/^%", c1))
552     ||  (c1 == '=' && c2 == '>')
553     ||  (c1 == '/' && c2 == '*')
554     ||  (c1 == '.' && c2 == TOK_NUM)
555     ||  (c1 == TOK_NUM && c2 == '.'))
556         return FALSE;
557 
558     return TRUE;
559 }
560 
js_compress(const char * filename,const char * outfilename,BOOL do_strip,BOOL keep_header)561 void js_compress(const char *filename, const char *outfilename,
562                  BOOL do_strip, BOOL keep_header)
563 {
564     FILE *outfile;
565     int ltok, seen_space;
566 
567     line_num = 1;
568     infile = fopen(filename, "rb");
569     if (!infile) {
570         perror(filename);
571         exit(1);
572     }
573 
574     output_line_num = 1;
575     outfile = fopen(outfilename, "wb");
576     if (!outfile) {
577         perror(outfilename);
578         exit(1);
579     }
580 
581     nextch();
582     next();
583     ltok = 0;
584     seen_space = 0;
585     if (do_strip) {
586         if (keep_header) {
587             while (tokc.tok == ' ' ||
588                    tokc.tok == '\n' ||
589                    tokc.tok == '\t' ||
590                    tokc.tok == '\v' ||
591                    tokc.tok == '\b' ||
592                    tokc.tok == '\f') {
593                 seen_space = 1;
594                 next();
595             }
596             if (tokc.tok == TOK_COM) {
597                 print_tok(outfile, &tokc);
598                 //fprintf(outfile, "\n");
599                 ltok = tokc.tok;
600                 seen_space = 0;
601                 next();
602             }
603         }
604 
605         for(;;) {
606             if (tokc.tok == TOK_EOF)
607                 break;
608             if (tokc.tok == ' ' ||
609                 tokc.tok == '\r' ||
610                 tokc.tok == '\t' ||
611                 tokc.tok == '\v' ||
612                 tokc.tok == '\b' ||
613                 tokc.tok == '\f' ||
614                 tokc.tok == TOK_LCOM ||
615                 tokc.tok == TOK_COM) {
616                 /* don't print spaces or comments */
617                 seen_space = 1;
618             } else if (tokc.tok == TOK_STR3) {
619                 print_tok(outfile, &tokc);
620                 ltok = tokc.tok;
621                 seen_space = 0;
622             } else if (tokc.tok == TOK_STR1 || tokc.tok == TOK_STR2) {
623                 int count, i;
624                 /* find the optimal quote char */
625                 count = 0;
626                 for(i = 0; i < tokc.len; i++) {
627                     if (tokc.str[i] == '\'')
628                         count++;
629                     else if (tokc.str[i] == '\"')
630                         count--;
631                 }
632                 if (count > 0)
633                     tokc.tok = TOK_STR2;
634                 else if (count < 0)
635                     tokc.tok = TOK_STR1;
636                 print_tok(outfile, &tokc);
637                 ltok = tokc.tok;
638                 seen_space = 0;
639             } else {
640                 if (seen_space && !compat_token(ltok, tokc.tok)) {
641                     fprintf(outfile, " ");
642                 }
643                 print_tok(outfile, &tokc);
644                 ltok = tokc.tok;
645                 seen_space = 0;
646             }
647             next();
648         }
649     } else {
650         /* just handle preprocessing */
651         while (tokc.tok != TOK_EOF) {
652             print_tok(outfile, &tokc);
653             next();
654         }
655     }
656 
657     fclose(outfile);
658     fclose(infile);
659 }
660 
661 #define HASH_SIZE 30011
662 #define MATCH_LEN_MIN 3
663 #define MATCH_LEN_MAX (4 + 63)
664 #define DIST_MAX 65535
665 
find_longest_match(int * pdist,const uint8_t * src,int src_len,const int * hash_next,int cur_pos)666 static int find_longest_match(int *pdist, const uint8_t *src, int src_len,
667                               const int *hash_next, int cur_pos)
668 {
669     int pos, i, match_len, match_pos, pos_min, len_max;
670 
671     len_max = min_int(src_len - cur_pos, MATCH_LEN_MAX);
672     match_len = 0;
673     match_pos = 0;
674     pos_min = max_int(cur_pos - DIST_MAX - 1, 0);
675     pos = hash_next[cur_pos];
676     while (pos >= pos_min) {
677         for(i = 0; i < len_max; i++) {
678             if (src[cur_pos + i] != src[pos + i])
679                 break;
680         }
681         if (i > match_len) {
682             match_len = i;
683             match_pos = pos;
684         }
685         pos = hash_next[pos];
686     }
687     *pdist = cur_pos - match_pos - 1;
688     return match_len;
689 }
690 
lz_compress(uint8_t ** pdst,const uint8_t * src,int src_len)691 int lz_compress(uint8_t **pdst, const uint8_t *src, int src_len)
692 {
693     int *hash_table, *hash_next;
694     uint32_t h, v;
695     int i, dist, len, len1, dist1;
696     uint8_t *dst, *q;
697 
698     /* build the hash table */
699 
700     hash_table = malloc(sizeof(hash_table[0]) * HASH_SIZE);
701     for(i = 0; i < HASH_SIZE; i++)
702         hash_table[i] = -1;
703     hash_next = malloc(sizeof(hash_next[0]) * src_len);
704     for(i = 0; i < src_len; i++)
705         hash_next[i] = -1;
706 
707     for(i = 0; i < src_len - MATCH_LEN_MIN + 1; i++) {
708         h = ((src[i] << 16) | (src[i + 1] << 8) | src[i + 2]) % HASH_SIZE;
709         hash_next[i] = hash_table[h];
710         hash_table[h] = i;
711     }
712     for(;i < src_len; i++) {
713         hash_next[i] = -1;
714     }
715     free(hash_table);
716 
717     dst = malloc(src_len + 4); /* never larger than the source */
718     q = dst;
719     *q++ = src_len >> 24;
720     *q++ = src_len >> 16;
721     *q++ = src_len >> 8;
722     *q++ = src_len >> 0;
723     /* compress */
724     i = 0;
725     while (i < src_len) {
726         if (src[i] >= 128)
727             return -1;
728         len = find_longest_match(&dist, src, src_len, hash_next, i);
729         if (len >= MATCH_LEN_MIN) {
730             /* heuristic: see if better length just after */
731             len1 = find_longest_match(&dist1, src, src_len, hash_next, i + 1);
732             if (len1 > len)
733                 goto no_match;
734         }
735         if (len < MATCH_LEN_MIN) {
736         no_match:
737             *q++ = src[i];
738             i++;
739         } else if (len <= (3 + 15) && dist < (1 << 10)) {
740             v = 0x8000 | ((len - 3) << 10) | dist;
741             *q++ = v >> 8;
742             *q++ = v;
743             i += len;
744         } else if (len >= 4 && len <= (4 + 63) && dist < (1 << 16)) {
745             v = 0xc00000 | ((len - 4) << 16) | dist;
746             *q++ = v >> 16;
747             *q++ = v >> 8;
748             *q++ = v;
749             i += len;
750         } else {
751             goto no_match;
752         }
753     }
754     free(hash_next);
755     *pdst = dst;
756     return q - dst;
757 }
758 
load_file(uint8_t ** pbuf,const char * filename)759 static int load_file(uint8_t **pbuf, const char *filename)
760 {
761     FILE *f;
762     uint8_t *buf;
763     int buf_len;
764 
765     f = fopen(filename, "rb");
766     if (!f) {
767         perror(filename);
768         exit(1);
769     }
770     fseek(f, 0, SEEK_END);
771     buf_len = ftell(f);
772     fseek(f, 0, SEEK_SET);
773     buf = malloc(buf_len + 1);
774     fread(buf, 1, buf_len, f);
775     buf[buf_len] = '\0';
776     fclose(f);
777     *pbuf = buf;
778     return buf_len;
779 }
780 
save_file(const char * filename,const uint8_t * buf,int buf_len)781 static void save_file(const char *filename, const uint8_t *buf, int buf_len)
782 {
783     FILE *f;
784 
785     f = fopen(filename, "wb");
786     if (!f) {
787         perror(filename);
788         exit(1);
789     }
790     fwrite(buf, 1, buf_len, f);
791     fclose(f);
792 }
793 
save_c_source(const char * filename,const uint8_t * buf,int buf_len,const char * var_name)794 static void save_c_source(const char *filename, const uint8_t *buf, int buf_len,
795                           const char *var_name)
796 {
797     FILE *f;
798     int i;
799 
800     f = fopen(filename, "wb");
801     if (!f) {
802         perror(filename);
803         exit(1);
804     }
805     fprintf(f, "/* This file is automatically generated - do not edit */\n\n");
806     fprintf(f, "const uint8_t %s[] = {\n", var_name);
807     for(i = 0; i < buf_len; i++) {
808         fprintf(f, " 0x%02x,", buf[i]);
809         if ((i % 8) == 7 || (i == buf_len - 1))
810             fprintf(f, "\n");
811     }
812     fprintf(f, "};\n");
813     fclose(f);
814 }
815 
816 #define DEFAULT_OUTPUT_FILENAME "out.js"
817 
help(void)818 void help(void)
819 {
820     printf("jscompress version 1.0 Copyright (c) 2008-2018 Fabrice Bellard\n"
821            "usage: jscompress [options] filename\n"
822            "Javascript compressor\n"
823            "\n"
824            "-h          print this help\n"
825            "-n          do not compress spaces\n"
826            "-H          keep the first comment\n"
827            "-c          compress to file\n"
828            "-C name     compress to C source ('name' is the variable name)\n"
829            "-D symbol   define preprocessor symbol\n"
830            "-U symbol   undefine preprocessor symbol\n"
831            "-o outfile  set the output filename (default=%s)\n",
832            DEFAULT_OUTPUT_FILENAME);
833     exit(1);
834 }
835 
main(int argc,char ** argv)836 int main(int argc, char **argv)
837 {
838     int c, do_strip, keep_header, compress;
839     const char *out_filename, *c_var, *fname;
840     char tmpfilename[1024];
841 
842     do_strip = 1;
843     keep_header = 0;
844     out_filename = DEFAULT_OUTPUT_FILENAME;
845     compress = 0;
846     c_var = NULL;
847     for(;;) {
848         c = getopt(argc, argv, "hno:HcC:D:U:");
849         if (c == -1)
850             break;
851         switch(c) {
852         case 'h':
853             help();
854             break;
855         case 'n':
856             do_strip = 0;
857             break;
858         case 'o':
859             out_filename = optarg;
860             break;
861         case 'H':
862             keep_header = 1;
863             break;
864         case 'c':
865             compress = 1;
866             break;
867         case 'C':
868             c_var = optarg;
869             compress = 1;
870             break;
871         case 'D':
872             define_symbol(optarg);
873             break;
874         case 'U':
875             undefine_symbol(optarg);
876             break;
877         }
878     }
879     if (optind >= argc)
880         help();
881 
882     filename = argv[optind++];
883 
884     if (compress) {
885 #if defined(__ANDROID__)
886         /* XXX: use another directory ? */
887         snprintf(tmpfilename, sizeof(tmpfilename), "out.%d", getpid());
888 #else
889         snprintf(tmpfilename, sizeof(tmpfilename), "/tmp/out.%d", getpid());
890 #endif
891         fname = tmpfilename;
892     } else {
893         fname = out_filename;
894     }
895     js_compress(filename, fname, do_strip, keep_header);
896 
897     if (compress) {
898         uint8_t *buf1, *buf2;
899         int buf1_len, buf2_len;
900 
901         buf1_len = load_file(&buf1, fname);
902         unlink(fname);
903         buf2_len = lz_compress(&buf2, buf1, buf1_len);
904         if (buf2_len < 0) {
905             fprintf(stderr, "Could not compress file (UTF8 chars are forbidden)\n");
906             exit(1);
907         }
908 
909         if (c_var) {
910             save_c_source(out_filename, buf2, buf2_len, c_var);
911         } else {
912             save_file(out_filename, buf2, buf2_len);
913         }
914         free(buf1);
915         free(buf2);
916     }
917     return 0;
918 }
919