1 /*
2   Copyright (c) 2015 kjdev
3 
4   Permission is hereby granted, free of charge, to any person obtaining
5   a copy of this software and associated documentation files (the
6   'Software'), to deal in the Software without restriction, including
7   without limitation the rights to use, copy, modify, merge, publish,
8   distribute, sublicense, and/or sell copies of the Software, and to
9   permit persons to whom the Software is furnished to do so, subject to
10   the following conditions:
11 
12   The above copyright notice and this permission notice shall be
13   included in all copies or substantial portions of the Software.
14 
15   THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
16   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18   IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19   CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20   TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21   SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 */
23 
24 #ifdef HAVE_CONFIG_H
25 #include "config.h"
26 #endif
27 
28 #include <php.h>
29 #include <php_ini.h>
30 #include <ext/standard/info.h>
31 #if ZEND_MODULE_API_NO >= 20141001
32 #include <ext/standard/php_smart_string.h>
33 #else
34 #include <ext/standard/php_smart_str.h>
35 #endif
36 #include "php_zstd.h"
37 
38 /* zstd */
39 #ifdef HAVE_SYS_TYPES_H
40 #include <sys/types.h>
41 #endif
42 #ifdef HAVE_STDINT_H
43 #include <stdint.h>
44 #endif
45 #include "zstd.h"
46 
47 #ifndef ZSTD_CLEVEL_DEFAULT
48 #define ZSTD_CLEVEL_DEFAULT 3
49 #endif
50 
51 #define FRAME_HEADER_SIZE 5
52 #define BLOCK_HEADER_SIZE 3
53 #define MAX_HEADER_SIZE FRAME_HEADER_SIZE+3
54 
55 #define DEFAULT_COMPRESS_LEVEL 3
56 
57 ZEND_BEGIN_ARG_INFO_EX(arginfo_zstd_compress, 0, 0, 1)
58     ZEND_ARG_INFO(0, data)
59     ZEND_ARG_INFO(0, level)
ZEND_END_ARG_INFO()60 ZEND_END_ARG_INFO()
61 
62 ZEND_BEGIN_ARG_INFO_EX(arginfo_zstd_uncompress, 0, 0, 1)
63     ZEND_ARG_INFO(0, data)
64 ZEND_END_ARG_INFO()
65 
66 ZEND_BEGIN_ARG_INFO_EX(arginfo_zstd_compress_dict, 0, 0, 1)
67     ZEND_ARG_INFO(0, data)
68     ZEND_ARG_INFO(0, dictBuffer)
69 ZEND_END_ARG_INFO()
70 
71 ZEND_BEGIN_ARG_INFO_EX(arginfo_zstd_uncompress_dict, 0, 0, 1)
72     ZEND_ARG_INFO(0, data)
73     ZEND_ARG_INFO(0, dictBuffer)
74 ZEND_END_ARG_INFO()
75 
76 ZEND_FUNCTION(zstd_compress)
77 {
78     zval *data;
79     char *output;
80     size_t size, result;
81     long level = DEFAULT_COMPRESS_LEVEL;
82     uint16_t maxLevel = (uint16_t)ZSTD_maxCLevel();
83 
84     if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
85                               "z|l", &data, &level) == FAILURE) {
86         RETURN_FALSE;
87     }
88 
89     if (Z_TYPE_P(data) != IS_STRING) {
90         zend_error(E_WARNING, "zstd_compress: expects parameter to be string.");
91         RETURN_FALSE;
92     }
93 
94 
95 #if ZSTD_VERSION_NUMBER >= 10304
96     if (level > maxLevel) {
97       zend_error(E_WARNING, "zstd_compress: compression level (%ld)"
98                  " must be within 1..%d or smaller then 0", level, maxLevel);
99 #else
100     if (level > maxLevel || level < 0) {
101       zend_error(E_WARNING, "zstd_compress: compression level (%ld)"
102                  " must be within 1..%d", level, maxLevel);
103 #endif
104       RETURN_FALSE;
105     } else if (level == 0) {
106 #if ZEND_MODULE_API_NO >= 20141001
107       RETURN_STRINGL(Z_STRVAL_P(data), Z_STRLEN_P(data));
108 #else
109       RETURN_STRINGL(Z_STRVAL_P(data), Z_STRLEN_P(data), 1);
110 #endif
111     }
112 
113     size = ZSTD_compressBound(Z_STRLEN_P(data));
114     output = (char *)emalloc(size + 1);
115     if (!output) {
116         zend_error(E_WARNING, "zstd_compress: memory error");
117         RETURN_FALSE;
118     }
119 
120     result = ZSTD_compress(output, size, Z_STRVAL_P(data), Z_STRLEN_P(data),
121                            level);
122 
123     if (ZSTD_isError(result)) {
124         RETVAL_FALSE;
125     } else if (result <= 0) {
126         RETVAL_FALSE;
127     } else {
128 #if ZEND_MODULE_API_NO >= 20141001
129         RETVAL_STRINGL(output, result);
130 #else
131         RETVAL_STRINGL(output, result, 1);
132 #endif
133     }
134 
135     efree(output);
136 }
137 
138 ZEND_FUNCTION(zstd_uncompress)
139 {
140     zval *data;
141     uint64_t size;
142     size_t result;
143     void *output;
144     uint8_t streaming = 0;
145 
146     if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
147                               "z", &data) == FAILURE) {
148         RETURN_FALSE;
149     }
150 
151     if (Z_TYPE_P(data) != IS_STRING) {
152         zend_error(E_WARNING,
153                    "zstd_uncompress: expects parameter to be string.");
154         RETURN_FALSE;
155     }
156 
157     size = ZSTD_getFrameContentSize(Z_STRVAL_P(data), Z_STRLEN_P(data));
158     if (size == ZSTD_CONTENTSIZE_ERROR) {
159         zend_error(E_WARNING, "zstd_uncompress: it was not compressed by zstd");
160         RETURN_FALSE;
161     } else if (size == ZSTD_CONTENTSIZE_UNKNOWN) {
162         streaming = 1;
163         size = ZSTD_DStreamOutSize();
164     }
165 
166     output = emalloc(size);
167     if (!output) {
168         zend_error(E_WARNING, "zstd_uncompress: memory error");
169         RETURN_FALSE;
170     }
171 
172     if (!streaming) {
173         result = ZSTD_decompress(output, size,
174                                  Z_STRVAL_P(data), Z_STRLEN_P(data));
175     } else {
176         ZSTD_DStream *stream;
177         ZSTD_inBuffer in = { NULL, 0, 0 };
178         ZSTD_outBuffer out = { NULL, 0, 0 };
179 
180         stream = ZSTD_createDStream();
181         if (stream == NULL) {
182             efree(output);
183             zend_error(E_WARNING, "zstd_uncompress: can not create stream");
184             RETURN_FALSE;
185         }
186 
187         result = ZSTD_initDStream(stream);
188         if (ZSTD_isError(result)) {
189             efree(output);
190             ZSTD_freeDStream(stream);
191             zend_error(E_WARNING, "zstd_uncompress: can not init stream");
192             RETURN_FALSE;
193         }
194 
195         in.src = Z_STRVAL_P(data);
196         in.size = Z_STRLEN_P(data);
197         in.pos = 0;
198 
199         out.dst = output;
200         out.size = size;
201         out.pos = 0;
202 
203         while (in.pos < in.size) {
204             if (out.pos == out.size) {
205                 out.size += size;
206                 output = erealloc(output, out.size);
207                 out.dst = output;
208             }
209 
210             result = ZSTD_decompressStream(stream, &out, &in);
211             if (ZSTD_isError(result)) {
212                 efree(output);
213                 ZSTD_freeDStream(stream);
214                 zend_error(E_WARNING,
215                            "zstd_uncompress: can not decompress stream");
216                 RETURN_FALSE;
217             }
218 
219             if (result == 0) {
220                 break;
221             }
222         }
223 
224         result = out.pos;
225 
226         ZSTD_freeDStream(stream);
227     }
228 
229     if (ZSTD_isError(result)) {
230         RETVAL_FALSE;
231     } else if (result < 0) {
232         RETVAL_FALSE;
233     } else {
234 #if ZEND_MODULE_API_NO >= 20141001
235         RETVAL_STRINGL(output, result);
236 #else
237         RETVAL_STRINGL(output, result, 1);
238 #endif
239     }
240 
241     efree(output);
242 }
243 
244 ZEND_FUNCTION(zstd_compress_dict)
245 {
246     zval *data, *dictBuffer;
247 
248     if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
249                               "z|z", &data, &dictBuffer) == FAILURE) {
250         RETURN_FALSE;
251     }
252     if (Z_TYPE_P(data) != IS_STRING) {
253         zend_error(E_WARNING, "zstd_compress_dict:"
254                    " expects the first parameter to be string.");
255         RETURN_FALSE;
256     }
257     if (Z_TYPE_P(dictBuffer) != IS_STRING) {
258         zend_error(E_WARNING, "zstd_compress_dict:"
259                    " expects the second parameter to be string.");
260         RETURN_FALSE;
261     }
262 
263     size_t const cBuffSize = ZSTD_compressBound(Z_STRLEN_P(data));
264     void* const cBuff = emalloc(cBuffSize);
265     if (!cBuff) {
266         zend_error(E_WARNING, "zstd_compress_dict: memory error");
267         RETURN_FALSE;
268     }
269     ZSTD_CCtx* const cctx = ZSTD_createCCtx();
270     if (cctx == NULL) {
271         efree(cBuff);
272         zend_error(E_WARNING, "ZSTD_createCCtx() error");
273         RETURN_FALSE;
274     }
275     ZSTD_CDict* const cdict = ZSTD_createCDict(Z_STRVAL_P(dictBuffer),
276                                                Z_STRLEN_P(dictBuffer),
277                                                DEFAULT_COMPRESS_LEVEL);
278     if (!cdict) {
279         efree(cBuff);
280         zend_error(E_WARNING, "ZSTD_createCDict() error");
281         RETURN_FALSE;
282     }
283     size_t const cSize = ZSTD_compress_usingCDict(cctx, cBuff, cBuffSize,
284                                                   Z_STRVAL_P(data),
285                                                   Z_STRLEN_P(data),
286                                                   cdict);
287     if (ZSTD_isError(cSize)) {
288         efree(cBuff);
289         zend_error(E_WARNING, "zstd_compress_dict: %s",
290                    ZSTD_getErrorName(cSize));
291         RETURN_FALSE;
292     }
293     ZSTD_freeCCtx(cctx);
294     ZSTD_freeCDict(cdict);
295 
296 #if ZEND_MODULE_API_NO >= 20141001
297     RETVAL_STRINGL(cBuff, cSize);
298 #else
299     RETVAL_STRINGL(cBuff, cSize, 1);
300 #endif
301 
302     efree(cBuff);
303 }
304 
305 ZEND_FUNCTION(zstd_uncompress_dict)
306 {
307     zval *data, *dictBuffer;
308 
309     if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
310                               "z|z", &data, &dictBuffer) == FAILURE) {
311         RETURN_FALSE;
312     }
313     if (Z_TYPE_P(data) != IS_STRING) {
314         zend_error(E_WARNING, "zstd_uncompress_dict:"
315                    " expects the first parameter to be string.");
316         RETURN_FALSE;
317     }
318     if (Z_TYPE_P(dictBuffer) != IS_STRING) {
319         zend_error(E_WARNING, "zstd_uncompress_dict:"
320                    " expects the second parameter to be string.");
321         RETURN_FALSE;
322     }
323 
324     unsigned long long const rSize = ZSTD_getDecompressedSize(Z_STRVAL_P(data),
325                                                               Z_STRLEN_P(data));
326     if (rSize == 0) {
327         RETURN_FALSE;
328     }
329     void* const rBuff = emalloc((size_t)rSize);
330     if (!rBuff) {
331         zend_error(E_WARNING, "zstd_uncompress_dict: memory error");
332         RETURN_FALSE;
333     }
334 
335     ZSTD_DCtx* const dctx = ZSTD_createDCtx();
336     if (dctx == NULL) {
337         efree(rBuff);
338         zend_error(E_WARNING, "ZSTD_createDCtx() error");
339         RETURN_FALSE;
340     }
341     ZSTD_DDict* const ddict = ZSTD_createDDict(Z_STRVAL_P(dictBuffer),
342                                                Z_STRLEN_P(dictBuffer));
343     if (!ddict) {
344         efree(rBuff);
345         zend_error(E_WARNING, "ZSTD_createDDict() error");
346         RETURN_FALSE;
347     }
348     size_t const dSize = ZSTD_decompress_usingDDict(dctx, rBuff, rSize,
349                                                     Z_STRVAL_P(data),
350                                                     Z_STRLEN_P(data),
351                                                     ddict);
352     if (dSize != rSize) {
353         efree(rBuff);
354         zend_error(E_WARNING, "zstd_uncompress_dict: %s",
355                    ZSTD_getErrorName(dSize));
356         RETURN_FALSE;
357     }
358     ZSTD_freeDCtx(dctx);
359     ZSTD_freeDDict(ddict);
360 
361 #if ZEND_MODULE_API_NO >= 20141001
362     RETVAL_STRINGL(rBuff, rSize);
363 #else
364     RETVAL_STRINGL(rBuff, rSize, 1);
365 #endif
366 
367     efree(rBuff);
368 }
369 
370 
371 typedef struct _php_zstd_stream_data {
372     char *bufin, *bufout;
373     size_t sizein, sizeout;
374     ZSTD_CCtx* cctx;
375     ZSTD_DCtx* dctx;
376     ZSTD_inBuffer input;
377     ZSTD_outBuffer output;
378     php_stream *stream;
379 } php_zstd_stream_data;
380 
381 
382 #define STREAM_DATA_FROM_STREAM() \
383     php_zstd_stream_data *self = (php_zstd_stream_data *) stream->abstract
384 
385 #define STREAM_NAME "compress.zstd"
386 
387 static int php_zstd_decomp_close(php_stream *stream, int close_handle TSRMLS_DC)
388 {
389     STREAM_DATA_FROM_STREAM();
390 
391     if (!self) {
392         return EOF;
393     }
394 
395     if (close_handle) {
396         if (self->stream) {
397             php_stream_close(self->stream);
398             self->stream = NULL;
399         }
400     }
401 
402     ZSTD_freeDCtx(self->dctx);
403     efree(self->bufin);
404     efree(self->bufout);
405     efree(self);
406     stream->abstract = NULL;
407 
408     return EOF;
409 }
410 
411 static int php_zstd_comp_flush_or_end(php_zstd_stream_data *self, int end TSRMLS_DC)
412 {
413     size_t res;
414     int ret = 0;
415 
416 #if ZSTD_VERSION_NUMBER < 10400
417     /* Compress remaining data */
418     if (self->input.size)  {
419         self->input.pos = 0;
420         do {
421             self->output.size = self->sizeout;
422             self->output.pos  = 0;
423             res = ZSTD_compressStream(self->cctx, &self->output, &self->input);
424             if (ZSTD_isError(res)) {
425                 php_error_docref(NULL TSRMLS_CC, E_WARNING, "libzstd error %s\n", ZSTD_getErrorName(res));
426                 ret = EOF;
427             }
428             php_stream_write(self->stream, self->bufout, self->output.pos);
429         } while (self->input.pos != self->input.size);
430     }
431 #endif
432 
433     /* Flush / End */
434     do {
435         self->output.size = self->sizeout;
436         self->output.pos  = 0;
437 #if ZSTD_VERSION_NUMBER >= 10400
438         res = ZSTD_compressStream2(self->cctx, &self->output, &self->input, end ? ZSTD_e_end : ZSTD_e_flush);
439 #else
440         if (end) {
441             res = ZSTD_endStream(self->cctx, &self->output);
442         } else {
443             res = ZSTD_flushStream(self->cctx, &self->output);
444         }
445 #endif
446         if (ZSTD_isError(res)) {
447             php_error_docref(NULL TSRMLS_CC, E_WARNING, "libzstd error %s\n", ZSTD_getErrorName(res));
448             ret = EOF;
449         }
450         php_stream_write(self->stream, self->bufout, self->output.pos);
451     } while (res > 0);
452 
453     self->input.pos = 0;
454     self->input.size = 0;
455 
456     return ret;
457 }
458 
459 
460 static int php_zstd_comp_flush(php_stream *stream TSRMLS_DC)
461 {
462     STREAM_DATA_FROM_STREAM();
463 
464     return php_zstd_comp_flush_or_end(self, 0 TSRMLS_CC);
465 }
466 
467 
468 static int php_zstd_comp_close(php_stream *stream, int close_handle TSRMLS_DC)
469 {
470     STREAM_DATA_FROM_STREAM();
471 
472     if (!self) {
473         return EOF;
474     }
475 
476     php_zstd_comp_flush_or_end(self, 1 TSRMLS_CC);
477 
478     if (close_handle) {
479         if (self->stream) {
480             php_stream_close(self->stream);
481             self->stream = NULL;
482         }
483     }
484 
485     ZSTD_freeCCtx(self->cctx);
486     efree(self->bufin);
487     efree(self->bufout);
488     efree(self);
489     stream->abstract = NULL;
490 
491     return EOF;
492 }
493 
494 
495 #if PHP_VERSION_ID < 70400
496 static size_t php_zstd_decomp_read(php_stream *stream, char *buf, size_t count TSRMLS_DC)
497 {
498     size_t ret = 0;
499 #else
500 static ssize_t php_zstd_decomp_read(php_stream *stream, char *buf, size_t count TSRMLS_DC)
501 {
502     ssize_t ret = 0;
503 #endif
504     size_t x, res;
505     STREAM_DATA_FROM_STREAM();
506 
507     while (count > 0) {
508         x = self->output.size - self->output.pos;
509         /* enough available */
510         if (x >= count) {
511             memcpy(buf, self->bufout + self->output.pos, count);
512             self->output.pos += count;
513             ret += count;
514             return ret;
515         }
516         /* take remaining from out  */
517         if (x) {
518             memcpy(buf, self->bufout + self->output.pos, x);
519             self->output.pos += x;
520             ret += x;
521             buf += x;
522             count -= x;
523         }
524         /* decompress */
525         if (self->input.pos < self->input.size) {
526             /* for zstd */
527             self->output.pos = 0;
528             self->output.size = self->sizeout;
529             res = ZSTD_decompressStream(self->dctx, &self->output , &self->input);
530             if (ZSTD_isError(res)) {
531                 php_error_docref(NULL TSRMLS_CC, E_WARNING, "libzstd error %s\n", ZSTD_getErrorName(res));
532 #if PHP_VERSION_ID >= 70400
533                 return -1;
534 #endif
535             }
536             /* for us */
537             self->output.size = self->output.pos;
538             self->output.pos = 0;
539         }  else {
540             /* read */
541             self->input.pos = 0;
542             self->input.size = php_stream_read(self->stream, self->bufin, self->sizein);
543             if (!self->input.size) {
544                 /* EOF */
545                 count = 0;
546             }
547         }
548     }
549     return ret;
550 }
551 
552 
553 #if PHP_VERSION_ID < 70400
554 static size_t php_zstd_comp_write(php_stream *stream, const char *buf, size_t count TSRMLS_DC)
555 {
556     size_t ret = 0;
557 #else
558 static ssize_t php_zstd_comp_write(php_stream *stream, const char *buf, size_t count TSRMLS_DC)
559 {
560     ssize_t ret = 0;
561 #endif
562     size_t x, res;
563 
564     STREAM_DATA_FROM_STREAM();
565 
566     while(count > 0) {
567         /* enough room for full data */
568         if (self->input.size + count < self->sizein) {
569             memcpy(self->bufin + self->input.size, buf, count);
570             self->input.size += count;
571             ret += count;
572             count = 0;
573             break;
574         }
575 
576         /* fill input buffer */
577         x = self->sizein - self->input.size;
578         memcpy(self->bufin + self->input.size, buf, x);
579         self->input.size += x;
580         buf += x;
581         count -= x;
582         ret += x;
583 
584         /* compress and write */
585         self->input.pos = 0;
586         do {
587             self->output.size = self->sizeout;
588             self->output.pos  = 0;
589 #if ZSTD_VERSION_NUMBER >= 10400
590             res = ZSTD_compressStream2(self->cctx, &self->output, &self->input, ZSTD_e_continue);
591 #else
592             res = ZSTD_compressStream(self->cctx, &self->output, &self->input);
593 #endif
594             if (ZSTD_isError(res)) {
595                 php_error_docref(NULL TSRMLS_CC, E_WARNING, "libzstd error %s\n", ZSTD_getErrorName(res));
596 #if PHP_VERSION_ID >= 70400
597                 return -1;
598 #endif
599             }
600             php_stream_write(self->stream, self->bufout, self->output.pos);
601         } while (self->input.pos != self->input.size);
602 
603         self->input.pos = 0;
604         self->input.size = 0;
605     }
606     return ret;
607 }
608 
609 
610 static php_stream_ops php_stream_zstd_read_ops = {
611     NULL,    /* write */
612     php_zstd_decomp_read,
613     php_zstd_decomp_close,
614     NULL,    /* flush */
615     STREAM_NAME,
616     NULL,    /* seek */
617     NULL,    /* cast */
618     NULL,    /* stat */
619     NULL     /* set_option */
620 };
621 
622 
623 static php_stream_ops php_stream_zstd_write_ops = {
624     php_zstd_comp_write,
625     NULL,    /* read */
626     php_zstd_comp_close,
627     php_zstd_comp_flush,
628     STREAM_NAME,
629     NULL,    /* seek */
630     NULL,    /* cast */
631     NULL,    /* stat */
632     NULL     /* set_option */
633 };
634 
635 
636 static php_stream *
637 php_stream_zstd_opener(
638     php_stream_wrapper *wrapper,
639 #if PHP_VERSION_ID < 50600
640     char *path,
641     char *mode,
642 #else
643     const char *path,
644     const char *mode,
645 #endif
646     int options,
647 #if PHP_MAJOR_VERSION < 7
648     char **opened_path,
649 #else
650     zend_string **opened_path,
651 #endif
652     php_stream_context *context
653     STREAMS_DC TSRMLS_DC)
654 {
655     php_zstd_stream_data *self;
656     int level = ZSTD_CLEVEL_DEFAULT;
657     int compress;
658 #if ZSTD_VERSION_NUMBER >= 10400
659     ZSTD_CDict *cdict = NULL;
660     ZSTD_DDict *ddict = NULL;
661 #endif
662 
663     if (strncasecmp(STREAM_NAME, path, sizeof(STREAM_NAME)-1) == 0) {
664         path += sizeof(STREAM_NAME)-1;
665         if (strncmp("://", path, 3) == 0) {
666             path += 3;
667         }
668     }
669 
670     if (php_check_open_basedir(path TSRMLS_CC)) {
671         return NULL;
672     }
673 
674     if (!strcmp(mode, "w") || !strcmp(mode, "wb")) {
675        compress = 1;
676     } else if (!strcmp(mode, "r") || !strcmp(mode, "rb")) {
677        compress = 0;
678     } else {
679         php_error_docref(NULL TSRMLS_CC, E_ERROR, "zstd: invalid open mode");
680         return NULL;
681     }
682 
683     if (context) {
684 #if PHP_MAJOR_VERSION >= 7
685         zval *tmpzval;
686         zend_string *data;
687 
688         if (NULL != (tmpzval = php_stream_context_get_option(context, "zstd", "level"))) {
689             level = zval_get_long(tmpzval);
690         }
691 #if ZSTD_VERSION_NUMBER >= 10400
692         if (NULL != (tmpzval = php_stream_context_get_option(context, "zstd", "dict"))) {
693             data = zval_get_string(tmpzval);
694             if (compress) {
695                 cdict = ZSTD_createCDict(ZSTR_VAL(data), ZSTR_LEN(data), level);
696             } else {
697                 ddict = ZSTD_createDDict(ZSTR_VAL(data), ZSTR_LEN(data));
698             }
699             zend_string_release(data);
700         }
701 #endif
702 #else
703         zval **tmpzval;
704 
705         if (php_stream_context_get_option(context, "zstd", "level", &tmpzval) == SUCCESS) {
706             convert_to_long_ex(tmpzval);
707             level = Z_LVAL_PP(tmpzval);
708         }
709 #if ZSTD_VERSION_NUMBER >= 10400
710         if (php_stream_context_get_option(context, "zstd", "dict", &tmpzval) == SUCCESS) {
711             convert_to_string(*tmpzval);
712             if (compress) {
713                 cdict = ZSTD_createCDict(Z_STRVAL_PP(tmpzval), Z_STRLEN_PP(tmpzval), level);
714             } else {
715                 ddict = ZSTD_createDDict(Z_STRVAL_PP(tmpzval), Z_STRLEN_PP(tmpzval));
716             }
717         }
718 #endif
719 #endif
720     }
721 
722     if (level > ZSTD_maxCLevel()) {
723         php_error_docref(NULL TSRMLS_CC, E_WARNING, "zstd: compression level (%d) must be less than %d", level, ZSTD_maxCLevel());
724         level = ZSTD_maxCLevel();
725     }
726 
727     self = ecalloc(sizeof(*self), 1);
728     self->stream = php_stream_open_wrapper(path, mode, options | REPORT_ERRORS, NULL);
729     if (!self->stream) {
730         efree(self);
731         return NULL;
732     }
733 
734     /* File */
735     if (compress) {
736         self->dctx = NULL;
737         self->cctx = ZSTD_createCCtx();
738         if (!self->cctx) {
739             php_error_docref(NULL TSRMLS_CC, E_WARNING, "zstd: compression context failed");
740             php_stream_close(self->stream);
741             efree(self);
742             return NULL;
743         }
744 #if ZSTD_VERSION_NUMBER >= 10400
745         ZSTD_CCtx_reset(self->cctx, ZSTD_reset_session_only);
746         ZSTD_CCtx_refCDict(self->cctx, cdict);
747         ZSTD_CCtx_setParameter(self->cctx, ZSTD_c_compressionLevel, level);
748 #else
749         ZSTD_initCStream(self->cctx, level);
750 #endif
751         self->bufin = emalloc(self->sizein = ZSTD_CStreamInSize());
752         self->bufout = emalloc(self->sizeout = ZSTD_CStreamOutSize());
753         self->input.src  = self->bufin;
754         self->input.pos   = 0;
755         self->input.size  = 0;
756         self->output.dst = self->bufout;
757         self->output.pos  = 0;
758         self->output.size = 0;
759 
760         return php_stream_alloc(&php_stream_zstd_write_ops, self, NULL, mode);
761 
762     } else {
763         self->dctx = ZSTD_createDCtx();
764         if (!self->dctx) {
765             php_error_docref(NULL TSRMLS_CC, E_WARNING, "zstd: compression context failed");
766             php_stream_close(self->stream);
767             efree(self);
768             return NULL;
769         }
770         self->cctx = NULL;
771         self->bufin = emalloc(self->sizein = ZSTD_DStreamInSize());
772         self->bufout = emalloc(self->sizeout = ZSTD_DStreamOutSize());
773 #if ZSTD_VERSION_NUMBER >= 10400
774         ZSTD_DCtx_reset(self->dctx, ZSTD_reset_session_only);
775         ZSTD_DCtx_refDDict(self->dctx, ddict);
776 #else
777         ZSTD_initDStream(self->dctx);
778 #endif
779         self->input.src   = self->bufin;
780         self->input.pos   = 0;
781         self->input.size  = 0;
782         self->output.dst  = self->bufout;
783         self->output.pos  = 0;
784         self->output.size = 0;
785 
786         return php_stream_alloc(&php_stream_zstd_read_ops, self, NULL, mode);
787     }
788     return NULL;
789 }
790 
791 
792 static php_stream_wrapper_ops zstd_stream_wops = {
793     php_stream_zstd_opener,
794     NULL,    /* close */
795     NULL,    /* fstat */
796     NULL,    /* stat */
797     NULL,    /* opendir */
798     STREAM_NAME,
799     NULL,    /* unlink */
800     NULL,    /* rename */
801     NULL,    /* mkdir */
802     NULL    /* rmdir */
803 #if PHP_VERSION_ID >= 50400
804     , NULL
805 #endif
806 };
807 
808 
809 php_stream_wrapper php_stream_zstd_wrapper = {
810     &zstd_stream_wops,
811     NULL,
812     0 /* is_url */
813 };
814 
815 
816 ZEND_MINIT_FUNCTION(zstd)
817 {
818     REGISTER_LONG_CONSTANT("ZSTD_COMPRESS_LEVEL_MIN",
819                            1,
820                            CONST_CS | CONST_PERSISTENT);
821     REGISTER_LONG_CONSTANT("ZSTD_COMPRESS_LEVEL_MAX",
822                            ZSTD_maxCLevel(),
823                            CONST_CS | CONST_PERSISTENT);
824     REGISTER_LONG_CONSTANT("ZSTD_COMPRESS_LEVEL_DEFAULT",
825                            DEFAULT_COMPRESS_LEVEL,
826                            CONST_CS | CONST_PERSISTENT);
827 
828     REGISTER_LONG_CONSTANT("LIBZSTD_VERSION_NUMBER",
829                            ZSTD_VERSION_NUMBER,
830                            CONST_CS | CONST_PERSISTENT);
831     REGISTER_STRING_CONSTANT("LIBZSTD_VERSION_STRING",
832                            ZSTD_VERSION_STRING,
833                            CONST_CS | CONST_PERSISTENT);
834 
835     php_register_url_stream_wrapper(STREAM_NAME, &php_stream_zstd_wrapper TSRMLS_CC);
836 
837     return SUCCESS;
838 }
839 
840 ZEND_MINFO_FUNCTION(zstd)
841 {
842     php_info_print_table_start();
843     php_info_print_table_row(2, "Zstd support", "enabled");
844     php_info_print_table_row(2, "Extension Version", PHP_ZSTD_EXT_VERSION);
845     php_info_print_table_row(2, "Interface Version", ZSTD_VERSION_STRING);
846     php_info_print_table_end();
847 }
848 
849 static zend_function_entry zstd_functions[] = {
850     ZEND_FE(zstd_compress, arginfo_zstd_compress)
851     ZEND_FE(zstd_uncompress, arginfo_zstd_uncompress)
852     ZEND_FALIAS(zstd_decompress, zstd_uncompress, arginfo_zstd_uncompress)
853 
854     ZEND_FE(zstd_compress_dict, arginfo_zstd_compress_dict)
855     ZEND_FE(zstd_uncompress_dict, arginfo_zstd_uncompress_dict)
856     ZEND_FALIAS(zstd_compress_usingcdict,
857                 zstd_compress_dict, arginfo_zstd_compress_dict)
858     ZEND_FALIAS(zstd_decompress_dict,
859                 zstd_uncompress_dict, arginfo_zstd_uncompress_dict)
860     ZEND_FALIAS(zstd_uncompress_usingcdict,
861                 zstd_uncompress_dict, arginfo_zstd_uncompress_dict)
862     ZEND_FALIAS(zstd_decompress_usingcdict,
863                 zstd_uncompress_dict, arginfo_zstd_uncompress_dict)
864 
865 // PHP 5.3+
866 #if ZEND_MODULE_API_NO >= 20090626
867     ZEND_NS_FALIAS(PHP_ZSTD_NS, compress,
868                    zstd_compress, arginfo_zstd_compress)
869     ZEND_NS_FALIAS(PHP_ZSTD_NS, uncompress,
870                    zstd_uncompress, arginfo_zstd_uncompress)
871     ZEND_NS_FALIAS(PHP_ZSTD_NS, decompress,
872                    zstd_uncompress, arginfo_zstd_uncompress)
873     ZEND_NS_FALIAS(PHP_ZSTD_NS, compress_dict,
874                    zstd_compress_dict, arginfo_zstd_compress_dict)
875     ZEND_NS_FALIAS(PHP_ZSTD_NS, compress_usingcdict,
876                    zstd_compress_dict, arginfo_zstd_compress_dict)
877     ZEND_NS_FALIAS(PHP_ZSTD_NS, uncompress_dict,
878                    zstd_uncompress_dict, arginfo_zstd_uncompress_dict)
879     ZEND_NS_FALIAS(PHP_ZSTD_NS, decompress_dict,
880                    zstd_uncompress_dict, arginfo_zstd_uncompress_dict)
881     ZEND_NS_FALIAS(PHP_ZSTD_NS, uncompress_usingcdict,
882                    zstd_uncompress_dict, arginfo_zstd_uncompress_dict)
883     ZEND_NS_FALIAS(PHP_ZSTD_NS, decompress_usingcdict,
884                    zstd_uncompress_dict, arginfo_zstd_uncompress_dict)
885 #endif
886     {NULL, NULL, NULL}
887 };
888 
889 zend_module_entry zstd_module_entry = {
890 #if ZEND_MODULE_API_NO >= 20010901
891     STANDARD_MODULE_HEADER,
892 #endif
893     "zstd",
894     zstd_functions,
895     ZEND_MINIT(zstd),
896     NULL,
897     NULL,
898     NULL,
899     ZEND_MINFO(zstd),
900 #if ZEND_MODULE_API_NO >= 20010901
901     PHP_ZSTD_EXT_VERSION,
902 #endif
903     STANDARD_MODULE_PROPERTIES
904 };
905 
906 #ifdef COMPILE_DL_ZSTD
907 ZEND_GET_MODULE(zstd)
908 #endif
909