]> Sergey Matveev's repositories - tofuproxy.git/blob - cmd/unzstd/unzstd.c
4815251cf318cac8efd082c31fea86aebd3907a6
[tofuproxy.git] / cmd / unzstd / unzstd.c
1 /*
2 tofuproxy -- flexible HTTP proxy, TLS terminator, X.509 certificates
3              manager, WARC/Gemini browser
4 Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
5
6 This program is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, version 3 of the License.
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
16 along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 // https://iipc.github.io/warc-specifications/specifications/warc-zstd/
20
21 #include <errno.h>
22 #include <inttypes.h>
23 #include <stdbool.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <sys/endian.h>
28
29 #include <zstd.h>
30
31 int
32 main(int argc, char **argv)
33 {
34     ZSTD_DCtx *ctx = ZSTD_createDCtx();
35     if (ctx == NULL) {
36         fputs("can not initialize ZSTD_DCtx\n", stderr);
37         return 1;
38     };
39     FILE *fdOff            = fdopen(3, "wb");
40     int rc                 = EXIT_FAILURE;
41     uint8_t *bufIn         = NULL;
42     uint8_t *bufOut        = NULL;
43     const size_t bufInSize = ZSTD_DStreamInSize();
44     bufIn                  = malloc(bufInSize);
45     if (bufIn == NULL) {
46         fputs("no memory\n", stderr);
47         goto Exit;
48     };
49     const size_t bufOutSize = ZSTD_DStreamOutSize();
50     bufOut                  = malloc(bufOutSize);
51     if (bufOut == NULL) {
52         fputs("no memory\n", stderr);
53         goto Exit;
54     };
55
56     unsigned long long bufSize = 0;
57
58     ZSTD_inBuffer bIn   = {bufIn, 0, 0};
59     ZSTD_outBuffer bOut = {bufOut, 0, 0};
60
61     bool isEmpty      = true;
62     bool lastBlock    = false;
63     size_t n          = 0;
64     size_t written    = 0;
65     size_t offset     = 0;
66     size_t offsetPrev = 0;
67     size_t zCode      = 0;
68 ReadAgain:
69     for (;;) {
70         n = fread(bufIn, 1, bufInSize, stdin);
71         if (n != bufInSize) {
72             if (feof(stdin)) {
73                 lastBlock = true;
74             } else {
75                 perror("can not fread(FILE)");
76                 goto Exit;
77             };
78         };
79         if (n >= 8 && le32dec(bufIn) == 0x184D2A5D) {
80             // dictionary frame
81             size_t dictSize = (size_t)le32dec(bufIn + 4);
82             uint8_t *dict   = malloc(dictSize);
83             if (dict == NULL) {
84                 fprintf(stderr, "insufficient memory for dictionary: %zu\n", dictSize);
85                 goto Exit;
86             };
87             const size_t alreadyRead = n - 8;
88             memcpy(dict, bufIn + 8, alreadyRead);
89             errno = 0;
90             n     = fread(dict + alreadyRead, 1, dictSize - alreadyRead, stdin);
91             if (n != dictSize - alreadyRead) {
92                 perror("can not read dictionary data");
93                 free(dict);
94                 goto Exit;
95             };
96             offset     = dictSize + 8;
97             offsetPrev = offset;
98             if (fdOff != NULL) {
99                 fprintf(fdOff, "%zu\t0\n", offset);
100             };
101             uint32_t hdr = le32dec(dict);
102             switch (hdr) {
103             case ZSTD_MAGIC_DICTIONARY:
104                 zCode = ZSTD_DCtx_loadDictionary(ctx, dict, dictSize);
105                 free(dict);
106                 if ((zCode != 0) && (ZSTD_isError(zCode))) {
107                     fprintf(
108                         stderr,
109                         "can not load dictionary: %s\n",
110                         ZSTD_getErrorName(zCode));
111                     goto Exit;
112                 };
113                 goto ReadAgain;
114                 break;
115             case ZSTD_MAGICNUMBER:
116                 bufSize = ZSTD_getFrameContentSize(dict, dictSize);
117                 switch (bufSize) {
118                 case ZSTD_CONTENTSIZE_UNKNOWN:
119                 case ZSTD_CONTENTSIZE_ERROR:
120                     fprintf(stderr, "can not determine dictionary's size\n");
121                     free(dict);
122                     goto Exit;
123                 };
124                 uint8_t *buf = malloc(bufSize);
125                 if (buf == NULL) {
126                     fprintf(
127                         stderr, "insufficient memory for dictionary: %llu\n", bufSize);
128                     free(dict);
129                     goto Exit;
130                 };
131                 zCode = ZSTD_decompress(buf, bufSize, dict, dictSize);
132                 free(dict);
133                 if (ZSTD_isError(zCode)) {
134                     fprintf(
135                         stderr,
136                         "can not decompress dictionary: %s\n",
137                         ZSTD_getErrorName(zCode));
138                     free(buf);
139                     goto Exit;
140                 };
141                 zCode = ZSTD_DCtx_loadDictionary(ctx, buf, zCode);
142                 free(buf);
143                 if ((zCode != 0) && (ZSTD_isError(zCode))) {
144                     fprintf(
145                         stderr,
146                         "can not load dictionary: %s\n",
147                         ZSTD_getErrorName(zCode));
148                     goto Exit;
149                 };
150                 goto ReadAgain;
151                 break;
152             default:
153                 fprintf(stderr, "unknown dictionary header\n");
154                 free(dict);
155                 goto Exit;
156             };
157         };
158         isEmpty  = false;
159         bIn.size = n;
160         bIn.pos  = 0;
161         while (bIn.pos < bIn.size) {
162             bOut.size = bufOutSize;
163             bOut.pos  = 0;
164             zCode     = ZSTD_decompressStream(ctx, &bOut, &bIn);
165             if ((zCode != 0) && (ZSTD_isError(zCode))) {
166                 fprintf(stderr, "can not decompress: %s\n", ZSTD_getErrorName(zCode));
167                 goto Exit;
168             };
169             n = fwrite(bufOut, 1, bOut.pos, stdout);
170             if (n != bOut.pos) {
171                 perror("can not fwrite(stdout)");
172                 goto Exit;
173             };
174             written += n;
175             if (zCode == 0) {
176                 offset += bIn.pos;
177                 if (fdOff != NULL) {
178                     fprintf(fdOff, "%zu\t%zu\n", offset - offsetPrev, written);
179                 };
180                 offsetPrev = offset + bIn.pos;
181                 written    = 0;
182             };
183         };
184         if (lastBlock) {
185             break;
186         };
187         offset += bIn.pos;
188     };
189
190     if (isEmpty) {
191         fputs("empty input\n", stderr);
192         goto Exit;
193     };
194     if (zCode != 0) {
195         fprintf(stderr, "unfinished decompression: %s\n", ZSTD_getErrorName(zCode));
196         goto Exit;
197     };
198     rc = EXIT_SUCCESS;
199
200 Exit:
201     if (bufOut != NULL) {
202         free(bufOut);
203     };
204     if (bufIn != NULL) {
205         free(bufIn);
206     };
207     ZSTD_freeDCtx(ctx);
208     if ((fdOff != NULL) && (fclose(fdOff) != 0)) {
209         perror("can not fclose(4)");
210         return EXIT_FAILURE;
211     };
212     return rc;
213 };