fix #98 #99 #100 #101 #102 #103
[m6w6/ext-http] / src / php_http_encoding_brotli.c
1 /*
2 +--------------------------------------------------------------------+
3 | PECL :: http |
4 +--------------------------------------------------------------------+
5 | Redistribution and use in source and binary forms, with or without |
6 | modification, are permitted provided that the conditions mentioned |
7 | in the accompanying LICENSE file are met. |
8 +--------------------------------------------------------------------+
9 | Copyright (c) 2004-2014, Michael Wallner <mike@php.net> |
10 +--------------------------------------------------------------------+
11 */
12
13 #include "php_http_api.h"
14
15 #if PHP_HTTP_HAVE_LIBBROTLI
16
17 #define PHP_HTTP_DEBROTLI_ROUNDS 100
18 #define PHP_HTTP_ENBROTLI_ROUNDS 100
19
20 #define PHP_HTTP_ENBROTLI_BUFFER_SIZE_GUESS(S) \
21 BrotliEncoderMaxCompressedSize(S)
22 #define PHP_HTTP_DEBROTLI_BUFFER_SIZE_ALIGN(S) \
23 ((S) += (S) >> 3)
24
25 #define PHP_HTTP_ENBROTLI_FLUSH_FLAG(flags) \
26 PHP_HTTP_ENCODING_STREAM_FLUSH_FLAG((flags), BROTLI_OPERATION_FLUSH, BROTLI_OPERATION_FLUSH, BROTLI_OPERATION_PROCESS)
27
28 #define PHP_HTTP_ENBROTLI_BUFFER_SIZE 0x2000
29 #define PHP_HTTP_DEBROTLI_BUFFER_SIZE 0x1000
30
31 #define PHP_HTTP_ENBROTLI_LEVEL_SET(flags, level) \
32 level = (((flags) & 0xf) ?: PHP_HTTP_ENBROTLI_LEVEL_DEF)
33 #define PHP_HTTP_ENBROTLI_WBITS_SET(flags, wbits) \
34 wbits = ((((flags) >> 4) & 0xff) ?: (PHP_HTTP_ENBROTLI_WBITS_DEF >> 4))
35 #define PHP_HTTP_ENBROTLI_MODE_SET(flags, mode) \
36 mode = (((flags) >> 12) & 0xf)
37
38
39 static php_http_encoding_stream_t *enbrotli_init(php_http_encoding_stream_t *s)
40 {
41 BrotliEncoderState *br;
42 int q, win, mode;
43
44 PHP_HTTP_ENBROTLI_LEVEL_SET(s->flags, q);
45 PHP_HTTP_ENBROTLI_WBITS_SET(s->flags, win);
46 PHP_HTTP_ENBROTLI_MODE_SET(s->flags, mode);
47
48 br = BrotliEncoderCreateInstance(NULL, NULL, NULL);
49 if (EXPECTED(br)) {
50 BrotliEncoderSetParameter(br, BROTLI_PARAM_QUALITY, q);
51 BrotliEncoderSetParameter(br, BROTLI_PARAM_LGWIN, win);
52 BrotliEncoderSetParameter(br, BROTLI_PARAM_MODE, mode);
53
54 s->ctx = br;
55 return s;
56 }
57
58 php_error_docref(NULL, E_WARNING, "Failed to initialize brotli encoding stream");
59 return NULL;
60 }
61
62 static ZEND_RESULT_CODE enbrotli_update(php_http_encoding_stream_t *s, const char *data, size_t data_len, char **encoded, size_t *encoded_len)
63 {
64 php_http_buffer_t out;
65 const unsigned char *in_ptr;
66 size_t in_len;
67 BROTLI_BOOL rc;
68
69 php_http_buffer_init_ex(&out, PHP_HTTP_ENBROTLI_BUFFER_SIZE_GUESS(data_len), 0);
70
71 in_len = data_len;
72 in_ptr = (const unsigned char *) data;
73
74 while (in_len) {
75 size_t out_len = 0;
76
77 rc = BrotliEncoderCompressStream(s->ctx, PHP_HTTP_ENBROTLI_FLUSH_FLAG(s->flags), &in_len, &in_ptr, &out_len, NULL, NULL);
78
79 if (!rc) {
80 break;
81 }
82
83 if (BrotliEncoderHasMoreOutput(s->ctx)) {
84 const char *out_str = (const char *) BrotliEncoderTakeOutput(s->ctx, &out_len);
85
86 php_http_buffer_append(&out, out_str, out_len);
87 }
88 }
89
90 if (rc) {
91 if (out.used) {
92 php_http_buffer_shrink(&out);
93 php_http_buffer_fix(&out);
94 *encoded = out.data;
95 *encoded_len = out.used;
96 } else {
97 *encoded = NULL;
98 *encoded_len = 0;
99 php_http_buffer_dtor(&out);
100 }
101 return SUCCESS;
102 }
103
104 php_http_buffer_dtor(&out);
105
106 *encoded = NULL;
107 *encoded_len = 0;
108
109 php_error_docref(NULL, E_WARNING, "Failed to update brotli encoding stream");
110 return FAILURE;
111 }
112
113 static inline ZEND_RESULT_CODE enbrotli_flush_ex(php_http_encoding_stream_t *s, BrotliEncoderOperation op, char **encoded, size_t *encoded_len)
114 {
115 php_http_buffer_t out;
116 BROTLI_BOOL rc;
117 int round = 0;
118
119 php_http_buffer_init_ex(&out, PHP_HTTP_ENBROTLI_BUFFER_SIZE, 0);
120
121 do {
122 const unsigned char *empty = NULL;
123 size_t unused = 0, out_len = 0;
124
125 rc = BrotliEncoderCompressStream(s->ctx, op, &unused, &empty, &out_len, NULL, NULL);
126
127 if (!rc) {
128 break;
129 }
130
131 if (BrotliEncoderHasMoreOutput(s->ctx)) {
132 const char *out_str = (const char *) BrotliEncoderTakeOutput(s->ctx, &out_len);
133
134 php_http_buffer_append(&out, out_str, out_len);
135 } else {
136 if (out.used) {
137 php_http_buffer_shrink(&out);
138 php_http_buffer_fix(&out);
139
140 *encoded = out.data;
141 *encoded_len = out.used;
142 } else {
143 *encoded = NULL;
144 *encoded_len = 0;
145 }
146 return SUCCESS;
147 }
148 } while (++round < PHP_HTTP_ENBROTLI_ROUNDS);
149
150 php_http_buffer_dtor(&out);
151
152 *encoded = NULL;
153 *encoded_len = 0;
154
155 php_error_docref(NULL, E_WARNING, "Failed to flush brotli encoding stream");
156 return FAILURE;
157 }
158
159 static ZEND_RESULT_CODE enbrotli_flush(php_http_encoding_stream_t *s, char **encoded, size_t *encoded_len)
160 {
161 return enbrotli_flush_ex(s, BROTLI_OPERATION_FLUSH, encoded, encoded_len);
162 }
163
164 static ZEND_RESULT_CODE enbrotli_finish(php_http_encoding_stream_t *s, char **encoded, size_t *encoded_len)
165 {
166 ZEND_RESULT_CODE rc;
167
168 do {
169 rc = enbrotli_flush_ex(s, BROTLI_OPERATION_FINISH, encoded, encoded_len);
170 } while (SUCCESS == rc && !BrotliEncoderIsFinished(s->ctx));
171
172 return rc;
173 }
174
175 static zend_bool enbrotli_done(php_http_encoding_stream_t *s)
176 {
177 return !(s->flags & PHP_HTTP_ENCODING_STREAM_DIRTY)
178 || BrotliEncoderIsFinished(s->ctx);
179 }
180
181 static void enbrotli_dtor(php_http_encoding_stream_t *s)
182 {
183 if (s->ctx) {
184 BrotliEncoderDestroyInstance(s->ctx);
185 s->ctx = NULL;
186 }
187 }
188
189 static php_http_encoding_stream_t *debrotli_init(php_http_encoding_stream_t *s)
190 {
191 BrotliDecoderState *br;
192
193 br = BrotliDecoderCreateInstance(NULL, NULL, NULL);
194 if (br) {
195 s->ctx = br;
196 return s;
197 }
198
199 php_error_docref(NULL, E_WARNING, "Failed to initialize brotli decoding stream");
200 return NULL;
201 }
202
203 static ZEND_RESULT_CODE debrotli_update(php_http_encoding_stream_t *s, const char *encoded, size_t encoded_len, char **decoded, size_t *decoded_len)
204 {
205 php_http_buffer_t out;
206 BrotliDecoderResult rc;
207 const unsigned char *in_ptr;
208 size_t in_len;
209
210 in_ptr = (const unsigned char *) encoded;
211 in_len = encoded_len;
212
213 php_http_buffer_init_ex(&out, encoded_len, PHP_HTTP_BUFFER_INIT_PREALLOC);
214
215 while (in_len) {
216 size_t out_len = 0;
217
218 rc = BrotliDecoderDecompressStream(s->ctx, &in_len, &in_ptr, &out_len, NULL, NULL);
219
220 if (BROTLI_DECODER_RESULT_ERROR == rc) {
221 break;
222 }
223
224 if (BrotliDecoderHasMoreOutput(s->ctx)) {
225 const char *out_str = (const char *) BrotliDecoderTakeOutput(s->ctx, &out_len);
226
227 php_http_buffer_append(&out, out_str, out_len);
228 }
229 }
230
231 if (BROTLI_DECODER_RESULT_ERROR != rc) {
232 if (out.used) {
233 php_http_buffer_shrink(&out);
234 php_http_buffer_fix(&out);
235 *decoded = out.data;
236 *decoded_len = out.used;
237 } else {
238 php_http_buffer_dtor(&out);
239 *decoded = NULL;
240 *decoded_len = 0;
241 }
242 return SUCCESS;
243 }
244
245 php_http_buffer_dtor(&out);
246
247 php_error_docref(NULL, E_WARNING, "Could not brotli decode data: %s", BrotliDecoderErrorString(BrotliDecoderGetErrorCode(s->ctx)));
248 return FAILURE;
249 }
250
251 static zend_bool debrotli_done(php_http_encoding_stream_t *s)
252 {
253 return !BrotliDecoderIsUsed(s->ctx) || BrotliDecoderIsFinished(s->ctx);
254 }
255
256 static void debrotli_dtor(php_http_encoding_stream_t *s)
257 {
258 if (s->ctx) {
259 BrotliDecoderDestroyInstance(s->ctx);
260 s->ctx = NULL;
261 }
262 }
263
264 static php_http_encoding_stream_ops_t php_http_encoding_enbrotli_ops = {
265 enbrotli_init,
266 NULL,
267 enbrotli_update,
268 enbrotli_flush,
269 enbrotli_done,
270 enbrotli_finish,
271 enbrotli_dtor
272 };
273
274 php_http_encoding_stream_ops_t *php_http_encoding_stream_get_enbrotli_ops(void)
275 {
276 return &php_http_encoding_enbrotli_ops;
277 }
278
279 static php_http_encoding_stream_ops_t php_http_encoding_debrotli_ops = {
280 debrotli_init,
281 NULL,
282 debrotli_update,
283 NULL,
284 debrotli_done,
285 NULL,
286 debrotli_dtor
287 };
288
289 php_http_encoding_stream_ops_t *php_http_encoding_stream_get_debrotli_ops(void)
290 {
291 return &php_http_encoding_debrotli_ops;
292 }
293
294 ZEND_RESULT_CODE php_http_encoding_enbrotli(int flags, const char *data, size_t data_len, char **encoded, size_t *encoded_len)
295 {
296 BROTLI_BOOL rc;
297 int q, win, mode;
298
299 *encoded_len = PHP_HTTP_ENBROTLI_BUFFER_SIZE_GUESS(data_len);
300 *encoded = emalloc(*encoded_len + 1);
301
302 PHP_HTTP_ENBROTLI_LEVEL_SET(flags, q);
303 PHP_HTTP_ENBROTLI_WBITS_SET(flags, win);
304 PHP_HTTP_ENBROTLI_MODE_SET(flags, mode);
305
306 rc = BrotliEncoderCompress(q, win, mode, data_len, (const unsigned char *) data, encoded_len, (unsigned char *) *encoded);
307 if (rc) {
308 return SUCCESS;
309 }
310
311 PTR_SET(*encoded, NULL);
312 *encoded_len = 0;
313
314 php_error_docref(NULL, E_WARNING, "Could not brotli encode data");
315 return FAILURE;
316 }
317
318 ZEND_RESULT_CODE php_http_encoding_debrotli(const char *encoded, size_t encoded_len, char **decoded, size_t *decoded_len)
319 {
320 php_http_encoding_stream_t s = {0};
321 ZEND_RESULT_CODE rc = FAILURE;
322
323 if (debrotli_init(&s)) {
324 rc = debrotli_update(&s, encoded, encoded_len, decoded, decoded_len);
325 debrotli_dtor(&s);
326 }
327
328 return rc;
329 }
330
331 static zend_class_entry *php_http_enbrotli_stream_class_entry;
332 zend_class_entry *php_http_get_enbrotli_stream_class_entry(void)
333 {
334 return php_http_enbrotli_stream_class_entry;
335 }
336
337 static zend_class_entry *php_http_debrotli_stream_class_entry;
338 zend_class_entry *php_http_get_debrotli_stream_class_entry(void)
339 {
340 return php_http_debrotli_stream_class_entry;
341 }
342
343 ZEND_BEGIN_ARG_INFO_EX(ai_HttpEnbrotliStream_encode, 0, 0, 1)
344 ZEND_ARG_INFO(0, data)
345 ZEND_ARG_INFO(0, flags)
346 ZEND_END_ARG_INFO();
347 static PHP_METHOD(HttpEnbrotliStream, encode)
348 {
349 char *str;
350 size_t len;
351 zend_long flags = PHP_HTTP_ENBROTLI_MODE_GENERIC | PHP_HTTP_ENBROTLI_WBITS_DEF | PHP_HTTP_ENBROTLI_LEVEL_DEF;
352
353 if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS(), "s|l", &str, &len, &flags)) {
354 char *enc_str = NULL;
355 size_t enc_len;
356
357 if (SUCCESS == php_http_encoding_enbrotli(flags, str, len, &enc_str, &enc_len)) {
358 if (enc_str) {
359 RETURN_STR(php_http_cs2zs(enc_str, enc_len));
360 } else {
361 RETURN_EMPTY_STRING();
362 }
363 }
364 }
365 RETURN_FALSE;
366 }
367 static zend_function_entry php_http_enbrotli_stream_methods[] = {
368 PHP_ME(HttpEnbrotliStream, encode, ai_HttpEnbrotliStream_encode, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
369 EMPTY_FUNCTION_ENTRY
370 };
371
372 ZEND_BEGIN_ARG_INFO_EX(ai_HttpDebrotliStream_decode, 0, 0, 1)
373 ZEND_ARG_INFO(0, data)
374 ZEND_END_ARG_INFO();
375 static PHP_METHOD(HttpDebrotliStream, decode)
376 {
377 char *str;
378 size_t len;
379
380 if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS(), "s", &str, &len)) {
381 char *enc_str = NULL;
382 size_t enc_len;
383
384 if (SUCCESS == php_http_encoding_debrotli(str, len, &enc_str, &enc_len)) {
385 if (enc_str) {
386 RETURN_STR(php_http_cs2zs(enc_str, enc_len));
387 } else {
388 RETURN_EMPTY_STRING();
389 }
390 }
391 }
392 RETURN_FALSE;
393 }
394
395 static zend_function_entry php_http_debrotli_stream_methods[] = {
396 PHP_ME(HttpDebrotliStream, decode, ai_HttpDebrotliStream_decode, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
397 EMPTY_FUNCTION_ENTRY
398 };
399
400 /* POS */
401 const void *BrotliGetDictionary();
402 const void *(*php_http_brotli_get_dictionary)();
403
404 PHP_MINIT_FUNCTION(http_encoding_brotli)
405 {
406 zend_class_entry ce;
407
408 /* force link to libbrotlicommon, because their libraries don't */
409 php_http_brotli_get_dictionary = BrotliGetDictionary();
410
411 memset(&ce, 0, sizeof(ce));
412 INIT_NS_CLASS_ENTRY(ce, "http\\Encoding\\Stream", "Enbrotli", php_http_enbrotli_stream_methods);
413 php_http_enbrotli_stream_class_entry = zend_register_internal_class_ex(&ce, php_http_get_encoding_stream_class_entry());
414 php_http_enbrotli_stream_class_entry->create_object = php_http_encoding_stream_object_new;
415
416 zend_declare_class_constant_long(php_http_enbrotli_stream_class_entry, ZEND_STRL("LEVEL_MIN"), PHP_HTTP_ENBROTLI_LEVEL_MIN);
417 zend_declare_class_constant_long(php_http_enbrotli_stream_class_entry, ZEND_STRL("LEVEL_DEF"), PHP_HTTP_ENBROTLI_LEVEL_DEF);
418 zend_declare_class_constant_long(php_http_enbrotli_stream_class_entry, ZEND_STRL("LEVEL_MAX"), PHP_HTTP_ENBROTLI_LEVEL_MAX);
419 zend_declare_class_constant_long(php_http_enbrotli_stream_class_entry, ZEND_STRL("WBITS_MIN"), PHP_HTTP_ENBROTLI_WBITS_MIN);
420 zend_declare_class_constant_long(php_http_enbrotli_stream_class_entry, ZEND_STRL("WBITS_DEF"), PHP_HTTP_ENBROTLI_WBITS_DEF);
421 zend_declare_class_constant_long(php_http_enbrotli_stream_class_entry, ZEND_STRL("WBITS_MAX"), PHP_HTTP_ENBROTLI_WBITS_MAX);
422 zend_declare_class_constant_long(php_http_enbrotli_stream_class_entry, ZEND_STRL("MODE_GENERIC"), PHP_HTTP_ENBROTLI_MODE_GENERIC);
423 zend_declare_class_constant_long(php_http_enbrotli_stream_class_entry, ZEND_STRL("MODE_TEXT"), PHP_HTTP_ENBROTLI_MODE_TEXT);
424 zend_declare_class_constant_long(php_http_enbrotli_stream_class_entry, ZEND_STRL("MODE_FONT"), PHP_HTTP_ENBROTLI_MODE_FONT);
425
426 memset(&ce, 0, sizeof(ce));
427 INIT_NS_CLASS_ENTRY(ce, "http\\Encoding\\Stream", "Debrotli", php_http_debrotli_stream_methods);
428 php_http_debrotli_stream_class_entry = zend_register_internal_class_ex(&ce, php_http_get_encoding_stream_class_entry());
429 php_http_debrotli_stream_class_entry->create_object = php_http_encoding_stream_object_new;
430
431 return SUCCESS;
432 }
433
434 #endif
435
436 /*
437 * Local variables:
438 * tab-width: 4
439 * c-basic-offset: 4
440 * End:
441 * vim600: noet sw=4 ts=4 fdm=marker
442 * vim<600: noet sw=4 ts=4
443 */
444