1 /* 2 * Copyright (C) 2024 Thibaut VARĂˆNE <hacks@slashdirt.org> 3 * 4 * Permission to use, copy, modify, and/or distribute this software for any 5 * purpose with or without fee is hereby granted, provided that the above 6 * copyright notice and this permission notice appear in all copies. 7 * 8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 */ 16 17 /** 18 * # Zlib bindings 19 * 20 * The `zlib` module provides single-call-oriented functions for interacting with zlib data. 21 * 22 * @module zlib 23 */ 24 25 #include <stdio.h> 26 #include <string.h> 27 #include <assert.h> 28 #include <errno.h> 29 #include <zlib.h> 30 31 #include "ucode/module.h" 32 #include "ucode/platform.h" 33 34 // https://zlib.net/zlib_how.html 35 36 /* 37 * CHUNK is simply the buffer size for feeding data to and pulling data from 38 * the zlib routines. Larger buffer sizes would be more efficient, especially 39 * for inflate(). If the memory is available, buffers sizes on the order of 40 * 128K or 256K bytes should be used. 41 */ 42 #define CHUNK 16384 43 44 45 /* zlib init error message */ 46 static const char * ziniterr(int ret) 47 { 48 const char * msg; 49 50 switch (ret) { 51 case Z_ERRNO: 52 msg = strerror(errno); 53 break; 54 case Z_STREAM_ERROR: // can only happen for deflateInit2() by construction 55 msg = "invalid compression level"; 56 break; 57 case Z_MEM_ERROR: 58 msg = "out of memory"; 59 break; 60 case Z_VERSION_ERROR: 61 msg = "zlib version mismatch!"; 62 break; 63 default: 64 msg = "unknown error"; 65 break; 66 } 67 68 return msg; 69 } 70 71 static uc_stringbuf_t * 72 uc_zlib_def_object(uc_vm_t *vm, uc_value_t *obj, z_stream *strm) 73 { 74 int ret; 75 bool eof = false; 76 uc_value_t *rfn, *rbuf; 77 uc_stringbuf_t *buf = NULL; 78 79 rfn = ucv_property_get(obj, "read"); 80 81 if (!ucv_is_callable(rfn)) { 82 uc_vm_raise_exception(vm, EXCEPTION_TYPE, 83 "Input object does not implement read() method"); 84 return NULL; 85 } 86 87 buf = ucv_stringbuf_new(); 88 89 do { 90 rbuf = NULL; 91 uc_vm_stack_push(vm, ucv_get(obj)); 92 uc_vm_stack_push(vm, ucv_get(rfn)); 93 uc_vm_stack_push(vm, ucv_int64_new(CHUNK)); 94 95 if (uc_vm_call(vm, true, 1) != EXCEPTION_NONE) 96 goto fail; 97 98 rbuf = uc_vm_stack_pop(vm); // read output chunk 99 100 /* we only accept strings */ 101 if (rbuf != NULL && ucv_type(rbuf) != UC_STRING) { 102 uc_vm_raise_exception(vm, EXCEPTION_TYPE, 103 "Input object read() method returned non-string value"); 104 goto fail; 105 } 106 107 /* check EOF */ 108 eof = (rbuf == NULL || ucv_string_length(rbuf) == 0); 109 110 strm->next_in = (unsigned char *)ucv_string_get(rbuf); 111 strm->avail_in = ucv_string_length(rbuf); 112 113 /* run deflate() on input until output buffer not full */ 114 do { 115 // enlarge buf by CHUNK amount as needed 116 printbuf_memset(buf, printbuf_length(buf) + CHUNK - 1, 0, 1); 117 buf->bpos -= CHUNK; 118 119 strm->avail_out = CHUNK; 120 strm->next_out = (unsigned char *)(buf->buf + buf->bpos);; 121 122 ret = deflate(strm, eof ? Z_FINISH : Z_NO_FLUSH); // no bad return value here 123 assert(ret != Z_STREAM_ERROR); // state never clobbered (would be mem corruption) 124 (void)ret; // XXX make annoying compiler that ignores assert() happy 125 126 // update bpos past data written by deflate() 127 buf->bpos += CHUNK - strm->avail_out; 128 } while (strm->avail_out == 0); 129 assert(strm->avail_in == 0); // all input will be used 130 131 ucv_put(rbuf); // release rbuf 132 } while (!eof); // finish compression if all of source has been read in 133 assert(ret == Z_STREAM_END); // stream will be complete 134 135 return buf; 136 137 fail: 138 ucv_put(rbuf); 139 printbuf_free(buf); 140 return NULL; 141 } 142 143 static uc_stringbuf_t * 144 uc_zlib_def_string(uc_vm_t *vm, uc_value_t *str, z_stream *strm) 145 { 146 int ret; 147 uc_stringbuf_t *buf = NULL; 148 149 buf = ucv_stringbuf_new(); 150 151 strm->next_in = (unsigned char *)ucv_string_get(str); 152 strm->avail_in = ucv_string_length(str); 153 154 do { 155 printbuf_memset(buf, printbuf_length(buf) + CHUNK - 1, 0, 1); 156 buf->bpos -= CHUNK; 157 158 strm->avail_out = CHUNK; 159 strm->next_out = (unsigned char *)(buf->buf + buf->bpos); 160 161 ret = deflate(strm, Z_FINISH); 162 assert(ret != Z_STREAM_ERROR); 163 164 buf->bpos += CHUNK - strm->avail_out; 165 } while (ret != Z_STREAM_END); 166 assert(strm->avail_in == 0); 167 168 return buf; 169 } 170 171 /** 172 * Compresses data in Zlib or gzip format. 173 * 174 * If the input argument is a plain string, it is directly compressed. 175 * 176 * If an array, object or resource value is given, this function will attempt to 177 * invoke a `read()` method on it to read chunks of input text to incrementally 178 * compress. Reading will stop if the object's `read()` method returns 179 * either `null` or an empty string. 180 * 181 * Throws an exception on errors. 182 * 183 * Returns the compressed data. 184 * 185 * @function module:zlib#deflate 186 * 187 * @param {string} str_or_resource 188 * The string or resource object to be parsed as JSON. 189 * 190 * @param {?boolean} [gzip=false] 191 * Add a gzip header if true (creates a gzip-compliant output, otherwise defaults to Zlib) 192 * 193 * @param {?number} [level=Z_DEFAULT_COMPRESSION] 194 * The compression level (0-9). 195 * 196 * @returns {?string} 197 * 198 * @example 199 * // deflate content using default compression 200 * const deflated = deflate(content); 201 * 202 * // deflate content using fastest compression 203 * const deflated = deflate(content, Z_BEST_SPEED); 204 */ 205 static uc_value_t * 206 uc_zlib_deflate(uc_vm_t *vm, size_t nargs) 207 { 208 uc_value_t *rv = NULL; 209 uc_value_t *src = uc_fn_arg(0); 210 uc_value_t *gzip = uc_fn_arg(1); 211 uc_value_t *level = uc_fn_arg(2); 212 uc_stringbuf_t *buf = NULL; 213 int ret, lvl = Z_DEFAULT_COMPRESSION; 214 bool gz = false; 215 z_stream strm = { 216 .zalloc = Z_NULL, 217 .zfree = Z_NULL, 218 .opaque = Z_NULL, 219 }; 220 221 if (gzip) { 222 if (ucv_type(gzip) != UC_BOOLEAN) { 223 uc_vm_raise_exception(vm, EXCEPTION_TYPE, "Passed gzip flag is not a boolean"); 224 goto out; 225 } 226 227 gz = (int)ucv_boolean_get(gzip); 228 } 229 230 if (level) { 231 if (ucv_type(level) != UC_INTEGER) { 232 uc_vm_raise_exception(vm, EXCEPTION_TYPE, "Passed level is not a number"); 233 goto out; 234 } 235 236 lvl = (int)ucv_int64_get(level); 237 } 238 239 ret = deflateInit2(&strm, lvl, 240 Z_DEFLATED, // only allowed method 241 gz ? 15+16 : 15, // 15 Zlib default, +16 for gzip 242 8, // default value 243 Z_DEFAULT_STRATEGY); // default value 244 if (ret != Z_OK) { 245 uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "Zlib error: %s", ziniterr(ret)); 246 goto out; 247 } 248 249 switch (ucv_type(src)) { 250 case UC_STRING: 251 buf = uc_zlib_def_string(vm, src, &strm); 252 break; 253 254 case UC_RESOURCE: 255 case UC_OBJECT: 256 case UC_ARRAY: 257 buf = uc_zlib_def_object(vm, src, &strm); 258 break; 259 260 default: 261 uc_vm_raise_exception(vm, EXCEPTION_TYPE, 262 "Passed value is neither a string nor an object"); 263 goto out; 264 } 265 266 if (!buf) { 267 if (vm->exception.type == EXCEPTION_NONE) // do not clobber previous exception 268 uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "Zlib error: %s", strm.msg); 269 goto out; 270 } 271 272 rv = ucv_stringbuf_finish(buf); 273 274 out: 275 (void)deflateEnd(&strm); 276 return rv; 277 } 278 279 static uc_stringbuf_t * 280 uc_zlib_inf_object(uc_vm_t *vm, uc_value_t *obj, z_stream *strm) 281 { 282 int ret = Z_STREAM_ERROR; // error out if EOF on first loop 283 bool eof = false; 284 uc_value_t *rfn, *rbuf; 285 uc_stringbuf_t *buf = NULL; 286 287 rfn = ucv_property_get(obj, "read"); 288 289 if (!ucv_is_callable(rfn)) { 290 uc_vm_raise_exception(vm, EXCEPTION_TYPE, 291 "Input object does not implement read() method"); 292 return NULL; 293 } 294 295 buf = ucv_stringbuf_new(); 296 297 do { 298 rbuf = NULL; 299 uc_vm_stack_push(vm, ucv_get(obj)); 300 uc_vm_stack_push(vm, ucv_get(rfn)); 301 uc_vm_stack_push(vm, ucv_int64_new(CHUNK)); 302 303 if (uc_vm_call(vm, true, 1) != EXCEPTION_NONE) 304 goto fail; 305 306 rbuf = uc_vm_stack_pop(vm); // read output chunk 307 308 /* we only accept strings */ 309 if (rbuf != NULL && ucv_type(rbuf) != UC_STRING) { 310 uc_vm_raise_exception(vm, EXCEPTION_TYPE, 311 "Input object read() method returned non-string value"); 312 goto fail; 313 } 314 315 /* check EOF */ 316 eof = (rbuf == NULL || ucv_string_length(rbuf) == 0); 317 if (eof) 318 break; 319 320 strm->next_in = (unsigned char *)ucv_string_get(rbuf); 321 strm->avail_in = ucv_string_length(rbuf); 322 323 /* run deflate() on input until output buffer not full */ 324 do { 325 // enlarge buf by CHUNK amount as needed 326 printbuf_memset(buf, printbuf_length(buf) + CHUNK - 1, 0, 1); 327 buf->bpos -= CHUNK; 328 329 strm->avail_out = CHUNK; 330 strm->next_out = (unsigned char *)(buf->buf + buf->bpos);; 331 332 ret = inflate(strm, Z_NO_FLUSH); 333 assert(ret != Z_STREAM_ERROR); // state never clobbered (would be mem corruption) 334 switch (ret) { 335 case Z_NEED_DICT: 336 case Z_DATA_ERROR: 337 case Z_MEM_ERROR: 338 goto fail; 339 } 340 341 // update bpos past data written by deflate() 342 buf->bpos += CHUNK - strm->avail_out; 343 } while (strm->avail_out == 0); 344 345 ucv_put(rbuf); // release rbuf 346 } while (ret != Z_STREAM_END); // done when inflate() says it's done 347 348 if (ret != Z_STREAM_END) { // data error 349 printbuf_free(buf); 350 buf = NULL; 351 } 352 353 return buf; 354 355 fail: 356 ucv_put(rbuf); 357 printbuf_free(buf); 358 return NULL; 359 } 360 361 static uc_stringbuf_t * 362 uc_zlib_inf_string(uc_vm_t *vm, uc_value_t *str, z_stream *strm) 363 { 364 int ret; 365 uc_stringbuf_t *buf = NULL; 366 367 buf = ucv_stringbuf_new(); 368 369 strm->next_in = (unsigned char *)ucv_string_get(str); 370 strm->avail_in = ucv_string_length(str); 371 372 do { 373 printbuf_memset(buf, printbuf_length(buf) + CHUNK - 1, 0, 1); 374 buf->bpos -= CHUNK; 375 376 strm->avail_out = CHUNK; 377 strm->next_out = (unsigned char *)(buf->buf + buf->bpos); 378 379 ret = inflate(strm, Z_FINISH); 380 assert(ret != Z_STREAM_ERROR); 381 switch (ret) { 382 case Z_NEED_DICT: 383 case Z_DATA_ERROR: 384 case Z_MEM_ERROR: 385 printbuf_free(buf); 386 return NULL; 387 } 388 389 buf->bpos += CHUNK - strm->avail_out; 390 } while (ret != Z_STREAM_END); 391 assert(strm->avail_in == 0); 392 393 return buf; 394 } 395 396 /** 397 * Decompresses data in Zlib or gzip format. 398 * 399 * If the input argument is a plain string, it is directly decompressed. 400 * 401 * If an array, object or resource value is given, this function will attempt to 402 * invoke a `read()` method on it to read chunks of input text to incrementally 403 * decompress. Reading will stop if the object's `read()` method returns 404 * either `null` or an empty string. 405 * 406 * Throws an exception on errors. 407 * 408 * Returns the decompressed data. 409 * 410 * @function module:zlib#inflate 411 * 412 * @param {string} str_or_resource 413 * The string or resource object to be parsed as JSON. 414 * 415 * @returns {?string} 416 */ 417 static uc_value_t * 418 uc_zlib_inflate(uc_vm_t *vm, size_t nargs) 419 { 420 uc_value_t *rv = NULL; 421 uc_value_t *src = uc_fn_arg(0); 422 uc_stringbuf_t *buf = NULL; 423 int ret; 424 z_stream strm = { 425 .zalloc = Z_NULL, 426 .zfree = Z_NULL, 427 .opaque = Z_NULL, 428 .avail_in = 0, // must be initialized before call to inflateInit 429 .next_in = Z_NULL, // must be initialized before call to inflateInit 430 }; 431 432 /* tell inflateInit2 to perform either zlib or gzip decompression: 15+32 */ 433 ret = inflateInit2(&strm, 15+32); 434 if (ret != Z_OK) { 435 uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "Zlib error: %s", ziniterr(ret)); 436 goto out; 437 } 438 439 switch (ucv_type(src)) { 440 case UC_STRING: 441 buf = uc_zlib_inf_string(vm, src, &strm); 442 break; 443 444 case UC_RESOURCE: 445 case UC_OBJECT: 446 case UC_ARRAY: 447 buf = uc_zlib_inf_object(vm, src, &strm); 448 break; 449 450 default: 451 uc_vm_raise_exception(vm, EXCEPTION_TYPE, 452 "Passed value is neither a string nor an object"); 453 goto out; 454 } 455 456 if (!buf) { 457 if (vm->exception.type == EXCEPTION_NONE) // do not clobber previous exception 458 uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "Zlib error: %s", strm.msg); 459 goto out; 460 } 461 462 rv = ucv_stringbuf_finish(buf); 463 464 out: 465 (void)inflateEnd(&strm); 466 return rv; 467 } 468 469 static const uc_function_list_t global_fns[] = { 470 { "deflate", uc_zlib_deflate }, 471 { "inflate", uc_zlib_inflate }, 472 }; 473 474 void uc_module_init(uc_vm_t *vm, uc_value_t *scope) 475 { 476 uc_function_list_register(scope, global_fns); 477 478 #define ADD_CONST(x) ucv_object_add(scope, #x, ucv_int64_new(x)) 479 480 /** 481 * @typedef 482 * @name Compression levels 483 * @description Constants representing predefined compression levels. 484 * @property {number} Z_NO_COMPRESSION. 485 * @property {number} Z_BEST_SPEED. 486 * @property {number} Z_BEST_COMPRESSION. 487 * @property {number} Z_DEFAULT_COMPRESSION - default compromise between speed and compression (currently equivalent to level 6). 488 */ 489 ADD_CONST(Z_NO_COMPRESSION); 490 ADD_CONST(Z_BEST_SPEED); 491 ADD_CONST(Z_BEST_COMPRESSION); 492 ADD_CONST(Z_DEFAULT_COMPRESSION); 493 } 494
This page was automatically generated by LXR 0.3.1. • OpenWrt