1 /* Copyright 2014 Google Inc. All Rights Reserved.
2 
3    Distributed under MIT license.
4    See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
5 */
6 
7 /* Command line interface for Brotli library. */
8 
9 #include <errno.h>
10 #include <fcntl.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <sys/stat.h>
15 #include <sys/types.h>
16 #include <time.h>
17 
18 #include "../common/constants.h"
19 #include "../common/version.h"
20 #include <brotli/decode.h>
21 #include <brotli/encode.h>
22 
23 #if !defined(_WIN32)
24 #include <unistd.h>
25 #include <utime.h>
26 #define MAKE_BINARY(FILENO) (FILENO)
27 #else
28 #include <io.h>
29 #include <share.h>
30 #include <sys/utime.h>
31 
32 #define MAKE_BINARY(FILENO) (_setmode((FILENO), _O_BINARY), (FILENO))
33 
34 #if !defined(__MINGW32__)
35 #define STDIN_FILENO _fileno(stdin)
36 #define STDOUT_FILENO _fileno(stdout)
37 #define S_IRUSR S_IREAD
38 #define S_IWUSR S_IWRITE
39 #endif
40 
41 #define fdopen _fdopen
42 #define isatty _isatty
43 #define unlink _unlink
44 #define utimbuf _utimbuf
45 #define utime _utime
46 
47 #define fopen ms_fopen
48 #define open ms_open
49 
50 #define chmod(F, P) (0)
51 #define chown(F, O, G) (0)
52 
53 #if defined(_MSC_VER) && (_MSC_VER >= 1400)
54 #define fseek _fseeki64
55 #define ftell _ftelli64
56 #endif
57 
ms_fopen(const char * filename,const char * mode)58 static FILE* ms_fopen(const char* filename, const char* mode) {
59   FILE* result = 0;
60   fopen_s(&result, filename, mode);
61   return result;
62 }
63 
ms_open(const char * filename,int oflag,int pmode)64 static int ms_open(const char* filename, int oflag, int pmode) {
65   int result = -1;
66   _sopen_s(&result, filename, oflag | O_BINARY, _SH_DENYNO, pmode);
67   return result;
68 }
69 #endif  /* WIN32 */
70 
71 typedef enum {
72   COMMAND_COMPRESS,
73   COMMAND_DECOMPRESS,
74   COMMAND_HELP,
75   COMMAND_INVALID,
76   COMMAND_TEST_INTEGRITY,
77   COMMAND_NOOP,
78   COMMAND_VERSION
79 } Command;
80 
81 #define DEFAULT_LGWIN 22
82 #define DEFAULT_SUFFIX ".br"
83 #define MAX_OPTIONS 20
84 
85 typedef struct {
86   /* Parameters */
87   int quality;
88   int lgwin;
89   BROTLI_BOOL force_overwrite;
90   BROTLI_BOOL junk_source;
91   BROTLI_BOOL copy_stat;
92   BROTLI_BOOL verbose;
93   BROTLI_BOOL write_to_stdout;
94   BROTLI_BOOL test_integrity;
95   BROTLI_BOOL decompress;
96   const char* output_path;
97   const char* suffix;
98   int not_input_indices[MAX_OPTIONS];
99   size_t longest_path_len;
100   size_t input_count;
101 
102   /* Inner state */
103   int argc;
104   char** argv;
105   char* modified_path;  /* Storage for path with appended / cut suffix */
106   int iterator;
107   int ignore;
108   BROTLI_BOOL iterator_error;
109   uint8_t* buffer;
110   uint8_t* input;
111   uint8_t* output;
112   const char* current_input_path;
113   const char* current_output_path;
114   FILE* fin;
115   FILE* fout;
116 } Context;
117 
118 /* Parse up to 5 decimal digits. */
ParseInt(const char * s,int low,int high,int * result)119 static BROTLI_BOOL ParseInt(const char* s, int low, int high, int* result) {
120   int value = 0;
121   int i;
122   for (i = 0; i < 5; ++i) {
123     char c = s[i];
124     if (c == 0) break;
125     if (s[i] < '0' || s[i] > '9') return BROTLI_FALSE;
126     value = (10 * value) + (c - '0');
127   }
128   if (i == 0) return BROTLI_FALSE;
129   if (i > 1 && s[0] == '0') return BROTLI_FALSE;
130   if (s[i] != 0) return BROTLI_FALSE;
131   if (value < low || value > high) return BROTLI_FALSE;
132   *result = value;
133   return BROTLI_TRUE;
134 }
135 
136 /* Returns "base file name" or its tail, if it contains '/' or '\'. */
FileName(const char * path)137 static const char* FileName(const char* path) {
138   const char* separator_position = strrchr(path, '/');
139   if (separator_position) path = separator_position + 1;
140   separator_position = strrchr(path, '\\');
141   if (separator_position) path = separator_position + 1;
142   return path;
143 }
144 
145 /* Detect if the program name is a special alias that infers a command type. */
ParseAlias(const char * name)146 static Command ParseAlias(const char* name) {
147   /* TODO: cast name to lower case? */
148   const char* unbrotli = "unbrotli";
149   size_t unbrotli_len = strlen(unbrotli);
150   name = FileName(name);
151   /* Partial comparison. On Windows there could be ".exe" suffix. */
152   if (strncmp(name, unbrotli, unbrotli_len) == 0) {
153     char terminator = name[unbrotli_len];
154     if (terminator == 0 || terminator == '.') return COMMAND_DECOMPRESS;
155   }
156   return COMMAND_COMPRESS;
157 }
158 
ParseParams(Context * params)159 static Command ParseParams(Context* params) {
160   int argc = params->argc;
161   char** argv = params->argv;
162   int i;
163   int next_option_index = 0;
164   size_t input_count = 0;
165   size_t longest_path_len = 1;
166   BROTLI_BOOL command_set = BROTLI_FALSE;
167   BROTLI_BOOL quality_set = BROTLI_FALSE;
168   BROTLI_BOOL output_set = BROTLI_FALSE;
169   BROTLI_BOOL keep_set = BROTLI_FALSE;
170   BROTLI_BOOL lgwin_set = BROTLI_FALSE;
171   BROTLI_BOOL suffix_set = BROTLI_FALSE;
172   BROTLI_BOOL after_dash_dash = BROTLI_FALSE;
173   Command command = ParseAlias(argv[0]);
174 
175   for (i = 1; i < argc; ++i) {
176     const char* arg = argv[i];
177     /* C99 5.1.2.2.1: "members argv[0] through argv[argc-1] inclusive shall
178        contain pointers to strings"; NULL and 0-length are not forbidden. */
179     size_t arg_len = arg ? strlen(arg) : 0;
180 
181     if (arg_len == 0) {
182       params->not_input_indices[next_option_index++] = i;
183       continue;
184     }
185 
186     /* Too many options. The expected longest option list is:
187        "-q 0 -w 10 -o f -D d -S b -d -f -k -n -v --", i.e. 16 items in total.
188        This check is an additinal guard that is never triggered, but provides an
189        additional guard for future changes. */
190     if (next_option_index > (MAX_OPTIONS - 2)) {
191       return COMMAND_INVALID;
192     }
193 
194     /* Input file entry. */
195     if (after_dash_dash || arg[0] != '-' || arg_len == 1) {
196       input_count++;
197       if (longest_path_len < arg_len) longest_path_len = arg_len;
198       continue;
199     }
200 
201     /* Not a file entry. */
202     params->not_input_indices[next_option_index++] = i;
203 
204     /* '--' entry stop parsing arguments. */
205     if (arg_len == 2 && arg[1] == '-') {
206       after_dash_dash = BROTLI_TRUE;
207       continue;
208     }
209 
210     /* Simple / coalesced options. */
211     if (arg[1] != '-') {
212       size_t j;
213       for (j = 1; j < arg_len; ++j) {
214         char c = arg[j];
215         if (c >= '0' && c <= '9') {
216           if (quality_set) return COMMAND_INVALID;
217           quality_set = BROTLI_TRUE;
218           params->quality = c - '0';
219           continue;
220         } else if (c == 'c') {
221           if (output_set) return COMMAND_INVALID;
222           output_set = BROTLI_TRUE;
223           params->write_to_stdout = BROTLI_TRUE;
224           continue;
225         } else if (c == 'd') {
226           if (command_set) return COMMAND_INVALID;
227           command_set = BROTLI_TRUE;
228           command = COMMAND_DECOMPRESS;
229           continue;
230         } else if (c == 'f') {
231           if (params->force_overwrite) return COMMAND_INVALID;
232           params->force_overwrite = BROTLI_TRUE;
233           continue;
234         } else if (c == 'h') {
235           /* Don't parse further. */
236           return COMMAND_HELP;
237         } else if (c == 'j' || c == 'k') {
238           if (keep_set) return COMMAND_INVALID;
239           keep_set = BROTLI_TRUE;
240           params->junk_source = TO_BROTLI_BOOL(c == 'j');
241           continue;
242         } else if (c == 'n') {
243           if (!params->copy_stat) return COMMAND_INVALID;
244           params->copy_stat = BROTLI_FALSE;
245           continue;
246         } else if (c == 't') {
247           if (command_set) return COMMAND_INVALID;
248           command_set = BROTLI_TRUE;
249           command = COMMAND_TEST_INTEGRITY;
250           continue;
251         } else if (c == 'v') {
252           if (params->verbose) return COMMAND_INVALID;
253           params->verbose = BROTLI_TRUE;
254           continue;
255         } else if (c == 'V') {
256           /* Don't parse further. */
257           return COMMAND_VERSION;
258         } else if (c == 'Z') {
259           if (quality_set) return COMMAND_INVALID;
260           quality_set = BROTLI_TRUE;
261           params->quality = 11;
262           continue;
263         }
264         /* o/q/w/D/S with parameter is expected */
265         if (c != 'o' && c != 'q' && c != 'w' && c != 'D' && c != 'S') {
266           return COMMAND_INVALID;
267         }
268         if (j + 1 != arg_len) return COMMAND_INVALID;
269         i++;
270         if (i == argc || !argv[i] || argv[i][0] == 0) return COMMAND_INVALID;
271         params->not_input_indices[next_option_index++] = i;
272         if (c == 'o') {
273           if (output_set) return COMMAND_INVALID;
274           params->output_path = argv[i];
275         } else if (c == 'q') {
276           if (quality_set) return COMMAND_INVALID;
277           quality_set = ParseInt(argv[i], BROTLI_MIN_QUALITY,
278                                  BROTLI_MAX_QUALITY, &params->quality);
279           if (!quality_set) return COMMAND_INVALID;
280         } else if (c == 'w') {
281           if (lgwin_set) return COMMAND_INVALID;
282           lgwin_set = ParseInt(argv[i], 0,
283                                BROTLI_MAX_WINDOW_BITS, &params->lgwin);
284           if (!lgwin_set) return COMMAND_INVALID;
285           if (params->lgwin != 0 && params->lgwin < BROTLI_MIN_WINDOW_BITS) {
286             return COMMAND_INVALID;
287           }
288         } else if (c == 'S') {
289           if (suffix_set) return COMMAND_INVALID;
290           suffix_set = BROTLI_TRUE;
291           params->suffix = argv[i];
292         }
293       }
294     } else {  /* Double-dash. */
295       arg = &arg[2];
296       if (strcmp("best", arg) == 0) {
297         if (quality_set) return COMMAND_INVALID;
298         quality_set = BROTLI_TRUE;
299         params->quality = 11;
300       } else if (strcmp("decompress", arg) == 0) {
301         if (command_set) return COMMAND_INVALID;
302         command_set = BROTLI_TRUE;
303         command = COMMAND_DECOMPRESS;
304       } else if (strcmp("force", arg) == 0) {
305         if (params->force_overwrite) return COMMAND_INVALID;
306         params->force_overwrite = BROTLI_TRUE;
307       } else if (strcmp("help", arg) == 0) {
308         /* Don't parse further. */
309         return COMMAND_HELP;
310       } else if (strcmp("keep", arg) == 0) {
311         if (keep_set) return COMMAND_INVALID;
312         keep_set = BROTLI_TRUE;
313         params->junk_source = BROTLI_FALSE;
314       } else if (strcmp("no-copy-stat", arg) == 0) {
315         if (!params->copy_stat) return COMMAND_INVALID;
316         params->copy_stat = BROTLI_FALSE;
317       } else if (strcmp("rm", arg) == 0) {
318         if (keep_set) return COMMAND_INVALID;
319         keep_set = BROTLI_TRUE;
320         params->junk_source = BROTLI_TRUE;
321       } else if (strcmp("stdout", arg) == 0) {
322         if (output_set) return COMMAND_INVALID;
323         output_set = BROTLI_TRUE;
324         params->write_to_stdout = BROTLI_TRUE;
325       } else if (strcmp("test", arg) == 0) {
326         if (command_set) return COMMAND_INVALID;
327         command_set = BROTLI_TRUE;
328         command = COMMAND_TEST_INTEGRITY;
329       } else if (strcmp("verbose", arg) == 0) {
330         if (params->verbose) return COMMAND_INVALID;
331         params->verbose = BROTLI_TRUE;
332       } else if (strcmp("version", arg) == 0) {
333         /* Don't parse further. */
334         return COMMAND_VERSION;
335       } else {
336         /* key=value */
337         const char* value = strrchr(arg, '=');
338         size_t key_len;
339         if (!value || value[1] == 0) return COMMAND_INVALID;
340         key_len = (size_t)(value - arg);
341         value++;
342         if (strncmp("lgwin", arg, key_len) == 0) {
343           if (lgwin_set) return COMMAND_INVALID;
344           lgwin_set = ParseInt(value, 0,
345                                BROTLI_MAX_WINDOW_BITS, &params->lgwin);
346           if (!lgwin_set) return COMMAND_INVALID;
347           if (params->lgwin != 0 && params->lgwin < BROTLI_MIN_WINDOW_BITS) {
348             return COMMAND_INVALID;
349           }
350         } else if (strncmp("output", arg, key_len) == 0) {
351           if (output_set) return COMMAND_INVALID;
352           params->output_path = value;
353         } else if (strncmp("quality", arg, key_len) == 0) {
354           if (quality_set) return COMMAND_INVALID;
355           quality_set = ParseInt(value, BROTLI_MIN_QUALITY,
356                                  BROTLI_MAX_QUALITY, &params->quality);
357           if (!quality_set) return COMMAND_INVALID;
358         } else if (strncmp("suffix", arg, key_len) == 0) {
359           if (suffix_set) return COMMAND_INVALID;
360           suffix_set = BROTLI_TRUE;
361           params->suffix = value;
362         } else {
363           return COMMAND_INVALID;
364         }
365       }
366     }
367   }
368 
369   params->input_count = input_count;
370   params->longest_path_len = longest_path_len;
371   params->decompress = (command == COMMAND_DECOMPRESS);
372   params->test_integrity = (command == COMMAND_TEST_INTEGRITY);
373 
374   if (input_count > 1 && output_set) return COMMAND_INVALID;
375   if (params->test_integrity) {
376     if (params->output_path) return COMMAND_INVALID;
377     if (params->write_to_stdout) return COMMAND_INVALID;
378   }
379   if (strchr(params->suffix, '/') || strchr(params->suffix, '\\')) {
380     return COMMAND_INVALID;
381   }
382 
383   return command;
384 }
385 
PrintVersion(void)386 static void PrintVersion(void) {
387   int major = BROTLI_VERSION >> 24;
388   int minor = (BROTLI_VERSION >> 12) & 0xFFF;
389   int patch = BROTLI_VERSION & 0xFFF;
390   fprintf(stdout, "brotli %d.%d.%d\n", major, minor, patch);
391 }
392 
PrintHelp(const char * name)393 static void PrintHelp(const char* name) {
394   /* String is cut to pieces with length less than 509, to conform C90 spec. */
395   fprintf(stdout,
396 "Usage: %s [OPTION]... [FILE]...\n",
397           name);
398   fprintf(stdout,
399 "Options:\n"
400 "  -#                          compression level (0-9)\n"
401 "  -c, --stdout                write on standard output\n"
402 "  -d, --decompress            decompress\n"
403 "  -f, --force                 force output file overwrite\n"
404 "  -h, --help                  display this help and exit\n");
405   fprintf(stdout,
406 "  -j, --rm                    remove source file(s)\n"
407 "  -k, --keep                  keep source file(s) (default)\n"
408 "  -n, --no-copy-stat          do not copy source file(s) attributes\n"
409 "  -o FILE, --output=FILE      output file (only if 1 input file)\n");
410   fprintf(stdout,
411 "  -q NUM, --quality=NUM       compression level (%d-%d)\n",
412           BROTLI_MIN_QUALITY, BROTLI_MAX_QUALITY);
413   fprintf(stdout,
414 "  -t, --test                  test compressed file integrity\n"
415 "  -v, --verbose               verbose mode\n");
416   fprintf(stdout,
417 "  -w NUM, --lgwin=NUM         set LZ77 window size (0, %d-%d) (default:%d)\n",
418           BROTLI_MIN_WINDOW_BITS, BROTLI_MAX_WINDOW_BITS, DEFAULT_LGWIN);
419   fprintf(stdout,
420 "                              window size = 2**NUM - 16\n"
421 "                              0 lets compressor choose the optimal value\n");
422   fprintf(stdout,
423 "  -S SUF, --suffix=SUF        output file suffix (default:'%s')\n",
424           DEFAULT_SUFFIX);
425   fprintf(stdout,
426 "  -V, --version               display version and exit\n"
427 "  -Z, --best                  use best compression level (11) (default)\n"
428 "Simple options could be coalesced, i.e. '-9kf' is equivalent to '-9 -k -f'.\n"
429 "With no FILE, or when FILE is -, read standard input.\n"
430 "All arguments after '--' are treated as files.\n");
431 }
432 
PrintablePath(const char * path)433 static const char* PrintablePath(const char* path) {
434   return path ? path : "con";
435 }
436 
OpenInputFile(const char * input_path,FILE ** f)437 static BROTLI_BOOL OpenInputFile(const char* input_path, FILE** f) {
438   *f = NULL;
439   if (!input_path) {
440     *f = fdopen(MAKE_BINARY(STDIN_FILENO), "rb");
441     return BROTLI_TRUE;
442   }
443   *f = fopen(input_path, "rb");
444   if (!*f) {
445     fprintf(stderr, "failed to open input file [%s]: %s\n",
446             PrintablePath(input_path), strerror(errno));
447     return BROTLI_FALSE;
448   }
449   return BROTLI_TRUE;
450 }
451 
OpenOutputFile(const char * output_path,FILE ** f,BROTLI_BOOL force)452 static BROTLI_BOOL OpenOutputFile(const char* output_path, FILE** f,
453                                   BROTLI_BOOL force) {
454   int fd;
455   *f = NULL;
456   if (!output_path) {
457     *f = fdopen(MAKE_BINARY(STDOUT_FILENO), "wb");
458     return BROTLI_TRUE;
459   }
460   fd = open(output_path, O_CREAT | (force ? 0 : O_EXCL) | O_WRONLY | O_TRUNC,
461             S_IRUSR | S_IWUSR);
462   if (fd < 0) {
463     fprintf(stderr, "failed to open output file [%s]: %s\n",
464             PrintablePath(output_path), strerror(errno));
465     return BROTLI_FALSE;
466   }
467   *f = fdopen(fd, "wb");
468   if (!*f) {
469     fprintf(stderr, "failed to open output file [%s]: %s\n",
470             PrintablePath(output_path), strerror(errno));
471     return BROTLI_FALSE;
472   }
473   return BROTLI_TRUE;
474 }
475 
476 /* Copy file times and permissions.
477    TODO: this is a "best effort" implementation; honest cross-platform
478    fully featured implementation is way too hacky; add more hacks by request. */
CopyStat(const char * input_path,const char * output_path)479 static void CopyStat(const char* input_path, const char* output_path) {
480   struct stat statbuf;
481   struct utimbuf times;
482   int res;
483   if (input_path == 0 || output_path == 0) {
484     return;
485   }
486   if (stat(input_path, &statbuf) != 0) {
487     return;
488   }
489   times.actime = statbuf.st_atime;
490   times.modtime = statbuf.st_mtime;
491   utime(output_path, &times);
492   res = chmod(output_path, statbuf.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO));
493   if (res != 0) {
494     fprintf(stderr, "setting access bits failed for [%s]: %s\n",
495             PrintablePath(output_path), strerror(errno));
496   }
497   res = chown(output_path, (uid_t)-1, statbuf.st_gid);
498   if (res != 0) {
499     fprintf(stderr, "setting group failed for [%s]: %s\n",
500             PrintablePath(output_path), strerror(errno));
501   }
502   res = chown(output_path, statbuf.st_uid, (gid_t)-1);
503   if (res != 0) {
504     fprintf(stderr, "setting user failed for [%s]: %s\n",
505             PrintablePath(output_path), strerror(errno));
506   }
507 }
508 
NextFile(Context * context)509 static BROTLI_BOOL NextFile(Context* context) {
510   const char* arg;
511   size_t arg_len;
512 
513   /* Iterator points to last used arg; increment to search for the next one. */
514   context->iterator++;
515 
516   /* No input path; read from console. */
517   if (context->input_count == 0) {
518     if (context->iterator > 1) return BROTLI_FALSE;
519     context->current_input_path = NULL;
520     /* Either write to the specified path, or to console. */
521     context->current_output_path = context->output_path;
522     return BROTLI_TRUE;
523   }
524 
525   /* Skip option arguments. */
526   while (context->iterator == context->not_input_indices[context->ignore]) {
527     context->iterator++;
528     context->ignore++;
529   }
530 
531   /* All args are scanned already. */
532   if (context->iterator >= context->argc) return BROTLI_FALSE;
533 
534   /* Iterator now points to the input file name. */
535   arg = context->argv[context->iterator];
536   arg_len = strlen(arg);
537   /* Read from console. */
538   if (arg_len == 1 && arg[0] == '-') {
539     context->current_input_path = NULL;
540     context->current_output_path = context->output_path;
541     return BROTLI_TRUE;
542   }
543 
544   context->current_input_path = arg;
545   context->current_output_path = context->output_path;
546 
547   if (context->output_path) return BROTLI_TRUE;
548   if (context->write_to_stdout) return BROTLI_TRUE;
549 
550   strcpy(context->modified_path, arg);
551   context->current_output_path = context->modified_path;
552   /* If output is not specified, input path suffix should match. */
553   if (context->decompress) {
554     size_t suffix_len = strlen(context->suffix);
555     char* name = (char*)FileName(context->modified_path);
556     char* name_suffix;
557     size_t name_len = strlen(name);
558     if (name_len < suffix_len + 1) {
559       fprintf(stderr, "empty output file name for [%s] input file\n",
560               PrintablePath(arg));
561       context->iterator_error = BROTLI_TRUE;
562       return BROTLI_FALSE;
563     }
564     name_suffix = name + name_len - suffix_len;
565     if (strcmp(context->suffix, name_suffix) != 0) {
566       fprintf(stderr, "input file [%s] suffix mismatch\n",
567               PrintablePath(arg));
568       context->iterator_error = BROTLI_TRUE;
569       return BROTLI_FALSE;
570     }
571     name_suffix[0] = 0;
572     return BROTLI_TRUE;
573   } else {
574     strcpy(context->modified_path + arg_len, context->suffix);
575     return BROTLI_TRUE;
576   }
577 }
578 
OpenFiles(Context * context)579 static BROTLI_BOOL OpenFiles(Context* context) {
580   BROTLI_BOOL is_ok = OpenInputFile(context->current_input_path, &context->fin);
581   if (!context->test_integrity && is_ok) {
582     is_ok = OpenOutputFile(
583         context->current_output_path, &context->fout, context->force_overwrite);
584   }
585   return is_ok;
586 }
587 
CloseFiles(Context * context,BROTLI_BOOL success)588 static BROTLI_BOOL CloseFiles(Context* context, BROTLI_BOOL success) {
589   BROTLI_BOOL is_ok = BROTLI_TRUE;
590   if (!context->test_integrity && context->fout) {
591     if (!success && context->current_output_path) {
592       unlink(context->current_output_path);
593     }
594     if (fclose(context->fout) != 0) {
595       if (success) {
596         fprintf(stderr, "fclose failed [%s]: %s\n",
597                 PrintablePath(context->current_output_path), strerror(errno));
598       }
599       is_ok = BROTLI_FALSE;
600     }
601 
602     /* TOCTOU violation, but otherwise it is impossible to set file times. */
603     if (success && is_ok && context->copy_stat) {
604       CopyStat(context->current_input_path, context->current_output_path);
605     }
606   }
607 
608   if (context->fin) {
609     if (fclose(context->fin) != 0) {
610       if (is_ok) {
611         fprintf(stderr, "fclose failed [%s]: %s\n",
612                 PrintablePath(context->current_input_path), strerror(errno));
613       }
614       is_ok = BROTLI_FALSE;
615     }
616   }
617   if (success && context->junk_source && context->current_input_path) {
618     unlink(context->current_input_path);
619   }
620 
621   context->fin = NULL;
622   context->fout = NULL;
623 
624   return is_ok;
625 }
626 
627 static const size_t kFileBufferSize = 1 << 16;
628 
DecompressFile(Context * context,BrotliDecoderState * s)629 static BROTLI_BOOL DecompressFile(Context* context, BrotliDecoderState* s) {
630   size_t available_in = 0;
631   const uint8_t* next_in = NULL;
632   size_t available_out = kFileBufferSize;
633   uint8_t* next_out = context->output;
634   BrotliDecoderResult result = BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT;
635   for (;;) {
636     if (next_out != context->output) {
637       if (!context->test_integrity) {
638         size_t out_size = (size_t)(next_out - context->output);
639         fwrite(context->output, 1, out_size, context->fout);
640         if (ferror(context->fout)) {
641           fprintf(stderr, "failed to write output [%s]: %s\n",
642                   PrintablePath(context->current_output_path), strerror(errno));
643           return BROTLI_FALSE;
644         }
645       }
646       available_out = kFileBufferSize;
647       next_out = context->output;
648     }
649 
650     if (result == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT) {
651       if (feof(context->fin)) {
652         fprintf(stderr, "corrupt input [%s]\n",
653                 PrintablePath(context->current_input_path));
654         return BROTLI_FALSE;
655       }
656       available_in = fread(context->input, 1, kFileBufferSize, context->fin);
657       next_in = context->input;
658       if (ferror(context->fin)) {
659         fprintf(stderr, "failed to read input [%s]: %s\n",
660                 PrintablePath(context->current_input_path), strerror(errno));
661       return BROTLI_FALSE;
662       }
663     } else if (result == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) {
664       /* Nothing to do - output is already written. */
665     } else if (result == BROTLI_DECODER_RESULT_SUCCESS) {
666       if (available_in != 0 || !feof(context->fin)) {
667         fprintf(stderr, "corrupt input [%s]\n",
668                 PrintablePath(context->current_input_path));
669         return BROTLI_FALSE;
670       }
671       return BROTLI_TRUE;
672     } else {
673       fprintf(stderr, "corrupt input [%s]\n",
674               PrintablePath(context->current_input_path));
675       return BROTLI_FALSE;
676     }
677 
678     result = BrotliDecoderDecompressStream(
679         s, &available_in, &next_in, &available_out, &next_out, 0);
680   }
681 }
682 
DecompressFiles(Context * context)683 static BROTLI_BOOL DecompressFiles(Context* context) {
684   while (NextFile(context)) {
685     BROTLI_BOOL is_ok = BROTLI_TRUE;
686     BrotliDecoderState* s = BrotliDecoderCreateInstance(NULL, NULL, NULL);
687     if (!s) {
688       fprintf(stderr, "out of memory\n");
689       return BROTLI_FALSE;
690     }
691     is_ok = OpenFiles(context);
692     if (is_ok && !context->current_input_path &&
693         !context->force_overwrite && isatty(STDIN_FILENO)) {
694       fprintf(stderr, "Use -h help. Use -f to force input from a terminal.\n");
695       is_ok = BROTLI_FALSE;
696     }
697     if (is_ok) is_ok = DecompressFile(context, s);
698     BrotliDecoderDestroyInstance(s);
699     if (!CloseFiles(context, is_ok)) is_ok = BROTLI_FALSE;
700     if (!is_ok) return BROTLI_FALSE;
701   }
702   return BROTLI_TRUE;
703 }
704 
CompressFile(Context * context,BrotliEncoderState * s)705 static BROTLI_BOOL CompressFile(Context* context, BrotliEncoderState* s) {
706   size_t available_in = 0;
707   const uint8_t* next_in = NULL;
708   size_t available_out = kFileBufferSize;
709   uint8_t* next_out = context->output;
710   BROTLI_BOOL is_eof = BROTLI_FALSE;
711 
712   for (;;) {
713     if (available_in == 0 && !is_eof) {
714       available_in = fread(context->input, 1, kFileBufferSize, context->fin);
715       next_in = context->input;
716       if (ferror(context->fin)) {
717         fprintf(stderr, "failed to read input [%s]: %s\n",
718                 PrintablePath(context->current_input_path), strerror(errno));
719         return BROTLI_FALSE;
720       }
721       is_eof = feof(context->fin) ? BROTLI_TRUE : BROTLI_FALSE;
722     }
723 
724     if (!BrotliEncoderCompressStream(s,
725         is_eof ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS,
726         &available_in, &next_in, &available_out, &next_out, NULL)) {
727       /* Should detect OOM? */
728       fprintf(stderr, "failed to compress data [%s]\n",
729               PrintablePath(context->current_input_path));
730       return BROTLI_FALSE;
731     }
732 
733     if (available_out != kFileBufferSize) {
734       size_t out_size = kFileBufferSize - available_out;
735       fwrite(context->output, 1, out_size, context->fout);
736       if (ferror(context->fout)) {
737         fprintf(stderr, "failed to write output [%s]: %s\n",
738                 PrintablePath(context->current_output_path), strerror(errno));
739         return BROTLI_FALSE;
740       }
741       available_out = kFileBufferSize;
742       next_out = context->output;
743     }
744 
745     if (BrotliEncoderIsFinished(s)) return BROTLI_TRUE;
746   }
747 }
748 
CompressFiles(Context * context)749 static BROTLI_BOOL CompressFiles(Context* context) {
750   while (NextFile(context)) {
751     BROTLI_BOOL is_ok = BROTLI_TRUE;
752     BrotliEncoderState* s = BrotliEncoderCreateInstance(NULL, NULL, NULL);
753     if (!s) {
754       fprintf(stderr, "out of memory\n");
755       return BROTLI_FALSE;
756     }
757     BrotliEncoderSetParameter(s,
758         BROTLI_PARAM_QUALITY, (uint32_t)context->quality);
759     BrotliEncoderSetParameter(s,
760         BROTLI_PARAM_LGWIN, (uint32_t)context->lgwin);
761     is_ok = OpenFiles(context);
762     if (is_ok && !context->current_output_path &&
763         !context->force_overwrite && isatty(STDOUT_FILENO)) {
764       fprintf(stderr, "Use -h help. Use -f to force output to a terminal.\n");
765       is_ok = BROTLI_FALSE;
766     }
767     if (is_ok) is_ok = CompressFile(context, s);
768     BrotliEncoderDestroyInstance(s);
769     if (!CloseFiles(context, is_ok)) is_ok = BROTLI_FALSE;
770     if (!is_ok) return BROTLI_FALSE;
771   }
772   return BROTLI_TRUE;
773 }
774 
main(int argc,char ** argv)775 int main(int argc, char** argv) {
776   Command command;
777   Context context;
778   BROTLI_BOOL is_ok = BROTLI_TRUE;
779   int i;
780 
781   context.quality = 11;
782   context.lgwin = DEFAULT_LGWIN;
783   context.force_overwrite = BROTLI_FALSE;
784   context.junk_source = BROTLI_FALSE;
785   context.copy_stat = BROTLI_TRUE;
786   context.test_integrity = BROTLI_FALSE;
787   context.verbose = BROTLI_FALSE;
788   context.write_to_stdout = BROTLI_FALSE;
789   context.decompress = BROTLI_FALSE;
790   context.output_path = NULL;
791   context.suffix = DEFAULT_SUFFIX;
792   for (i = 0; i < MAX_OPTIONS; ++i) context.not_input_indices[i] = 0;
793   context.longest_path_len = 1;
794   context.input_count = 0;
795 
796   context.argc = argc;
797   context.argv = argv;
798   context.modified_path = NULL;
799   context.iterator = 0;
800   context.ignore = 0;
801   context.iterator_error = BROTLI_FALSE;
802   context.buffer = NULL;
803   context.current_input_path = NULL;
804   context.current_output_path = NULL;
805   context.fin = NULL;
806   context.fout = NULL;
807 
808   command = ParseParams(&context);
809 
810   if (command == COMMAND_COMPRESS || command == COMMAND_DECOMPRESS ||
811       command == COMMAND_TEST_INTEGRITY) {
812     if (is_ok) {
813       size_t modified_path_len =
814           context.longest_path_len + strlen(context.suffix) + 1;
815       context.modified_path = (char*)malloc(modified_path_len);
816       context.buffer = (uint8_t*)malloc(kFileBufferSize * 2);
817       if (!context.modified_path || !context.buffer) {
818         fprintf(stderr, "out of memory\n");
819         is_ok = BROTLI_FALSE;
820       } else {
821         context.input = context.buffer;
822         context.output = context.buffer + kFileBufferSize;
823       }
824     }
825   }
826 
827   if (!is_ok) command = COMMAND_NOOP;
828 
829   switch (command) {
830     case COMMAND_NOOP:
831       break;
832 
833     case COMMAND_VERSION:
834       PrintVersion();
835       break;
836 
837     case COMMAND_COMPRESS:
838       is_ok = CompressFiles(&context);
839       break;
840 
841     case COMMAND_DECOMPRESS:
842     case COMMAND_TEST_INTEGRITY:
843       is_ok = DecompressFiles(&context);
844       break;
845 
846     case COMMAND_HELP:
847     case COMMAND_INVALID:
848     default:
849       PrintHelp(FileName(argv[0]));
850       is_ok = (command == COMMAND_HELP);
851       break;
852   }
853 
854   if (context.iterator_error) is_ok = BROTLI_FALSE;
855 
856   free(context.modified_path);
857   free(context.buffer);
858 
859   if (!is_ok) exit(1);
860   return 0;
861 }
862