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