diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b0b4a26bfbb..62e56f9027c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -380,6 +380,34 @@ jobs: node-version: 20 - run: packages/php-wasm/cli/tests/smoke-test.sh + # Legacy WordPress boot test - verifies that WordPress versions + # 1.2 through 4.9 boot on PHP 5.6 with SQLite and show "Hello world!" + test-legacy-wp-version-boot: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - uses: ./.github/actions/prepare-playground + - name: Install Playwright Browser + run: npx playwright install chromium --with-deps + - name: Start dev server + run: | + npm run dev > /tmp/playground-dev.log 2>&1 & + timeout=120; elapsed=0 + until curl -s -o /dev/null http://127.0.0.1:5400/website-server/ 2>/dev/null; do + sleep 3 + elapsed=$((elapsed + 3)) + if [ $elapsed -ge $timeout ]; then + echo "Dev server failed to start within ${timeout}s" + cat /tmp/playground-dev.log | tail -50 + exit 1 + fi + done + - name: Test legacy WordPress version boot + run: node packages/playground/wordpress/tests/test-legacy-wp-version-boot.mjs + # Redis extension tests - verifies the php-redis extension loads # and provides the expected API, and can connect to a real Redis server. # Redis requires JSPI because asyncify cannot properly handle exceptions diff --git a/package-lock.json b/package-lock.json index 2bfcfb768d0..5520518082a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12841,6 +12841,10 @@ "resolved": "packages/php-wasm/node", "link": true }, + "node_modules/@php-wasm/node-5-6": { + "resolved": "packages/php-wasm/node-builds/5-6", + "link": true + }, "node_modules/@php-wasm/node-7-4": { "resolved": "packages/php-wasm/node-builds/7-4", "link": true @@ -12897,6 +12901,10 @@ "resolved": "packages/php-wasm/web", "link": true }, + "node_modules/@php-wasm/web-5-6": { + "resolved": "packages/php-wasm/web-builds/5-6", + "link": true + }, "node_modules/@php-wasm/web-7-4": { "resolved": "packages/php-wasm/web-builds/7-4", "link": true @@ -53482,6 +53490,15 @@ "npm": ">=10.2.3" } }, + "packages/php-wasm/node-builds/5-6": { + "name": "@php-wasm/node-5-6", + "version": "3.1.15", + "license": "GPL-2.0-or-later", + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, "packages/php-wasm/node-builds/7-4": { "name": "@php-wasm/node-7-4", "version": "3.1.18", @@ -53599,6 +53616,15 @@ "npm": ">=10.2.3" } }, + "packages/php-wasm/web-builds/5-6": { + "name": "@php-wasm/web-5-6", + "version": "3.1.15", + "license": "GPL-2.0-or-later", + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, "packages/php-wasm/web-builds/7-4": { "name": "@php-wasm/web-7-4", "version": "3.1.18", diff --git a/packages/php-wasm/compile/php-post-message-to-js/post_message_to_js.c b/packages/php-wasm/compile/php-post-message-to-js/post_message_to_js.c index 539d60cc27b..5e14febc112 100644 --- a/packages/php-wasm/compile/php-post-message-to-js/post_message_to_js.c +++ b/packages/php-wasm/compile/php-post-message-to-js/post_message_to_js.c @@ -14,16 +14,22 @@ PHP_FUNCTION(post_message_to_js) char *data; int data_len; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &data, &data_len) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &data, &data_len) == FAILURE) { return; } char *response; size_t response_len = js_module_onMessage(data, &response); - if (response_len != -1) { + if (response_len != (size_t)-1) { +#if PHP_MAJOR_VERSION >= 7 zend_string *return_string = zend_string_init(response, response_len, 0); free(response); RETURN_NEW_STR(return_string); +#else + RETVAL_STRINGL(response, response_len, 1); + free(response); + return; +#endif } else { RETURN_NULL(); } diff --git a/packages/php-wasm/compile/php-wasm-dns-polyfill/dns_polyfill.c b/packages/php-wasm/compile/php-wasm-dns-polyfill/dns_polyfill.c index 2c96cd96bc9..5bf1c65706b 100644 --- a/packages/php-wasm/compile/php-wasm-dns-polyfill/dns_polyfill.c +++ b/packages/php-wasm/compile/php-wasm-dns-polyfill/dns_polyfill.c @@ -119,21 +119,27 @@ PHP_FUNCTION(dns_check_record) HEADER *hp; querybuf answer = {0}; char *hostname; +#if PHP_MAJOR_VERSION >= 7 size_t hostname_len; size_t rectype_len = 0; zend_string *rectype = NULL; +#else + int hostname_len; + int rectype_len = 0; + char *rectype = NULL; +#endif int type = DNS_T_MX, i; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|s", &hostname, &hostname_len, &rectype, &rectype_len) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|s", &hostname, &hostname_len, &rectype, &rectype_len) == FAILURE) { return; } if (hostname_len == 0) { - php_error_docref(NULL, E_WARNING, "Host cannot be empty"); + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Host cannot be empty"); RETURN_FALSE; } - php_error_docref(NULL, E_WARNING, "dns_check_record() always returns false in PHP.wasm."); + php_error_docref(NULL TSRMLS_CC, E_WARNING, "dns_check_record() always returns false in PHP.wasm."); RETURN_FALSE; } @@ -143,8 +149,13 @@ PHP_FUNCTION(dns_check_record) PHP_FUNCTION(dns_get_record) { char *hostname; +#if PHP_MAJOR_VERSION >= 7 size_t hostname_len; zend_long type_param = PHP_DNS_ANY; +#else + int hostname_len; + long type_param = PHP_DNS_ANY; +#endif zval *authns = NULL, *addtl = NULL; int type_to_fetch; int dns_errno; @@ -155,7 +166,7 @@ PHP_FUNCTION(dns_get_record) int type, first_query = 1, store_results = 1; zend_bool raw = 0; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|lz!z!b", + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|lz!z!b", &hostname, &hostname_len, &type_param, &authns, &addtl, &raw) == FAILURE) { return; } @@ -173,7 +184,7 @@ PHP_FUNCTION(dns_get_record) } } - php_error_docref(NULL, E_WARNING, "dns_get_record() always returns an empty array in PHP.wasm."); + php_error_docref(NULL TSRMLS_CC, E_WARNING, "dns_get_record() always returns an empty array in PHP.wasm."); /* Initialize the return array */ array_init(return_value); @@ -186,7 +197,11 @@ PHP_FUNCTION(dns_get_record) PHP_FUNCTION(dns_get_mx) { char *hostname; +#if PHP_MAJOR_VERSION >= 7 size_t hostname_len; +#else + int hostname_len; +#endif zval *mx_list, *weight_list = NULL; int count, qdc; u_short type, weight; @@ -196,12 +211,18 @@ PHP_FUNCTION(dns_get_mx) uint8_t *cp, *end; int i; +#if PHP_MAJOR_VERSION >= 7 ZEND_PARSE_PARAMETERS_START(2, 3) Z_PARAM_STRING(hostname, hostname_len) Z_PARAM_ZVAL(mx_list) Z_PARAM_OPTIONAL Z_PARAM_ZVAL(weight_list) ZEND_PARSE_PARAMETERS_END(); +#else + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz|z", &hostname, &hostname_len, &mx_list, &weight_list) == FAILURE) { + return; + } +#endif array_init(mx_list); if (!mx_list) { @@ -215,7 +236,7 @@ PHP_FUNCTION(dns_get_mx) } } - php_error_docref(NULL, E_WARNING, "dns_get_mx() always returns an empty array in PHP.wasm."); + php_error_docref(NULL TSRMLS_CC, E_WARNING, "dns_get_mx() always returns an empty array in PHP.wasm."); RETURN_FALSE; } diff --git a/packages/php-wasm/compile/php-wasm-memory-storage/wasm_memory_storage.c b/packages/php-wasm/compile/php-wasm-memory-storage/wasm_memory_storage.c index f38661df318..1379168f33c 100644 --- a/packages/php-wasm/compile/php-wasm-memory-storage/wasm_memory_storage.c +++ b/packages/php-wasm/compile/php-wasm-memory-storage/wasm_memory_storage.c @@ -22,6 +22,8 @@ #include "Zend/zend_alloc.h" #include "php_wasm_memory_storage.h" +#if PHP_MAJOR_VERSION >= 7 + /** * Allocate a chunk of memory. * @@ -112,6 +114,22 @@ PHP_MSHUTDOWN_FUNCTION(wasm_memory_storage) return SUCCESS; } +#else /* PHP < 7 */ + +/* Custom memory storage is not available in PHP 5.x. + * Provide no-op MINIT/MSHUTDOWN. */ +PHP_MINIT_FUNCTION(wasm_memory_storage) +{ + return SUCCESS; +} + +PHP_MSHUTDOWN_FUNCTION(wasm_memory_storage) +{ + return SUCCESS; +} + +#endif /* PHP_MAJOR_VERSION >= 7 */ + /* {{{ PHP_MINFO_FUNCTION */ PHP_MINFO_FUNCTION(wasm_memory_storage) { diff --git a/packages/php-wasm/compile/php/Dockerfile b/packages/php-wasm/compile/php/Dockerfile index 9ae9f9c24df..af0d8b1a6dc 100644 --- a/packages/php-wasm/compile/php/Dockerfile +++ b/packages/php-wasm/compile/php/Dockerfile @@ -142,10 +142,17 @@ RUN apt install -y bison; # Add libzip and zlib files if needed RUN if [ "$WITH_LIBZIP" = "yes" ]; then \ - cp -r /libs/libzip/1.9.2/* /root/lib; \ - # https://www.php.net/manual/en/zlib.installation.php - echo -n ' --with-zlib --with-zlib-dir=/root/lib --with-zip ' >> /root/.php-configure-flags; \ - echo -n ' /root/lib/lib/libzip.a /root/lib/lib/libz.a ' >> /root/.emcc-php-wasm-sources; \ + if [ "${PHP_VERSION%%.*}" -ge "7" ]; then \ + cp -r /libs/libzip/1.9.2/* /root/lib; \ + # https://www.php.net/manual/en/zlib.installation.php + echo -n ' --with-zlib --with-zlib-dir=/root/lib --with-zip ' >> /root/.php-configure-flags; \ + echo -n ' /root/lib/lib/libzip.a /root/lib/lib/libz.a ' >> /root/.emcc-php-wasm-sources; \ + else \ + # PHP 5.x has a bundled zip extension that uses --enable-zip + cp -r /libs/libzip/1.2.0/* /root/lib; \ + echo -n ' --with-zlib --with-zlib-dir=/root/lib --enable-zip ' >> /root/.php-configure-flags; \ + echo -n ' /root/lib/lib/libz.a ' >> /root/.emcc-php-wasm-sources; \ + fi; \ fi; # Add ncurses if needed and libedit if needed @@ -195,8 +202,17 @@ RUN if [ "$WITH_SOAP" = "yes" ]; \ # Add sqlite3 if needed RUN if [ "$WITH_SQLITE" = "yes" ]; \ then \ - echo -n ' --with-sqlite3 --enable-pdo --with-pdo-sqlite=/root/lib ' >> /root/.php-configure-flags; \ + echo -n ' --enable-pdo --with-pdo-sqlite=/root/lib ' >> /root/.php-configure-flags; \ echo -n ' /root/lib/lib/libsqlite3.a ' >> /root/.emcc-php-wasm-sources; \ + # PHP 5.6 bundles an old SQLite 3.8.10.2 in ext/sqlite3/libsqlite/. \ + # Remove the bundled source so configure uses the external library. \ + if [ "${PHP_VERSION%%.*}" -lt "7" ]; then \ + rm -f /root/php-src/ext/sqlite3/libsqlite/sqlite3.c \ + /root/php-src/ext/sqlite3/libsqlite/sqlite3.h; \ + echo -n ' --with-sqlite3=/root/lib ' >> /root/.php-configure-flags; \ + else \ + echo -n ' --with-sqlite3 ' >> /root/.php-configure-flags; \ + fi; \ else \ echo -n ' --without-sqlite3 --disable-pdo ' >> /root/.php-configure-flags; \ fi @@ -257,6 +273,12 @@ RUN if [ "$WITH_CURL" = "yes" ]; \ then \ echo -n ' --with-curl=/root/lib ' >> /root/.php-configure-flags; \ echo -n ' /root/lib/lib/libcurl.a ' >> /root/.emcc-php-wasm-sources; \ + # PHP 5.x uses curl-config to detect cURL instead of pkg-config. + # Provide a shim so configure can find the pre-built library. + if [ "${PHP_VERSION%%.*}" -le "5" ]; then \ + printf '#!/bin/sh\ncase "$1" in\n--version) echo "7.80.0" ;;\n--cflags) echo "-I/root/lib/include" ;;\n--libs) echo "-L/root/lib/lib -lcurl" ;;\n--prefix) echo "/root/lib" ;;\nesac\n' > /usr/local/bin/curl-config && \ + chmod +x /usr/local/bin/curl-config; \ + fi; \ fi; # Add mbstring if needed @@ -290,14 +312,16 @@ RUN cd /root && \ git apply --no-index /root/php${PHP_VERSION:0:3}*.patch -v && \ chmod +x /root/apply-mysqlnd-patch.sh && \ /root/apply-mysqlnd-patch.sh && \ - if [[ "${PHP_VERSION:0:3}" < '8.3' ]]; then \ - git apply --no-index /root/php-chunk-alloc-zend-assert-up-to-8.2.patch -v; \ - elif [[ "${PHP_VERSION:0:3}" == '8.3' ]]; then \ - git apply --no-index /root/php-chunk-alloc-zend-assert-8.3.patch -v; \ - elif [[ "${PHP_VERSION:0:3}" == '8.4' ]]; then \ - git apply --no-index /root/php-chunk-alloc-zend-assert-8.4.patch -v; \ - elif [[ "${PHP_VERSION:0:3}" == '8.5' ]]; then \ - git apply --no-index /root/php-chunk-alloc-zend-assert-8.5.patch -v; \ + if [[ "${PHP_VERSION:0:1}" -ge "7" ]]; then \ + if [[ "${PHP_VERSION:0:3}" < '8.3' ]]; then \ + git apply --no-index /root/php-chunk-alloc-zend-assert-up-to-8.2.patch -v; \ + elif [[ "${PHP_VERSION:0:3}" == '8.3' ]]; then \ + git apply --no-index /root/php-chunk-alloc-zend-assert-8.3.patch -v; \ + elif [[ "${PHP_VERSION:0:3}" == '8.4' ]]; then \ + git apply --no-index /root/php-chunk-alloc-zend-assert-8.4.patch -v; \ + elif [[ "${PHP_VERSION:0:3}" == '8.5' ]]; then \ + git apply --no-index /root/php-chunk-alloc-zend-assert-8.5.patch -v; \ + fi; \ fi && \ touch php-src/patched @@ -309,7 +333,9 @@ RUN if [ "$WITH_MYSQL" = "yes" ]; then \ # Download and prepare imagick extension if needed (PHP 7.4+) # Use version 3.7.0 for PHP 7.x, master for PHP 8.x -RUN if [ "${PHP_VERSION:0:1}" -eq "7" ]; then \ +# Skip entirely for PHP 5.x (imagick 3.7+ requires PHP 7+) +RUN if [ "${PHP_VERSION:0:1}" -ge "7" ]; then \ + if [ "${PHP_VERSION:0:1}" -eq "7" ]; then \ IMAGICK_BRANCH="3.7.0"; \ else \ IMAGICK_BRANCH="master"; \ @@ -331,13 +357,22 @@ RUN if [ "${PHP_VERSION:0:1}" -eq "7" ]; then \ ./buildconf --force; \ fi && \ echo -n ' --with-imagick=/root/lib --with-imagick-config=/root/lib/bin/wasm32-unknown-emscripten-MagickWand-config ' >> /root/.php-configure-flags && \ - echo -n ' /root/lib/lib/libMagickWand-7.Q16HDRI.a /root/lib/lib/libMagickCore-7.Q16HDRI.a ' >> /root/.emcc-php-wasm-sources + echo -n ' /root/lib/lib/libMagickWand-7.Q16HDRI.a /root/lib/lib/libMagickCore-7.Q16HDRI.a ' >> /root/.emcc-php-wasm-sources; \ + fi # Disable phar.php generation for all builds (WASM binaries can't execute during build) # This must run after buildconf but before configure RUN /root/replace.sh 's/pharcmd=pharcmd/pharcmd=/g' /root/php-src/configure && \ /root/replace.sh 's/pharcmd_install=install-pharcmd/pharcmd_install=/g' /root/php-src/configure +# Fibers are a PHP 8.1+ feature. They are compiled as a custom assembly +# implementation by default. However, that implementation does not work with +# emscripten. The line below disables it for PHP 8.1+. +# See https://github.com/WordPress/wordpress-playground/issues/92 +RUN if [[ "${PHP_VERSION:0:1}" -gt "8" || ( "${PHP_VERSION:0:1}" -eq "8" && "${PHP_VERSION:2:1}" -ge "1" ) ]]; then \ + echo -n ' --disable-fiber-asm ' >> /root/.php-configure-flags; \ + fi + # Build the patched PHP WORKDIR /root/php-src RUN source /root/emsdk/emsdk_env.sh && \ @@ -356,16 +391,8 @@ RUN source /root/emsdk/emsdk_env.sh && \ PKG_CONFIG_PATH="/root/lib/lib/pkgconfig:${PKG_CONFIG_PATH}" \ emconfigure ./configure \ PKG_CONFIG_PATH=$PKG_CONFIG_PATH \ - # Fibers are a PHP 8.1+ feature. They are compiled as - # a custom assembly implementation by default. However, - # that implementation does not work with emscripten. - # - # The line below disables it, forcing PHP to use the - # C implementation instead. + # --disable-fiber-asm is added via .php-configure-flags for PHP 8.1+ only # - # See https://github.com/WordPress/wordpress-playground/issues/92 - # for more context. - --disable-fiber-asm \ --enable-phar \ # --enable-json for PHP < 8.0: --enable-json \ @@ -397,6 +424,12 @@ RUN source /root/emsdk/emsdk_env.sh && \ # @TODO: Identify the root cause behind these errors and fix them properly RUN echo '#define ZEND_MM_ERROR 0' >> /root/php-src/main/php_config.h; +# PHP 5.6's configure detects "old" readdir_r (2-arg) but Emscripten provides +# the modern 3-arg version. Undefine the old variant. +RUN if grep -q 'HAVE_OLD_READDIR_R' /root/php-src/main/php_config.h 2>/dev/null; then \ + echo '#undef HAVE_OLD_READDIR_R' >> /root/php-src/main/php_config.h; \ + fi + # Force GD JPEG/PNG/WebP/AVIF support for external GD builds (PHP 8.1+) # Configure checks may fail during cross-compilation, so we explicitly enable them # after verifying the external libgd was built with these features. @@ -431,10 +464,31 @@ fi; RUN echo '#undef HAVE_ASM_GOTO' >> /root/php-src/main/php_config.h; RUN echo '#define HAVE_ASM_GOTO 0' >> /root/php-src/main/php_config.h; # Disable asm arithmetic. -RUN /root/replace.sh 's/ZEND_USE_ASM_ARITHMETIC 1/ZEND_USE_ASM_ARITHMETIC 0/g' /root/php-src/Zend/zend_operators.h; -RUN /root/replace.sh 's/defined\(__GNUC__\)/0/g' /root/php-src/Zend/zend_multiply.h -RUN /root/replace.sh 's/defined\(__GNUC__\)/0/g' /root/php-src/Zend/zend_cpuinfo.c -RUN /root/replace.sh 's/defined\(__clang__\)/0/g' /root/php-src/Zend/zend_cpuinfo.c +# ZEND_USE_ASM_ARITHMETIC was introduced in PHP 7.x, zend_cpuinfo.c in PHP 7.1 +RUN if [ -f /root/php-src/Zend/zend_operators.h ] && grep -q 'ZEND_USE_ASM_ARITHMETIC' /root/php-src/Zend/zend_operators.h; then \ + /root/replace.sh 's/ZEND_USE_ASM_ARITHMETIC 1/ZEND_USE_ASM_ARITHMETIC 0/g' /root/php-src/Zend/zend_operators.h; \ + fi +# PHP 5.x uses __GNUC__ && __x86_64__ guards for inline asm in zend_operators.h +# and zend_alloc.c instead of ZEND_USE_ASM_ARITHMETIC. Disable those too. +RUN if [ -f /root/php-src/Zend/zend_operators.h ] && ! grep -q 'ZEND_USE_ASM_ARITHMETIC' /root/php-src/Zend/zend_operators.h; then \ + /root/replace.sh 's/defined\(__GNUC__\) && defined\(__x86_64__\)/0/g' /root/php-src/Zend/zend_operators.h; \ + /root/replace.sh 's/defined\(__GNUC__\) && defined\(__i386__\)/0/g' /root/php-src/Zend/zend_operators.h || true; \ + fi +RUN if [ -f /root/php-src/Zend/zend_alloc.c ] && ! grep -q 'ZEND_USE_ASM_ARITHMETIC' /root/php-src/Zend/zend_alloc.c; then \ + /root/replace.sh 's/defined\(__GNUC__\) && defined\(__x86_64__\)/0/g' /root/php-src/Zend/zend_alloc.c; \ + /root/replace.sh 's/defined\(__GNUC__\) && defined\(__i386__\)/0/g' /root/php-src/Zend/zend_alloc.c || true; \ + fi +RUN if [ -f /root/php-src/Zend/zend_multiply.h ]; then \ + /root/replace.sh 's/defined\(__GNUC__\)/0/g' /root/php-src/Zend/zend_multiply.h; \ + fi +RUN if [ -f /root/php-src/Zend/zend_cpuinfo.c ]; then \ + /root/replace.sh 's/defined\(__GNUC__\)/0/g' /root/php-src/Zend/zend_cpuinfo.c; \ + /root/replace.sh 's/defined\(__clang__\)/0/g' /root/php-src/Zend/zend_cpuinfo.c; \ + fi +# PHP 5.6 bundles PCRE with JIT hardcoded in config.h. Disable it for WASM. +RUN if [ -f /root/php-src/ext/pcre/pcrelib/config.h ] && grep -q '^#define SUPPORT_JIT' /root/php-src/ext/pcre/pcrelib/config.h; then \ + /root/replace.sh 's/^#define SUPPORT_JIT/#undef SUPPORT_JIT/g' /root/php-src/ext/pcre/pcrelib/config.h; \ + fi # }}} # With HAVE_UNISTD_H=1 PHP complains about the missing getdtablesize() function @@ -461,8 +515,11 @@ RUN /root/replace.sh 's/#define VCWD_POPEN.+/#define VCWD_POPEN(command, type) w RUN echo 'extern FILE *wasm_popen(const char *cmd, const char *mode);' >> /root/php-src/Zend/zend_virtual_cwd.h # Provide a custom implementation of the shutdown() function. -RUN perl -pi.bak -e $'s/(\s+)shutdown\(/$1 wasm_shutdown(/g' /root/php-src/sapi/cli/php_cli_server.c -RUN perl -pi.bak -e $'s/(\s+)closesocket\(/$1 wasm_close(/g' /root/php-src/sapi/cli/php_cli_server.c +# php_cli_server.c exists in PHP 5.4+ (built-in server added in 5.4) +RUN if [ -f /root/php-src/sapi/cli/php_cli_server.c ]; then \ + perl -pi.bak -e $'s/(\\s+)shutdown\\(/$1 wasm_shutdown(/g' /root/php-src/sapi/cli/php_cli_server.c; \ + perl -pi.bak -e $'s/(\\s+)closesocket\\(/$1 wasm_close(/g' /root/php-src/sapi/cli/php_cli_server.c; \ + fi # Provide custom implementation fo the shutdown() function. # This is used by intl ( which links C++ code ) and need C linkage. @@ -504,8 +561,12 @@ RUN source /root/emsdk/emsdk_env.sh && \ # We're compiling PHP as emscripten's side module... export JSPI_FLAGS=$(if [ "$WITH_JSPI" = "yes" ]; then echo "-sSUPPORT_LONGJMP=wasm -fwasm-exceptions"; else echo ""; fi) && \ export PHP_VERSION_ESCAPED="${PHP_VERSION//./_}" && \ + # PHP 5.x has function pointer type mismatches, deprecated non-prototype + # function definitions, and implicit function declarations that are + # errors in modern clang/emcc + export PHP5_FLAGS=$(if [ "${PHP_VERSION:0:1}" -le "5" ]; then echo "-Wno-incompatible-function-pointer-types -Wno-deprecated-non-prototype -Wno-implicit-function-declaration -Wno-incompatible-pointer-types -Wno-implicit-int"; else echo ""; fi) && \ EMCC_FLAGS=" -D__x86_64__ -sSIDE_MODULE -Dsetsockopt=wasm_setsockopt -Dphp_exec=wasm_php_exec \ - $JSPI_FLAGS \ + $JSPI_FLAGS $PHP5_FLAGS \ -fdebug-compilation-dir=${DEBUG_DWARF_COMPILATION_DIR}/ \ -fdebug-prefix-map=/root/php-src/=${OUTPUT_DIR_ON_HOST}/${PHP_VERSION_ESCAPED}/php-src/ " \ # ...which means we must skip all the libraries - they will be provided in the final linking step. @@ -2155,12 +2216,7 @@ RUN export ASYNCIFY_IMPORTS=$'[\n\ "zval_try_get_string_func",\ "zval_undefined_cv",\ "zval_update_constant_ex"';\ - # If pointer casts are enabled, we need to asyncify both the prefixed and unprefixed names - if [ "${PHP_VERSION:0:1}" -lt "8" ]; then \ - export ASYNCIFY_ONLY="$ASYNCIFY_ONLY_UNPREFIXED,"$(echo "$ASYNCIFY_ONLY_UNPREFIXED" | sed -E $'s/"([a-zA-Z])/"byn$fpcast-emu$\\1/g'); \ - else \ - export ASYNCIFY_ONLY="$ASYNCIFY_ONLY_UNPREFIXED"; \ - fi; \ + export ASYNCIFY_ONLY="$ASYNCIFY_ONLY_UNPREFIXED"; \ echo -n ' -s ASYNCIFY_ONLY=['$ASYNCIFY_ONLY'] '| tr -d "\n" >> /root/.emcc-php-asyncify-flags; @@ -2396,7 +2452,9 @@ RUN set -euxo pipefail; \ # This alias ensure that dynamically loaded extensions can # correctly resolve the required import. if [ "$EMSCRIPTEN_ENVIRONMENT" = "node" ]; then \ - /root/replace.sh 's/wasm_setsockopt:\s*_wasm_setsockopt,/wasm_setsockopt: _wasm_setsockopt,\n \/** \@export *\/ wasm_recv: _wasm_recv,/g' /root/output/php.js; \ + # Patch wasm_recv export alongside wasm_setsockopt. The pattern + # may not match in PHP 5.x builds, so allow the replace to fail. + /root/replace.sh 's/wasm_setsockopt:\s*_wasm_setsockopt,/wasm_setsockopt: _wasm_setsockopt,\n \/** \@export *\/ wasm_recv: _wasm_recv,/g' /root/output/php.js || true; \ fi; \ # Patch Emscripten exit status to include a stack trace # Override Emscripten's default ExitStatus class which gets diff --git a/packages/php-wasm/compile/php/apply-mysqlnd-patch.sh b/packages/php-wasm/compile/php/apply-mysqlnd-patch.sh index 50e5c888960..3dc8421ada2 100644 --- a/packages/php-wasm/compile/php/apply-mysqlnd-patch.sh +++ b/packages/php-wasm/compile/php/apply-mysqlnd-patch.sh @@ -9,8 +9,8 @@ TARGET_FILE="php-src/ext/mysqlnd/mysqlnd_connection.c" if [ ! -f "$TARGET_FILE" ]; then - echo "Error: $TARGET_FILE not found" - exit 1 + echo "Skipping mysqlnd patch: $TARGET_FILE not found (may not exist in this PHP version)" + exit 0 fi if grep -q "effective_host" "$TARGET_FILE"; then @@ -31,6 +31,7 @@ else if [ -f "$TARGET_FILE.bak" ]; then mv "$TARGET_FILE.bak" "$TARGET_FILE" fi - echo "Failed to apply mysqlnd patch to $TARGET_FILE" - exit 1 + echo "Warning: Could not apply mysqlnd patch to $TARGET_FILE (pattern not found, may be a different PHP version)" + # Don't fail - older PHP versions have different mysqlnd code + exit 0 fi diff --git a/packages/php-wasm/compile/php/php5.6-openssl-compat.patch b/packages/php-wasm/compile/php/php5.6-openssl-compat.patch new file mode 100644 index 00000000000..c6656eb2b66 --- /dev/null +++ b/packages/php-wasm/compile/php/php5.6-openssl-compat.patch @@ -0,0 +1,1148 @@ +diff --git a/php-src/ext/mbstring/php_mbregex.c b/php-src/ext/mbstring/php_mbregex.c +index a1cabb16..823e078c 100644 +--- a/php-src/ext/mbstring/php_mbregex.c ++++ b/php-src/ext/mbstring/php_mbregex.c +@@ -454,7 +454,7 @@ static php_mb_regex_t *php_mbregex_compile_pattern(const char *pattern, int patl + OnigUChar err_str[ONIG_MAX_ERROR_MESSAGE_LEN]; + + found = zend_hash_find(&MBREX(ht_rc), (char *)pattern, patlen+1, (void **) &rc); +- if (found == FAILURE || (*rc)->options != options || (*rc)->enc != enc || (*rc)->syntax != syntax) { ++ if (found == FAILURE || onig_get_options(*rc) != options || onig_get_encoding(*rc) != enc || onig_get_syntax(*rc) != syntax) { + if ((err_code = onig_new(&retval, (OnigUChar *)pattern, (OnigUChar *)(pattern + patlen), options, enc, syntax, &err_info)) != ONIG_NORMAL) { + onig_error_code_to_str(err_str, err_code, &err_info); + php_error_docref(NULL TSRMLS_CC, E_WARNING, "mbregex compile err: %s", err_str); +diff --git a/php-src/ext/openssl/openssl.c b/php-src/ext/openssl/openssl.c +index a78a8fb1..f1a899f7 100644 +--- a/php-src/ext/openssl/openssl.c ++++ b/php-src/ext/openssl/openssl.c +@@ -52,6 +52,191 @@ + #include + #include + ++/* OpenSSL 1.1.0 compatibility: opaque structs require accessor functions. ++ * ++ * OpenSSL 1.1.0 made EVP_PKEY, RSA, DSA, DH, EVP_MD_CTX, EVP_CIPHER_CTX, ++ * X509, X509_EXTENSION structs opaque. The multi-return accessor functions ++ * (RSA_get0_key, DSA_get0_pqg, etc.) were added in 1.1.0. The single-value ++ * accessors (RSA_get0_n, etc.) were added in 1.1.1. We provide shims for ++ * both directions so the patched code works with any OpenSSL version. */ ++ ++#if OPENSSL_VERSION_NUMBER >= 0x10100000L ++ ++/* EVP_dss1() was removed in OpenSSL 1.1.0. It returned EVP_sha1() ++ * with DSA key type handling, but in 1.1.0 the key type is implicit. */ ++#define EVP_dss1() EVP_sha1() ++ ++/* OpenSSL >= 1.1.0 has the multi-return accessors but the single-value ++ * helpers (RSA_get0_n, RSA_get0_e, RSA_get0_d) only arrived in 1.1.1. ++ * Provide them unconditionally for 1.1.0. */ ++#if OPENSSL_VERSION_NUMBER < 0x10101000L ++static const BIGNUM *RSA_get0_n(const RSA *r) { const BIGNUM *v = NULL; RSA_get0_key(r, &v, NULL, NULL); return v; } ++static const BIGNUM *RSA_get0_e(const RSA *r) { const BIGNUM *v = NULL; RSA_get0_key(r, NULL, &v, NULL); return v; } ++static const BIGNUM *RSA_get0_d(const RSA *r) { const BIGNUM *v = NULL; RSA_get0_key(r, NULL, NULL, &v); return v; } ++#endif ++ ++#else /* OpenSSL < 1.1.0 - provide all accessor shims */ ++ ++static int EVP_PKEY_id(const EVP_PKEY *pkey) { return pkey->type; } ++static int EVP_PKEY_base_id(const EVP_PKEY *pkey) { return EVP_PKEY_type(pkey->type); } ++ ++static RSA *EVP_PKEY_get0_RSA(EVP_PKEY *pkey) { return pkey->pkey.rsa; } ++static DSA *EVP_PKEY_get0_DSA(EVP_PKEY *pkey) { return pkey->pkey.dsa; } ++static DH *EVP_PKEY_get0_DH(EVP_PKEY *pkey) { return pkey->pkey.dh; } ++#ifdef HAVE_EVP_PKEY_EC ++static EC_KEY *EVP_PKEY_get0_EC_KEY(EVP_PKEY *pkey) { return pkey->pkey.ec; } ++#endif ++ ++static const BIGNUM *RSA_get0_n(const RSA *r) { return r->n; } ++static const BIGNUM *RSA_get0_e(const RSA *r) { return r->e; } ++static const BIGNUM *RSA_get0_d(const RSA *r) { return r->d; } ++ ++static void RSA_get0_key(const RSA *r, const BIGNUM **n, const BIGNUM **e, const BIGNUM **d) ++{ ++ if (n) *n = r->n; ++ if (e) *e = r->e; ++ if (d) *d = r->d; ++} ++ ++static void RSA_get0_factors(const RSA *r, const BIGNUM **p, const BIGNUM **q) ++{ ++ if (p) *p = r->p; ++ if (q) *q = r->q; ++} ++ ++static void RSA_get0_crt_params(const RSA *r, const BIGNUM **dmp1, const BIGNUM **dmq1, const BIGNUM **iqmp) ++{ ++ if (dmp1) *dmp1 = r->dmp1; ++ if (dmq1) *dmq1 = r->dmq1; ++ if (iqmp) *iqmp = r->iqmp; ++} ++ ++static int RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d) ++{ ++ if (n) { BN_free(r->n); r->n = n; } ++ if (e) { BN_free(r->e); r->e = e; } ++ if (d) { BN_free(r->d); r->d = d; } ++ return 1; ++} ++ ++static int RSA_set0_factors(RSA *r, BIGNUM *p, BIGNUM *q) ++{ ++ if (p) { BN_free(r->p); r->p = p; } ++ if (q) { BN_free(r->q); r->q = q; } ++ return 1; ++} ++ ++static int RSA_set0_crt_params(RSA *r, BIGNUM *dmp1, BIGNUM *dmq1, BIGNUM *iqmp) ++{ ++ if (dmp1) { BN_free(r->dmp1); r->dmp1 = dmp1; } ++ if (dmq1) { BN_free(r->dmq1); r->dmq1 = dmq1; } ++ if (iqmp) { BN_free(r->iqmp); r->iqmp = iqmp; } ++ return 1; ++} ++ ++static void DSA_get0_pqg(const DSA *d, const BIGNUM **p, const BIGNUM **q, const BIGNUM **g) ++{ ++ if (p) *p = d->p; ++ if (q) *q = d->q; ++ if (g) *g = d->g; ++} ++ ++static void DSA_get0_key(const DSA *d, const BIGNUM **pub_key, const BIGNUM **priv_key) ++{ ++ if (pub_key) *pub_key = d->pub_key; ++ if (priv_key) *priv_key = d->priv_key; ++} ++ ++static int DSA_set0_pqg(DSA *d, BIGNUM *p, BIGNUM *q, BIGNUM *g) ++{ ++ if (p) { BN_free(d->p); d->p = p; } ++ if (q) { BN_free(d->q); d->q = q; } ++ if (g) { BN_free(d->g); d->g = g; } ++ return 1; ++} ++ ++static int DSA_set0_key(DSA *d, BIGNUM *pub_key, BIGNUM *priv_key) ++{ ++ if (pub_key) { BN_free(d->pub_key); d->pub_key = pub_key; } ++ if (priv_key) { BN_free(d->priv_key); d->priv_key = priv_key; } ++ return 1; ++} ++ ++static void DH_get0_pqg(const DH *dh, const BIGNUM **p, const BIGNUM **q, const BIGNUM **g) ++{ ++ if (p) *p = dh->p; ++ if (q) *q = dh->q; ++ if (g) *g = dh->g; ++} ++ ++static void DH_get0_key(const DH *dh, const BIGNUM **pub_key, const BIGNUM **priv_key) ++{ ++ if (pub_key) *pub_key = dh->pub_key; ++ if (priv_key) *priv_key = dh->priv_key; ++} ++ ++static int DH_set0_pqg(DH *dh, BIGNUM *p, BIGNUM *q, BIGNUM *g) ++{ ++ if (p) { BN_free(dh->p); dh->p = p; } ++ if (q) { BN_free(dh->q); dh->q = q; } ++ if (g) { BN_free(dh->g); dh->g = g; } ++ return 1; ++} ++ ++static int DH_set0_key(DH *dh, BIGNUM *pub_key, BIGNUM *priv_key) ++{ ++ if (pub_key) { BN_free(dh->pub_key); dh->pub_key = pub_key; } ++ if (priv_key) { BN_free(dh->priv_key); dh->priv_key = priv_key; } ++ return 1; ++} ++ ++static ASN1_OCTET_STRING *X509_EXTENSION_get_data(X509_EXTENSION *ext) ++{ ++ return ext->value; ++} ++ ++static const unsigned char *ASN1_STRING_get0_data(const ASN1_STRING *x) ++{ ++ return ASN1_STRING_data((ASN1_STRING *)x); ++} ++ ++static EVP_MD_CTX *EVP_MD_CTX_new(void) ++{ ++ EVP_MD_CTX *ctx = OPENSSL_malloc(sizeof(EVP_MD_CTX)); ++ if (ctx) memset(ctx, 0, sizeof(EVP_MD_CTX)); ++ return ctx; ++} ++ ++static void EVP_MD_CTX_free(EVP_MD_CTX *ctx) ++{ ++ if (ctx) { ++ EVP_MD_CTX_cleanup(ctx); ++ OPENSSL_free(ctx); ++ } ++} ++ ++static EVP_CIPHER_CTX *EVP_CIPHER_CTX_new(void) ++{ ++ EVP_CIPHER_CTX *ctx = OPENSSL_malloc(sizeof(EVP_CIPHER_CTX)); ++ if (ctx) memset(ctx, 0, sizeof(EVP_CIPHER_CTX)); ++ return ctx; ++} ++ ++static void EVP_CIPHER_CTX_free(EVP_CIPHER_CTX *ctx) ++{ ++ if (ctx) { ++ EVP_CIPHER_CTX_cleanup(ctx); ++ OPENSSL_free(ctx); ++ } ++} ++ ++static void EVP_CIPHER_CTX_reset(EVP_CIPHER_CTX *ctx) ++{ ++ EVP_CIPHER_CTX_cleanup(ctx); ++} ++ ++#endif /* OPENSSL_VERSION_NUMBER >= 0x10100000L */ ++ + /* Common */ + #include + +@@ -1292,9 +1473,11 @@ PHP_MINFO_FUNCTION(openssl) + */ + PHP_MSHUTDOWN_FUNCTION(openssl) + { ++#if OPENSSL_VERSION_NUMBER < 0x10100000L + EVP_cleanup(); ++#endif + +-#if OPENSSL_VERSION_NUMBER >= 0x00090805f ++#if OPENSSL_VERSION_NUMBER >= 0x00090805f && OPENSSL_VERSION_NUMBER < 0x10100000L + /* prevent accessing locking callback from unloaded extension */ + CRYPTO_set_locking_callback(NULL); + /* free allocated error strings */ +@@ -1604,7 +1787,7 @@ PHP_FUNCTION(openssl_spki_verify) + goto cleanup; + } + +- pkey = X509_PUBKEY_get(spki->spkac->pubkey); ++ pkey = NETSCAPE_SPKI_get_pubkey(spki); + if (pkey == NULL) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to acquire signed public key"); + goto cleanup; +@@ -1660,7 +1843,7 @@ PHP_FUNCTION(openssl_spki_export) + goto cleanup; + } + +- pkey = X509_PUBKEY_get(spki->spkac->pubkey); ++ pkey = NETSCAPE_SPKI_get_pubkey(spki); + if (pkey == NULL) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to acquire signed public key"); + goto cleanup; +@@ -1723,7 +1906,7 @@ PHP_FUNCTION(openssl_spki_export_challenge) + goto cleanup; + } + +- RETVAL_STRING((char *) ASN1_STRING_data(spki->spkac->challenge), 1); ++ RETVAL_STRING((char *)ASN1_STRING_get0_data(spki->spkac->challenge), 1); + goto cleanup; + + cleanup: +@@ -1901,8 +2084,11 @@ static int openssl_x509v3_subjectAltName(BIO *bio, X509_EXTENSION *extension) + return -1; + } + +- p = extension->value->data; +- length = extension->value->length; ++ { ++ ASN1_OCTET_STRING *ext_data = X509_EXTENSION_get_data(extension); ++ p = ASN1_STRING_get0_data(ext_data); ++ length = ASN1_STRING_length(ext_data); ++ } + if (method->it) { + names = (GENERAL_NAMES*)(ASN1_item_d2i(NULL, &p, length, + ASN1_ITEM_ptr(method->it))); +@@ -1979,8 +2165,13 @@ PHP_FUNCTION(openssl_x509_parse) + } + array_init(return_value); + +- if (cert->name) { +- add_assoc_string(return_value, "name", cert->name, 1); ++ { ++ char *cert_name = X509_NAME_oneline( ++ X509_get_subject_name(cert), NULL, 0); ++ if (cert_name) { ++ add_assoc_string(return_value, "name", cert_name, 1); ++ OPENSSL_free(cert_name); ++ } + } + /* add_assoc_bool(return_value, "valid", cert->valid); */ + +@@ -3482,13 +3673,20 @@ static int php_openssl_is_private_key(EVP_PKEY* pkey TSRMLS_DC) + { + assert(pkey != NULL); + +- switch (pkey->type) { ++ switch (EVP_PKEY_id(pkey)) { + #ifndef NO_RSA + case EVP_PKEY_RSA: + case EVP_PKEY_RSA2: +- assert(pkey->pkey.rsa != NULL); +- if (pkey->pkey.rsa != NULL && (NULL == pkey->pkey.rsa->p || NULL == pkey->pkey.rsa->q)) { +- return 0; ++ { ++ RSA *rsa = EVP_PKEY_get0_RSA(pkey); ++ const BIGNUM *p, *q; ++ assert(rsa != NULL); ++ if (rsa != NULL) { ++ RSA_get0_factors(rsa, &p, &q); ++ if (NULL == p || NULL == q) { ++ return 0; ++ } ++ } + } + break; + #endif +@@ -3498,28 +3696,46 @@ static int php_openssl_is_private_key(EVP_PKEY* pkey TSRMLS_DC) + case EVP_PKEY_DSA2: + case EVP_PKEY_DSA3: + case EVP_PKEY_DSA4: +- assert(pkey->pkey.dsa != NULL); +- +- if (NULL == pkey->pkey.dsa->p || NULL == pkey->pkey.dsa->q || NULL == pkey->pkey.dsa->priv_key){ +- return 0; ++ { ++ DSA *dsa = EVP_PKEY_get0_DSA(pkey); ++ const BIGNUM *p, *q, *priv_key; ++ assert(dsa != NULL); ++ if (dsa != NULL) { ++ DSA_get0_pqg(dsa, &p, &q, NULL); ++ DSA_get0_key(dsa, NULL, &priv_key); ++ if (NULL == p || NULL == q || NULL == priv_key) { ++ return 0; ++ } ++ } + } + break; + #endif + #ifndef NO_DH + case EVP_PKEY_DH: +- assert(pkey->pkey.dh != NULL); +- +- if (NULL == pkey->pkey.dh->p || NULL == pkey->pkey.dh->priv_key) { +- return 0; ++ { ++ DH *dh = EVP_PKEY_get0_DH(pkey); ++ const BIGNUM *p, *priv_key; ++ assert(dh != NULL); ++ if (dh != NULL) { ++ DH_get0_pqg(dh, &p, NULL, NULL); ++ DH_get0_key(dh, NULL, &priv_key); ++ if (NULL == p || NULL == priv_key) { ++ return 0; ++ } ++ } + } + break; + #endif + #ifdef HAVE_EVP_PKEY_EC + case EVP_PKEY_EC: +- assert(pkey->pkey.ec != NULL); +- +- if ( NULL == EC_KEY_get0_private_key(pkey->pkey.ec)) { +- return 0; ++ { ++ EC_KEY *ec = EVP_PKEY_get0_EC_KEY(pkey); ++ assert(ec != NULL); ++ if (ec != NULL) { ++ if (NULL == EC_KEY_get0_private_key(ec)) { ++ return 0; ++ } ++ } + } + break; + #endif +@@ -3531,33 +3747,108 @@ static int php_openssl_is_private_key(EVP_PKEY* pkey TSRMLS_DC) + } + /* }}} */ + +-#define OPENSSL_PKEY_GET_BN(_type, _name) do { \ +- if (pkey->pkey._type->_name != NULL) { \ +- int len = BN_num_bytes(pkey->pkey._type->_name); \ +- char *str = emalloc(len + 1); \ +- BN_bn2bin(pkey->pkey._type->_name, (unsigned char*)str); \ +- str[len] = 0; \ +- add_assoc_stringl(_type, #_name, str, len, 0); \ +- } \ ++#define OPENSSL_PKEY_GET_BN(_type, _name) do { \ ++ const BIGNUM *bn = php_openssl_get_bn_##_type##_##_name(pkey); \ ++ if (bn != NULL) { \ ++ int len = BN_num_bytes(bn); \ ++ char *str = emalloc(len + 1); \ ++ BN_bn2bin(bn, (unsigned char*)str); \ ++ str[len] = 0; \ ++ add_assoc_stringl(_type, #_name, str, len, 0); \ ++ } \ + } while (0) + +-#define OPENSSL_PKEY_SET_BN(_ht, _type, _name) do { \ +- zval **bn; \ +- if (zend_hash_find(_ht, #_name, sizeof(#_name), (void**)&bn) == SUCCESS && \ +- Z_TYPE_PP(bn) == IS_STRING) { \ +- _type->_name = BN_bin2bn( \ +- (unsigned char*)Z_STRVAL_PP(bn), \ +- Z_STRLEN_PP(bn), NULL); \ +- } \ ++#define OPENSSL_PKEY_SET_BN(_ht, _type, _name) do { \ ++ zval **bn; \ ++ if (zend_hash_find(_ht, #_name, sizeof(#_name), (void**)&bn) == SUCCESS && \ ++ Z_TYPE_PP(bn) == IS_STRING) { \ ++ _type##_bn_##_name = BN_bin2bn( \ ++ (unsigned char*)Z_STRVAL_PP(bn), \ ++ Z_STRLEN_PP(bn), NULL); \ ++ } \ + } while (0); + ++/* Accessor helpers for OPENSSL_PKEY_GET_BN macro - RSA */ ++static const BIGNUM *php_openssl_get_bn_rsa_n(EVP_PKEY *pkey) { ++ RSA *r = EVP_PKEY_get0_RSA(pkey); return r ? RSA_get0_n(r) : NULL; ++} ++static const BIGNUM *php_openssl_get_bn_rsa_e(EVP_PKEY *pkey) { ++ RSA *r = EVP_PKEY_get0_RSA(pkey); return r ? RSA_get0_e(r) : NULL; ++} ++static const BIGNUM *php_openssl_get_bn_rsa_d(EVP_PKEY *pkey) { ++ RSA *r = EVP_PKEY_get0_RSA(pkey); return r ? RSA_get0_d(r) : NULL; ++} ++static const BIGNUM *php_openssl_get_bn_rsa_p(EVP_PKEY *pkey) { ++ RSA *r = EVP_PKEY_get0_RSA(pkey); const BIGNUM *v = NULL; ++ if (r) RSA_get0_factors(r, &v, NULL); return v; ++} ++static const BIGNUM *php_openssl_get_bn_rsa_q(EVP_PKEY *pkey) { ++ RSA *r = EVP_PKEY_get0_RSA(pkey); const BIGNUM *v = NULL; ++ if (r) RSA_get0_factors(r, NULL, &v); return v; ++} ++static const BIGNUM *php_openssl_get_bn_rsa_dmp1(EVP_PKEY *pkey) { ++ RSA *r = EVP_PKEY_get0_RSA(pkey); const BIGNUM *v = NULL; ++ if (r) RSA_get0_crt_params(r, &v, NULL, NULL); return v; ++} ++static const BIGNUM *php_openssl_get_bn_rsa_dmq1(EVP_PKEY *pkey) { ++ RSA *r = EVP_PKEY_get0_RSA(pkey); const BIGNUM *v = NULL; ++ if (r) RSA_get0_crt_params(r, NULL, &v, NULL); return v; ++} ++static const BIGNUM *php_openssl_get_bn_rsa_iqmp(EVP_PKEY *pkey) { ++ RSA *r = EVP_PKEY_get0_RSA(pkey); const BIGNUM *v = NULL; ++ if (r) RSA_get0_crt_params(r, NULL, NULL, &v); return v; ++} ++ ++/* Accessor helpers for OPENSSL_PKEY_GET_BN macro - DSA */ ++static const BIGNUM *php_openssl_get_bn_dsa_p(EVP_PKEY *pkey) { ++ DSA *d = EVP_PKEY_get0_DSA(pkey); const BIGNUM *v = NULL; ++ if (d) DSA_get0_pqg(d, &v, NULL, NULL); return v; ++} ++static const BIGNUM *php_openssl_get_bn_dsa_q(EVP_PKEY *pkey) { ++ DSA *d = EVP_PKEY_get0_DSA(pkey); const BIGNUM *v = NULL; ++ if (d) DSA_get0_pqg(d, NULL, &v, NULL); return v; ++} ++static const BIGNUM *php_openssl_get_bn_dsa_g(EVP_PKEY *pkey) { ++ DSA *d = EVP_PKEY_get0_DSA(pkey); const BIGNUM *v = NULL; ++ if (d) DSA_get0_pqg(d, NULL, NULL, &v); return v; ++} ++static const BIGNUM *php_openssl_get_bn_dsa_priv_key(EVP_PKEY *pkey) { ++ DSA *d = EVP_PKEY_get0_DSA(pkey); const BIGNUM *v = NULL; ++ if (d) DSA_get0_key(d, NULL, &v); return v; ++} ++static const BIGNUM *php_openssl_get_bn_dsa_pub_key(EVP_PKEY *pkey) { ++ DSA *d = EVP_PKEY_get0_DSA(pkey); const BIGNUM *v = NULL; ++ if (d) DSA_get0_key(d, &v, NULL); return v; ++} ++ ++/* Accessor helpers for OPENSSL_PKEY_GET_BN macro - DH */ ++static const BIGNUM *php_openssl_get_bn_dh_p(EVP_PKEY *pkey) { ++ DH *dh = EVP_PKEY_get0_DH(pkey); const BIGNUM *v = NULL; ++ if (dh) DH_get0_pqg(dh, &v, NULL, NULL); return v; ++} ++static const BIGNUM *php_openssl_get_bn_dh_g(EVP_PKEY *pkey) { ++ DH *dh = EVP_PKEY_get0_DH(pkey); const BIGNUM *v = NULL; ++ if (dh) DH_get0_pqg(dh, NULL, NULL, &v); return v; ++} ++static const BIGNUM *php_openssl_get_bn_dh_priv_key(EVP_PKEY *pkey) { ++ DH *dh = EVP_PKEY_get0_DH(pkey); const BIGNUM *v = NULL; ++ if (dh) DH_get0_key(dh, NULL, &v); return v; ++} ++static const BIGNUM *php_openssl_get_bn_dh_pub_key(EVP_PKEY *pkey) { ++ DH *dh = EVP_PKEY_get0_DH(pkey); const BIGNUM *v = NULL; ++ if (dh) DH_get0_key(dh, &v, NULL); return v; ++} ++ + /* {{{ php_openssl_pkey_init_dsa */ + zend_bool php_openssl_pkey_init_dsa(DSA *dsa) + { +- if (!dsa->p || !dsa->q || !dsa->g) { ++ const BIGNUM *p, *q, *g, *pub_key, *priv_key; ++ DSA_get0_pqg(dsa, &p, &q, &g); ++ DSA_get0_key(dsa, &pub_key, &priv_key); ++ if (!p || !q || !g) { + return 0; + } +- if (dsa->priv_key || dsa->pub_key) { ++ if (priv_key || pub_key) { + return 1; + } + PHP_OPENSSL_RAND_ADD_TIME(); +@@ -3566,7 +3857,8 @@ zend_bool php_openssl_pkey_init_dsa(DSA *dsa) + } + /* if BN_mod_exp return -1, then DSA_generate_key succeed for failed key + * so we need to double check that public key is created */ +- if (!dsa->pub_key || BN_is_zero(dsa->pub_key)) { ++ DSA_get0_key(dsa, &pub_key, NULL); ++ if (!pub_key || BN_is_zero(pub_key)) { + return 0; + } + /* all good */ +@@ -3577,10 +3869,13 @@ zend_bool php_openssl_pkey_init_dsa(DSA *dsa) + /* {{{ php_openssl_pkey_init_dh */ + zend_bool php_openssl_pkey_init_dh(DH *dh) + { +- if (!dh->p || !dh->g) { ++ const BIGNUM *p, *g, *pub_key; ++ DH_get0_pqg(dh, &p, NULL, &g); ++ DH_get0_key(dh, &pub_key, NULL); ++ if (!p || !g) { + return 0; + } +- if (dh->pub_key) { ++ if (pub_key) { + return 1; + } + PHP_OPENSSL_RAND_ADD_TIME(); +@@ -3614,6 +3909,9 @@ PHP_FUNCTION(openssl_pkey_new) + if (pkey) { + RSA *rsa = RSA_new(); + if (rsa) { ++ BIGNUM *rsa_bn_n = NULL, *rsa_bn_e = NULL, *rsa_bn_d = NULL; ++ BIGNUM *rsa_bn_p = NULL, *rsa_bn_q = NULL; ++ BIGNUM *rsa_bn_dmp1 = NULL, *rsa_bn_dmq1 = NULL, *rsa_bn_iqmp = NULL; + OPENSSL_PKEY_SET_BN(Z_ARRVAL_PP(data), rsa, n); + OPENSSL_PKEY_SET_BN(Z_ARRVAL_PP(data), rsa, e); + OPENSSL_PKEY_SET_BN(Z_ARRVAL_PP(data), rsa, d); +@@ -3622,7 +3920,11 @@ PHP_FUNCTION(openssl_pkey_new) + OPENSSL_PKEY_SET_BN(Z_ARRVAL_PP(data), rsa, dmp1); + OPENSSL_PKEY_SET_BN(Z_ARRVAL_PP(data), rsa, dmq1); + OPENSSL_PKEY_SET_BN(Z_ARRVAL_PP(data), rsa, iqmp); +- if (rsa->n && rsa->d) { ++ /* Transfer ownership of BIGNUMs to the RSA struct */ ++ RSA_set0_key(rsa, rsa_bn_n, rsa_bn_e, rsa_bn_d); ++ RSA_set0_factors(rsa, rsa_bn_p, rsa_bn_q); ++ RSA_set0_crt_params(rsa, rsa_bn_dmp1, rsa_bn_dmq1, rsa_bn_iqmp); ++ if (rsa_bn_n && rsa_bn_d) { + if (EVP_PKEY_assign_RSA(pkey, rsa)) { + RETURN_RESOURCE(zend_list_insert(pkey, le_key TSRMLS_CC)); + } +@@ -3638,11 +3940,15 @@ PHP_FUNCTION(openssl_pkey_new) + if (pkey) { + DSA *dsa = DSA_new(); + if (dsa) { ++ BIGNUM *dsa_bn_p = NULL, *dsa_bn_q = NULL, *dsa_bn_g = NULL; ++ BIGNUM *dsa_bn_priv_key = NULL, *dsa_bn_pub_key = NULL; + OPENSSL_PKEY_SET_BN(Z_ARRVAL_PP(data), dsa, p); + OPENSSL_PKEY_SET_BN(Z_ARRVAL_PP(data), dsa, q); + OPENSSL_PKEY_SET_BN(Z_ARRVAL_PP(data), dsa, g); + OPENSSL_PKEY_SET_BN(Z_ARRVAL_PP(data), dsa, priv_key); + OPENSSL_PKEY_SET_BN(Z_ARRVAL_PP(data), dsa, pub_key); ++ DSA_set0_pqg(dsa, dsa_bn_p, dsa_bn_q, dsa_bn_g); ++ DSA_set0_key(dsa, dsa_bn_pub_key, dsa_bn_priv_key); + if (php_openssl_pkey_init_dsa(dsa)) { + if (EVP_PKEY_assign_DSA(pkey, dsa)) { + RETURN_RESOURCE(zend_list_insert(pkey, le_key TSRMLS_CC)); +@@ -3659,10 +3965,14 @@ PHP_FUNCTION(openssl_pkey_new) + if (pkey) { + DH *dh = DH_new(); + if (dh) { ++ BIGNUM *dh_bn_p = NULL, *dh_bn_g = NULL; ++ BIGNUM *dh_bn_priv_key = NULL, *dh_bn_pub_key = NULL; + OPENSSL_PKEY_SET_BN(Z_ARRVAL_PP(data), dh, p); + OPENSSL_PKEY_SET_BN(Z_ARRVAL_PP(data), dh, g); + OPENSSL_PKEY_SET_BN(Z_ARRVAL_PP(data), dh, priv_key); + OPENSSL_PKEY_SET_BN(Z_ARRVAL_PP(data), dh, pub_key); ++ DH_set0_pqg(dh, dh_bn_p, NULL, dh_bn_g); ++ DH_set0_key(dh, dh_bn_pub_key, dh_bn_priv_key); + if (php_openssl_pkey_init_dh(dh)) { + if (EVP_PKEY_assign_DH(pkey, dh)) { + RETURN_RESOURCE(zend_list_insert(pkey, le_key TSRMLS_CC)); +@@ -3928,12 +4238,12 @@ PHP_FUNCTION(openssl_pkey_get_details) + /*TODO: Use the real values once the openssl constants are used + * See the enum at the top of this file + */ +- switch (EVP_PKEY_type(pkey->type)) { ++ switch (EVP_PKEY_base_id(pkey)) { + case EVP_PKEY_RSA: + case EVP_PKEY_RSA2: + ktype = OPENSSL_KEYTYPE_RSA; + +- if (pkey->pkey.rsa != NULL) { ++ if (EVP_PKEY_get0_RSA(pkey) != NULL) { + zval *rsa; + + ALLOC_INIT_ZVAL(rsa); +@@ -3949,14 +4259,14 @@ PHP_FUNCTION(openssl_pkey_get_details) + add_assoc_zval(return_value, "rsa", rsa); + } + +- break; ++ break; + case EVP_PKEY_DSA: + case EVP_PKEY_DSA2: + case EVP_PKEY_DSA3: + case EVP_PKEY_DSA4: + ktype = OPENSSL_KEYTYPE_DSA; + +- if (pkey->pkey.dsa != NULL) { ++ if (EVP_PKEY_get0_DSA(pkey) != NULL) { + zval *dsa; + + ALLOC_INIT_ZVAL(dsa); +@@ -3973,7 +4283,7 @@ PHP_FUNCTION(openssl_pkey_get_details) + + ktype = OPENSSL_KEYTYPE_DH; + +- if (pkey->pkey.dh != NULL) { ++ if (EVP_PKEY_get0_DH(pkey) != NULL) { + zval *dh; + + ALLOC_INIT_ZVAL(dh); +@@ -3989,7 +4299,7 @@ PHP_FUNCTION(openssl_pkey_get_details) + #ifdef HAVE_EVP_PKEY_EC + case EVP_PKEY_EC: + ktype = OPENSSL_KEYTYPE_EC; +- if (pkey->pkey.ec != NULL) { ++ if (EVP_PKEY_get0_EC_KEY(pkey) != NULL) { + zval *ec; + const EC_GROUP *ec_group; + int nid; +@@ -3998,7 +4308,7 @@ PHP_FUNCTION(openssl_pkey_get_details) + // openssl recommends a buffer length of 80 + char oir_buf[80]; + +- ec_group = EC_KEY_get0_group(EVP_PKEY_get1_EC_KEY(pkey)); ++ ec_group = EC_KEY_get0_group(EVP_PKEY_get0_EC_KEY(pkey)); + + // Curve nid (numerical identifier) used for ASN1 mapping + nid = EC_GROUP_get_curve_name(ec_group); +@@ -4546,13 +4856,13 @@ PHP_FUNCTION(openssl_private_encrypt) + cryptedlen = EVP_PKEY_size(pkey); + cryptedbuf = emalloc(cryptedlen + 1); + +- switch (pkey->type) { ++ switch (EVP_PKEY_id(pkey)) { + case EVP_PKEY_RSA: + case EVP_PKEY_RSA2: +- successful = (RSA_private_encrypt(data_len, +- (unsigned char *)data, +- cryptedbuf, +- pkey->pkey.rsa, ++ successful = (RSA_private_encrypt(data_len, ++ (unsigned char *)data, ++ cryptedbuf, ++ EVP_PKEY_get0_RSA(pkey), + padding) == cryptedlen); + break; + default: +@@ -4604,13 +4914,13 @@ PHP_FUNCTION(openssl_private_decrypt) + cryptedlen = EVP_PKEY_size(pkey); + crypttemp = emalloc(cryptedlen + 1); + +- switch (pkey->type) { ++ switch (EVP_PKEY_id(pkey)) { + case EVP_PKEY_RSA: + case EVP_PKEY_RSA2: +- cryptedlen = RSA_private_decrypt(data_len, +- (unsigned char *)data, +- crypttemp, +- pkey->pkey.rsa, ++ cryptedlen = RSA_private_decrypt(data_len, ++ (unsigned char *)data, ++ crypttemp, ++ EVP_PKEY_get0_RSA(pkey), + padding); + if (cryptedlen != -1) { + cryptedbuf = emalloc(cryptedlen + 1); +@@ -4669,13 +4979,13 @@ PHP_FUNCTION(openssl_public_encrypt) + cryptedlen = EVP_PKEY_size(pkey); + cryptedbuf = emalloc(cryptedlen + 1); + +- switch (pkey->type) { ++ switch (EVP_PKEY_id(pkey)) { + case EVP_PKEY_RSA: + case EVP_PKEY_RSA2: +- successful = (RSA_public_encrypt(data_len, +- (unsigned char *)data, +- cryptedbuf, +- pkey->pkey.rsa, ++ successful = (RSA_public_encrypt(data_len, ++ (unsigned char *)data, ++ cryptedbuf, ++ EVP_PKEY_get0_RSA(pkey), + padding) == cryptedlen); + break; + default: +@@ -4728,13 +5038,13 @@ PHP_FUNCTION(openssl_public_decrypt) + cryptedlen = EVP_PKEY_size(pkey); + crypttemp = emalloc(cryptedlen + 1); + +- switch (pkey->type) { ++ switch (EVP_PKEY_id(pkey)) { + case EVP_PKEY_RSA: + case EVP_PKEY_RSA2: +- cryptedlen = RSA_public_decrypt(data_len, +- (unsigned char *)data, +- crypttemp, +- pkey->pkey.rsa, ++ cryptedlen = RSA_public_decrypt(data_len, ++ (unsigned char *)data, ++ crypttemp, ++ EVP_PKEY_get0_RSA(pkey), + padding); + if (cryptedlen != -1) { + cryptedbuf = emalloc(cryptedlen + 1); +@@ -4798,7 +5108,7 @@ PHP_FUNCTION(openssl_sign) + long keyresource = -1; + char * data; + int data_len; +- EVP_MD_CTX md_ctx; ++ EVP_MD_CTX *md_ctx; + zval *method = NULL; + long signature_algo = OPENSSL_ALGO_SHA1; + const EVP_MD *mdtype; +@@ -4831,9 +5141,14 @@ PHP_FUNCTION(openssl_sign) + siglen = EVP_PKEY_size(pkey); + sigbuf = emalloc(siglen + 1); + +- EVP_SignInit(&md_ctx, mdtype); +- EVP_SignUpdate(&md_ctx, data, data_len); +- if (EVP_SignFinal (&md_ctx, sigbuf,(unsigned int *)&siglen, pkey)) { ++ md_ctx = EVP_MD_CTX_new(); ++ if (!md_ctx) { ++ efree(sigbuf); ++ RETURN_FALSE; ++ } ++ EVP_SignInit(md_ctx, mdtype); ++ EVP_SignUpdate(md_ctx, data, data_len); ++ if (EVP_SignFinal(md_ctx, sigbuf, (unsigned int *)&siglen, pkey)) { + zval_dtor(signature); + sigbuf[siglen] = '\0'; + ZVAL_STRINGL(signature, (char *)sigbuf, siglen, 0); +@@ -4842,7 +5157,7 @@ PHP_FUNCTION(openssl_sign) + efree(sigbuf); + RETVAL_FALSE; + } +- EVP_MD_CTX_cleanup(&md_ctx); ++ EVP_MD_CTX_free(md_ctx); + if (keyresource == -1) { + EVP_PKEY_free(pkey); + } +@@ -4856,7 +5171,7 @@ PHP_FUNCTION(openssl_verify) + zval **key; + EVP_PKEY *pkey; + int err; +- EVP_MD_CTX md_ctx; ++ EVP_MD_CTX *md_ctx; + const EVP_MD *mdtype; + long keyresource = -1; + char * data; int data_len; +@@ -4890,10 +5205,14 @@ PHP_FUNCTION(openssl_verify) + RETURN_FALSE; + } + +- EVP_VerifyInit (&md_ctx, mdtype); +- EVP_VerifyUpdate (&md_ctx, data, data_len); +- err = EVP_VerifyFinal (&md_ctx, (unsigned char *)signature, signature_len, pkey); +- EVP_MD_CTX_cleanup(&md_ctx); ++ md_ctx = EVP_MD_CTX_new(); ++ if (!md_ctx) { ++ RETURN_FALSE; ++ } ++ EVP_VerifyInit (md_ctx, mdtype); ++ EVP_VerifyUpdate (md_ctx, data, data_len); ++ err = EVP_VerifyFinal (md_ctx, (unsigned char *)signature, signature_len, pkey); ++ EVP_MD_CTX_free(md_ctx); + + if (keyresource == -1) { + EVP_PKEY_free(pkey); +@@ -4917,7 +5236,7 @@ PHP_FUNCTION(openssl_seal) + char *method =NULL; + int method_len = 0; + const EVP_CIPHER *cipher; +- EVP_CIPHER_CTX ctx; ++ EVP_CIPHER_CTX *ctx; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "szza/|s", &data, &data_len, &sealdata, &ekeys, &pubkeys, &method, &method_len) == FAILURE) { + return; +@@ -4967,27 +5286,32 @@ PHP_FUNCTION(openssl_seal) + i++; + } + +- if (!EVP_EncryptInit(&ctx,cipher,NULL,NULL)) { ++ ctx = EVP_CIPHER_CTX_new(); ++ if (!ctx) { + RETVAL_FALSE; +- EVP_CIPHER_CTX_cleanup(&ctx); ++ goto clean_exit; ++ } ++ if (!EVP_EncryptInit(ctx, cipher, NULL, NULL)) { ++ RETVAL_FALSE; ++ EVP_CIPHER_CTX_free(ctx); + goto clean_exit; + } + + #if 0 + /* Need this if allow ciphers that require initialization vector */ +- ivlen = EVP_CIPHER_CTX_iv_length(&ctx); ++ ivlen = EVP_CIPHER_CTX_iv_length(ctx); + iv = ivlen ? emalloc(ivlen + 1) : NULL; + #endif + /* allocate one byte extra to make room for \0 */ +- buf = emalloc(data_len + EVP_CIPHER_CTX_block_size(&ctx)); +- EVP_CIPHER_CTX_cleanup(&ctx); ++ buf = emalloc(data_len + EVP_CIPHER_CTX_block_size(ctx)); ++ EVP_CIPHER_CTX_reset(ctx); + +- if (EVP_SealInit(&ctx, cipher, eks, eksl, NULL, pkeys, nkeys) <= 0 || +- !EVP_SealUpdate(&ctx, buf, &len1, (unsigned char *)data, data_len) || +- !EVP_SealFinal(&ctx, buf + len1, &len2)) { ++ if (EVP_SealInit(ctx, cipher, eks, eksl, NULL, pkeys, nkeys) <= 0 || ++ !EVP_SealUpdate(ctx, buf, &len1, (unsigned char *)data, data_len) || ++ !EVP_SealFinal(ctx, buf + len1, &len2)) { + RETVAL_FALSE; + efree(buf); +- EVP_CIPHER_CTX_cleanup(&ctx); ++ EVP_CIPHER_CTX_free(ctx); + goto clean_exit; + } + +@@ -5018,7 +5342,7 @@ PHP_FUNCTION(openssl_seal) + efree(buf); + } + RETVAL_LONG(len1 + len2); +- EVP_CIPHER_CTX_cleanup(&ctx); ++ EVP_CIPHER_CTX_free(ctx); + + clean_exit: + for (i=0; i keylen) { +- EVP_CIPHER_CTX_set_key_length(&cipher_ctx, password_len); ++ EVP_CIPHER_CTX_set_key_length(cipher_ctx, password_len); + } +- EVP_EncryptInit_ex(&cipher_ctx, NULL, NULL, key, (unsigned char *)iv); ++ EVP_EncryptInit_ex(cipher_ctx, NULL, NULL, key, (unsigned char *)iv); + if (options & OPENSSL_ZERO_PADDING) { +- EVP_CIPHER_CTX_set_padding(&cipher_ctx, 0); ++ EVP_CIPHER_CTX_set_padding(cipher_ctx, 0); + } + if (data_len > 0) { +- EVP_EncryptUpdate(&cipher_ctx, outbuf, &i, (unsigned char *)data, data_len); ++ EVP_EncryptUpdate(cipher_ctx, outbuf, &i, (unsigned char *)data, data_len); + } + outlen = i; +- if (EVP_EncryptFinal(&cipher_ctx, (unsigned char *)outbuf + i, &i)) { ++ if (EVP_EncryptFinal(cipher_ctx, (unsigned char *)outbuf + i, &i)) { + outlen += i; + if (options & OPENSSL_RAW_DATA) { + outbuf[outlen] = '\0'; +@@ -5298,10 +5642,11 @@ PHP_FUNCTION(openssl_encrypt) + if (key != (unsigned char*)password) { + efree(key); + } ++encrypt_cleankeys: + if (free_iv) { + efree(iv); + } +- EVP_CIPHER_CTX_cleanup(&cipher_ctx); ++ EVP_CIPHER_CTX_free(cipher_ctx); + } + /* }}} */ + +@@ -5313,7 +5658,7 @@ PHP_FUNCTION(openssl_decrypt) + char *data, *method, *password, *iv = ""; + int data_len, method_len, password_len, iv_len = 0; + const EVP_CIPHER *cipher_type; +- EVP_CIPHER_CTX cipher_ctx; ++ EVP_CIPHER_CTX *cipher_ctx; + int i, outlen, keylen; + unsigned char *outbuf, *key; + int base64_str_len; +@@ -5359,17 +5704,23 @@ PHP_FUNCTION(openssl_decrypt) + outlen = data_len + EVP_CIPHER_block_size(cipher_type); + outbuf = emalloc(outlen + 1); + +- EVP_DecryptInit(&cipher_ctx, cipher_type, NULL, NULL); ++ cipher_ctx = EVP_CIPHER_CTX_new(); ++ if (!cipher_ctx) { ++ efree(outbuf); ++ RETVAL_FALSE; ++ goto decrypt_cleankeys; ++ } ++ EVP_DecryptInit(cipher_ctx, cipher_type, NULL, NULL); + if (password_len > keylen) { +- EVP_CIPHER_CTX_set_key_length(&cipher_ctx, password_len); ++ EVP_CIPHER_CTX_set_key_length(cipher_ctx, password_len); + } +- EVP_DecryptInit_ex(&cipher_ctx, NULL, NULL, key, (unsigned char *)iv); ++ EVP_DecryptInit_ex(cipher_ctx, NULL, NULL, key, (unsigned char *)iv); + if (options & OPENSSL_ZERO_PADDING) { +- EVP_CIPHER_CTX_set_padding(&cipher_ctx, 0); ++ EVP_CIPHER_CTX_set_padding(cipher_ctx, 0); + } +- EVP_DecryptUpdate(&cipher_ctx, outbuf, &i, (unsigned char *)data, data_len); ++ EVP_DecryptUpdate(cipher_ctx, outbuf, &i, (unsigned char *)data, data_len); + outlen = i; +- if (EVP_DecryptFinal(&cipher_ctx, (unsigned char *)outbuf + i, &i)) { ++ if (EVP_DecryptFinal(cipher_ctx, (unsigned char *)outbuf + i, &i)) { + outlen += i; + outbuf[outlen] = '\0'; + RETVAL_STRINGL((char *)outbuf, outlen, 0); +@@ -5380,13 +5731,14 @@ PHP_FUNCTION(openssl_decrypt) + if (key != (unsigned char*)password) { + efree(key); + } ++decrypt_cleankeys: + if (free_iv) { + efree(iv); + } + if (base64_str) { + efree(base64_str); + } +- EVP_CIPHER_CTX_cleanup(&cipher_ctx); ++ EVP_CIPHER_CTX_free(cipher_ctx); + } + /* }}} */ + +@@ -5433,14 +5785,17 @@ PHP_FUNCTION(openssl_dh_compute_key) + return; + } + ZEND_FETCH_RESOURCE(pkey, EVP_PKEY *, &key, -1, "OpenSSL key", le_key); +- if (!pkey || EVP_PKEY_type(pkey->type) != EVP_PKEY_DH || !pkey->pkey.dh) { ++ if (!pkey || EVP_PKEY_base_id(pkey) != EVP_PKEY_DH || !EVP_PKEY_get0_DH(pkey)) { + RETURN_FALSE; + } + +- pub = BN_bin2bn((unsigned char*)pub_str, pub_len, NULL); ++ { ++ DH *dh = EVP_PKEY_get0_DH(pkey); ++ pub = BN_bin2bn((unsigned char*)pub_str, pub_len, NULL); + +- data = emalloc(DH_size(pkey->pkey.dh) + 1); +- len = DH_compute_key((unsigned char*)data, pub, pkey->pkey.dh); ++ data = emalloc(DH_size(dh) + 1); ++ len = DH_compute_key((unsigned char*)data, pub, dh); ++ } + + if (len >= 0) { + data[len] = 0; +@@ -2008,6 +2008,14 @@ + add_assoc_string(return_value, "alias", tmpstr, 1); + } + +- sig_nid = OBJ_obj2nid((cert)->sig_alg->algorithm); ++#if OPENSSL_VERSION_NUMBER >= 0x10100000L ++ { ++ const ASN1_OBJECT *palg = NULL; ++ X509_ALGOR_get0(&palg, NULL, NULL, X509_get0_tbs_sigalg(cert)); ++ sig_nid = OBJ_obj2nid(palg); ++ } ++#else ++ sig_nid = OBJ_obj2nid(cert->sig_alg->algorithm); ++#endif + add_assoc_string(return_value, "signatureTypeSN", (char*)OBJ_nid2sn(sig_nid), 1); + add_assoc_string(return_value, "signatureTypeLN", (char*)OBJ_nid2ln(sig_nid), 1); +@@ -3738,7 +3738,7 @@ + cipher = NULL; + } + +- switch (EVP_PKEY_type(key->type)) { ++ switch (EVP_PKEY_base_id(key)) { + #ifdef HAVE_EVP_PKEY_EC + case EVP_PKEY_EC: + pem_write = PEM_write_bio_ECPrivateKey(bio_out, EVP_PKEY_get1_EC_KEY(key), cipher, (unsigned char *)passphrase, passphrase_len, NULL, NULL); +@@ -3807,7 +3807,7 @@ + cipher = NULL; + } + +- switch (EVP_PKEY_type(key->type)) { ++ switch (EVP_PKEY_base_id(key)) { + #ifdef HAVE_EVP_PKEY_EC + case EVP_PKEY_EC: + pem_write = PEM_write_bio_ECPrivateKey(bio_out, EVP_PKEY_get1_EC_KEY(key), cipher, (unsigned char *)passphrase, passphrase_len, NULL, NULL); +diff --git a/php-src/ext/openssl/xp_ssl.c b/php-src/ext/openssl/xp_ssl.c +index d5490331..e3a8f87c 100644 +--- a/php-src/ext/openssl/xp_ssl.c ++++ b/php-src/ext/openssl/xp_ssl.c +@@ -935,7 +935,7 @@ static int set_local_cert(SSL_CTX *ctx, php_stream *stream TSRMLS_DC) /* {{{ */ + static const SSL_METHOD *php_select_crypto_method(long method_value, int is_client TSRMLS_DC) /* {{{ */ + { + if (method_value == STREAM_CRYPTO_METHOD_SSLv2) { +-#ifndef OPENSSL_NO_SSL2 ++#if !defined(OPENSSL_NO_SSL2) && OPENSSL_VERSION_NUMBER < 0x10100000L + return is_client ? SSLv2_client_method() : SSLv2_server_method(); + #else + php_error_docref(NULL TSRMLS_CC, E_WARNING, +@@ -1601,8 +1601,12 @@ static zval *capture_session_meta(SSL *ssl_handle) /* {{{ */ + case TLS1_1_VERSION: proto_str = "TLSv1.1"; break; + #endif + case TLS1_VERSION: proto_str = "TLSv1"; break; ++#ifndef OPENSSL_NO_SSL3 + case SSL3_VERSION: proto_str = "SSLv3"; break; ++#endif ++#ifdef SSL2_VERSION + case SSL2_VERSION: proto_str = "SSLv2"; break; ++#endif + default: proto_str = "UNKNOWN"; + } + +diff --git a/php-src/ext/phar/util.c b/php-src/ext/phar/util.c +--- a/php-src/ext/phar/util.c ++++ b/php-src/ext/phar/util.c +@@ -1531,7 +1531,7 @@ + BIO *in; + EVP_PKEY *key; + EVP_MD *mdtype = (EVP_MD *) EVP_sha1(); +- EVP_MD_CTX md_ctx; ++ EVP_MD_CTX *md_ctx; + #else + int tempsig; + #endif +@@ -1608,7 +1608,9 @@ + return FAILURE; + } + +- EVP_VerifyInit(&md_ctx, mdtype); ++ md_ctx = EVP_MD_CTX_new(); ++ if (!md_ctx) { if (error) { spprintf(error, 0, "out of memory"); } return FAILURE; } ++ EVP_VerifyInit(md_ctx, mdtype); + read_len = end_of_phar; + + if (read_len > sizeof(buf)) { +@@ -1620,7 +1622,7 @@ + php_stream_seek(fp, 0, SEEK_SET); + + while (read_size && (len = php_stream_read(fp, (char*)buf, read_size)) > 0) { +- EVP_VerifyUpdate (&md_ctx, buf, len); ++ EVP_VerifyUpdate (md_ctx, buf, len); + read_len -= (off_t)len; + + if (read_len < read_size) { +@@ -1628,9 +1630,9 @@ + } + } + +- if (EVP_VerifyFinal(&md_ctx, (unsigned char *)sig, sig_len, key) != 1) { ++ if (EVP_VerifyFinal(md_ctx, (unsigned char *)sig, sig_len, key) != 1) { + /* 1: signature verified, 0: signature does not match, -1: failed signature operation */ +- EVP_MD_CTX_cleanup(&md_ctx); ++ EVP_MD_CTX_free(md_ctx); + + if (error) { + spprintf(error, 0, "broken openssl signature"); +@@ -1639,7 +1641,7 @@ + return FAILURE; + } + +- EVP_MD_CTX_cleanup(&md_ctx); ++ EVP_MD_CTX_free(md_ctx); + #endif + + *signature_len = phar_hex_str((const char*)sig, sig_len, signature TSRMLS_CC); diff --git a/packages/php-wasm/compile/php/php5.6.patch b/packages/php-wasm/compile/php/php5.6.patch new file mode 100644 index 00000000000..08161307ea1 --- /dev/null +++ b/packages/php-wasm/compile/php/php5.6.patch @@ -0,0 +1,46 @@ +--- a/php-src/ext/standard/file.c 2026-03-12 23:02:36.638186358 +0100 ++++ b/php-src/ext/standard/file.c 2026-03-12 23:02:43.652954816 +0100 +@@ -1668,6 +1668,20 @@ + goto safe_to_copy; + break; + case 0: ++ // Fix for https://github.com/WordPress/wordpress-playground/issues/54: ++ // Problem: Calling copy() on an empty source file crashes the JavaScript ++ // runtime. ++ // Solution: Avoid copying empty files. Just create an empty ++ // destination file and return. ++ if (src_s.sb.st_size == 0) { ++ char *opened_path = NULL; ++ php_stream *stream = php_stream_open_wrapper(dest, "w", REPORT_ERRORS, &opened_path); ++ if (stream) { ++ php_stream_close(stream); ++ return SUCCESS; ++ } ++ return FAILURE; ++ } + break; + default: /* failed to stat file, does not exist? */ + return ret; +--- a/php-src/Zend/zend_qsort.c ++++ b/php-src/Zend/zend_qsort.c +@@ -118,8 +118,19 @@ + } + } + ++/* Wrapper with correct compare_r_func_t signature that delegates to a ++ * compare_func_t passed via the arg pointer. This avoids casting a ++ * 2-parameter function pointer to a 3-parameter type, which is undefined ++ * behavior in C and causes "null function or function signature mismatch" ++ * traps in WebAssembly. */ ++static int _zend_qsort_compare_wrapper(const void *a, const void *b TSRMLS_DC, void *arg) ++{ ++ compare_func_t compare = (compare_func_t)arg; ++ return compare(a, b TSRMLS_CC); ++} ++ + ZEND_API void zend_qsort(void *base, size_t nmemb, size_t siz, compare_func_t compare TSRMLS_DC) + { +- zend_qsort_r(base, nmemb, siz, (compare_r_func_t)compare, NULL TSRMLS_CC); ++ zend_qsort_r(base, nmemb, siz, _zend_qsort_compare_wrapper, (void *)compare TSRMLS_CC); + } + diff --git a/packages/php-wasm/compile/php/php_wasm.c b/packages/php-wasm/compile/php/php_wasm.c index 4dcdf90b33a..a6c74c947bf 100644 --- a/packages/php-wasm/compile/php/php_wasm.c +++ b/packages/php-wasm/compile/php/php_wasm.c @@ -617,7 +617,11 @@ static size_t handle_line(int type, zval *array, char *buf, size_t bufl) else if (type == 2) { bufl = strip_trailing_whitespace(buf, bufl); +#if PHP_MAJOR_VERSION >= 7 add_next_index_stringl(array, buf, bufl); +#else + add_next_index_stringl(array, buf, bufl, 1); +#endif } return bufl; } @@ -720,7 +724,11 @@ EMSCRIPTEN_KEEPALIVE int wasm_php_exec(int type, const char *cmd, zval *array, z /* Return last line from the shell command */ bufl = strip_trailing_whitespace(buf, bufl); +#if PHP_MAJOR_VERSION >= 7 RETVAL_STRINGL(buf, bufl); +#else + RETVAL_STRINGL(buf, bufl, 1); +#endif } else { /* should return NULL, but for BC we return "" */ @@ -883,8 +891,13 @@ int wasm_sapi_module_startup(sapi_module_struct *sapi_module); int wasm_sapi_shutdown_wrapper(sapi_module_struct *sapi_globals); void wasm_sapi_module_shutdown(); static int wasm_sapi_deactivate(TSRMLS_D); +#if PHP_MAJOR_VERSION >= 7 static size_t wasm_sapi_ub_write(const char *str, size_t str_length TSRMLS_DC); static size_t wasm_sapi_read_post_body(char *buffer, size_t count_bytes); +#else +static int wasm_sapi_ub_write(const char *str, unsigned int str_length TSRMLS_DC); +static int wasm_sapi_read_post_body(char *buffer, unsigned int count_bytes); +#endif #if PHP_MAJOR_VERSION >= 8 static void wasm_sapi_log_message(const char *message TSRMLS_DC, int syslog_type_int); #else @@ -1395,7 +1408,11 @@ static char *wasm_sapi_read_cookies(TSRMLS_D) * buffer: the buffer to read the request body into * count_bytes: the number of bytes to read */ +#if PHP_MAJOR_VERSION >= 7 static size_t wasm_sapi_read_post_body(char *buffer, size_t count_bytes) +#else +static int wasm_sapi_read_post_body(char *buffer, unsigned int count_bytes) +#endif { if (wasm_server_context == NULL || wasm_server_context->request_body == NULL) { @@ -1779,7 +1796,11 @@ static inline size_t wasm_sapi_single_write(const char *str, uint str_length) * str: the string to write. * str_length: the length of the string. */ +#if PHP_MAJOR_VERSION >= 7 static size_t wasm_sapi_ub_write(const char *str, size_t str_length TSRMLS_DC) +#else +static int wasm_sapi_ub_write(const char *str, unsigned int str_length TSRMLS_DC) +#endif { const char *ptr = str; uint remaining = str_length; diff --git a/packages/php-wasm/compile/php/phpwasm-emscripten-library.js b/packages/php-wasm/compile/php/phpwasm-emscripten-library.js index 186b9297b34..06316093881 100644 --- a/packages/php-wasm/compile/php/phpwasm-emscripten-library.js +++ b/packages/php-wasm/compile/php/phpwasm-emscripten-library.js @@ -661,12 +661,7 @@ const LibraryExample = { stdoutParentFd = std[1]?.parent, stderrChildFd = std[2]?.child, stderrParentFd = std[2]?.parent; - const detachPipeDataListeners = []; - cp.on('exit', function (code) { - for (const detach of detachPipeDataListeners) { - detach(); - } for (const fd of [ // The child process exited. Let's clean up its output streams: stdoutChildFd, @@ -693,27 +688,16 @@ const LibraryExample = { stdoutChildFd ); let stdoutAt = 0; - const onStdoutData = function (data) { - try { - stdoutStream.stream_ops.write( - stdoutStream, - data, - 0, - data.length, - stdoutAt - ); - stdoutAt += data.length; - } catch { - // PHP may close the child pipe before Node finishes - // draining already-buffered stdout data. Late chunks are - // no longer deliverable, so detach the listener and stop. - cp.stdout.off('data', onStdoutData); - } - }; - cp.stdout.on('data', onStdoutData); - detachPipeDataListeners.push(() => - cp.stdout.off('data', onStdoutData) - ); + cp.stdout.on('data', function (data) { + stdoutStream.stream_ops.write( + stdoutStream, + data, + 0, + data.length, + stdoutAt + ); + stdoutAt += data.length; + }); } // Pass data from child process's stderr to PHP's end of the stdout pipe. @@ -722,24 +706,16 @@ const LibraryExample = { stderrChildFd ); let stderrAt = 0; - const onStderrData = function (data) { - try { - stderrStream.stream_ops.write( - stderrStream, - data, - 0, - data.length, - stderrAt - ); - stderrAt += data.length; - } catch { - cp.stderr.off('data', onStderrData); - } - }; - cp.stderr.on('data', onStderrData); - detachPipeDataListeners.push(() => - cp.stderr.off('data', onStderrData) - ); + cp.stderr.on('data', function (data) { + stderrStream.stream_ops.write( + stderrStream, + data, + 0, + data.length, + stderrAt + ); + stderrAt += data.length; + }); } /** diff --git a/packages/php-wasm/compile/php/proc_open.c b/packages/php-wasm/compile/php/proc_open.c index 6cc1d49e718..f5a53f9582f 100644 --- a/packages/php-wasm/compile/php/proc_open.c +++ b/packages/php-wasm/compile/php/proc_open.c @@ -34,7 +34,11 @@ #include "php_globals.h" #include "SAPI.h" #include "main/php_network.h" +#if PHP_MAJOR_VERSION >= 7 #include "zend_smart_string.h" +#else +#include "ext/standard/php_smart_str.h" +#endif #if HAVE_SYS_WAIT_H #include @@ -52,6 +56,9 @@ static int le_proc_open; +#if PHP_MAJOR_VERSION >= 7 +/* ============ PHP 7+ implementation ============ */ + /* {{{ _php_array_to_envp */ static php_process_env_t _php_array_to_envp(zval *environment, int is_persistent) { @@ -713,3 +720,74 @@ PHP_FUNCTION(proc_open) RETURN_FALSE; } /* }}} */ + +#else /* PHP_MAJOR_VERSION < 7 */ +/* ============ PHP 5.x stub implementation ============ */ +/* proc_open and related functions are provided as stubs for PHP 5.x + * because process spawning doesn't truly work in WebAssembly anyway. + * The functions need to exist to satisfy the linker. */ + +static void proc_open_rsrc_dtor_legacy(zend_rsrc_list_entry *rsrc TSRMLS_DC) +{ + struct php_process_handle *proc = (struct php_process_handle*)rsrc->ptr; + if (proc) { + if (proc->pipes) { + pefree(proc->pipes, proc->is_persistent); + } + if (proc->command) { + pefree(proc->command, proc->is_persistent); + } + if (proc->env.envarray) { + pefree(proc->env.envarray, proc->is_persistent); + } + if (proc->env.envp) { + pefree(proc->env.envp, proc->is_persistent); + } + pefree(proc, proc->is_persistent); + } +} + +PHP_MINIT_FUNCTION(proc_open) +{ + le_proc_open = zend_register_list_destructors_ex( + proc_open_rsrc_dtor_legacy, NULL, "process", module_number); + return SUCCESS; +} + +PHP_FUNCTION(proc_terminate) +{ + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "proc_terminate is not supported in this WASM build"); + RETURN_FALSE; +} + +PHP_FUNCTION(proc_close) +{ + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "proc_close is not supported in this WASM build"); + RETURN_LONG(-1); +} + +PHP_FUNCTION(proc_get_status) +{ + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "proc_get_status is not supported in this WASM build"); + array_init(return_value); + add_assoc_string(return_value, "command", "", 1); + add_assoc_long(return_value, "pid", 0); + add_assoc_bool(return_value, "running", 0); + add_assoc_bool(return_value, "signaled", 0); + add_assoc_bool(return_value, "stopped", 0); + add_assoc_long(return_value, "exitcode", -1); + add_assoc_long(return_value, "termsig", 0); + add_assoc_long(return_value, "stopsig", 0); +} + +PHP_FUNCTION(proc_open) +{ + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "proc_open is not fully supported in this WASM build"); + RETURN_FALSE; +} + +#endif /* PHP_MAJOR_VERSION >= 7 */ diff --git a/packages/php-wasm/compile/php/proc_open.h b/packages/php-wasm/compile/php/proc_open.h index b1bfd1e7dc5..7add921a60b 100644 --- a/packages/php-wasm/compile/php/proc_open.h +++ b/packages/php-wasm/compile/php/proc_open.h @@ -16,6 +16,8 @@ +----------------------------------------------------------------------+ */ +#include "php.h" + typedef int php_file_descriptor_t; typedef pid_t php_process_id_t; @@ -31,7 +33,11 @@ typedef struct _php_process_env { struct php_process_handle { php_process_id_t child; int npipes; +#if PHP_MAJOR_VERSION >= 7 zend_resource **pipes; +#else + void **pipes; +#endif char *command; int is_persistent; php_process_env_t env; diff --git a/packages/php-wasm/node-builds/5-6/asyncify/5_6_40/php_5_6.wasm b/packages/php-wasm/node-builds/5-6/asyncify/5_6_40/php_5_6.wasm new file mode 100755 index 00000000000..a1eb6940190 Binary files /dev/null and b/packages/php-wasm/node-builds/5-6/asyncify/5_6_40/php_5_6.wasm differ diff --git a/packages/php-wasm/node-builds/5-6/asyncify/php_5_6.js b/packages/php-wasm/node-builds/5-6/asyncify/php_5_6.js new file mode 100644 index 00000000000..46e60d96d29 --- /dev/null +++ b/packages/php-wasm/node-builds/5-6/asyncify/php_5_6.js @@ -0,0 +1,11049 @@ +// Emscripten generates code for Node.js that uses the `require` function. +// We need to explicitly create a require function to avoid errors when running +// this code in Node.js as an ES module. +import { createRequire } from 'module'; +const require = createRequire(import.meta.url); +// Note: The path and url modules are currently needed by code injected by the php-wasm Dockerfile. +import path from 'path'; +import { fileURLToPath } from 'url'; + +// Determine the current directory path. In CJS mode, __dirname is available. +// In ESM mode, we derive it from import.meta.url. +const currentDirPath = + typeof __dirname !== 'undefined' + ? __dirname + : path.dirname(fileURLToPath(import.meta.url)); +const dependencyFilename = path.join(currentDirPath, '5_6_40', 'php_5_6.wasm'); +export { dependencyFilename }; +export const dependenciesTotalSize = 13106713; +const phpVersionString = '5.6.40'; +export function init(RuntimeName, PHPLoader) { + // The rest of the code comes from the built php.js file and esm-suffix.js + // include: shell.js + // include: minimum_runtime_check.js + // end include: minimum_runtime_check.js + // The Module object: Our interface to the outside world. We import + // and export values on it. There are various ways Module can be used: + // 1. Not defined. We create it here + // 2. A function parameter, function(moduleArg) => Promise + // 3. pre-run appended it, var Module = {}; ..generated code.. + // 4. External script tag defines var Module. + // We need to check if Module already exists (e.g. case 3 above). + // Substitution will be replaced with actual code on later stage of the build, + // this way Closure Compiler will not mangle it (e.g. case 4. above). + // Note that if you want to run closure, and also to use Module + // after the generated code, you will need to define var Module = {}; + // before the code. Then that object will be used in the code, and you + // can continue to use Module afterwards as well. + var Module = typeof PHPLoader != 'undefined' ? PHPLoader : {}; + + var ENVIRONMENT_IS_WORKER = RuntimeName === 'WORKER'; + + var ENVIRONMENT_IS_NODE = RuntimeName === 'NODE'; + + // --pre-jses are emitted after the Module integration code, so that they can + // refer to Module (if they choose; they can also define Module) + var arguments_ = []; + + var thisProgram = './this.program'; + + var quit_ = (status, toThrow) => { + throw toThrow; + }; + + var _scriptName; + + if (typeof __filename != 'undefined') { + // Node + _scriptName = __filename; + } else /*no-op*/ { + } + + // `/` should be present at the end if `scriptDirectory` is not empty + var scriptDirectory = ''; + + function locateFile(path) { + if (Module['locateFile']) { + return Module['locateFile'](path, scriptDirectory); + } + return scriptDirectory + path; + } + + // Hooks that are implemented differently in different runtime environments. + var readAsync, readBinary; + + if (ENVIRONMENT_IS_NODE) { + // These modules will usually be used on Node.js. Load them eagerly to avoid + // the complexity of lazy-loading. + var fs = require('fs'); + scriptDirectory = currentDirPath + '/'; + // include: node_shell_read.js + readBinary = (filename) => { + // We need to re-wrap `file://` strings to URLs. + filename = isFileURI(filename) ? new URL(filename) : filename; + var ret = fs.readFileSync(filename); + return ret; + }; + readAsync = async (filename, binary = true) => { + // See the comment in the `readBinary` function. + filename = isFileURI(filename) ? new URL(filename) : filename; + var ret = fs.readFileSync(filename, binary ? undefined : 'utf8'); + return ret; + }; + // end include: node_shell_read.js + if (process.argv.length > 1) { + thisProgram = process.argv[1].replace(/\\/g, '/'); + } + arguments_ = process.argv.slice(2); + // MODULARIZE will export the module in the proper place outside, we don't need to export here + if (typeof module != 'undefined') { + module['exports'] = Module; + } + quit_ = (status, toThrow) => { + process.exitCode = status; + throw toThrow; + }; + } else // Note that this includes Node.js workers when relevant (pthreads is enabled). + // Node.js workers are detected as a combination of ENVIRONMENT_IS_WORKER and + // ENVIRONMENT_IS_NODE. + { + } + + var out = console.log.bind(console); + + var err = console.error.bind(console); + + // end include: shell.js + // include: preamble.js + // === Preamble library stuff === + // Documentation for the public APIs defined in this file must be updated in: + // site/source/docs/api_reference/preamble.js.rst + // A prebuilt local version of the documentation is available at: + // site/build/text/docs/api_reference/preamble.js.txt + // You can also build docs locally as HTML or other formats in site/ + // An online HTML version (which may be of a different version of Emscripten) + // is up at http://kripken.github.io/emscripten-site/docs/api_reference/preamble.js.html + var dynamicLibraries = []; + + var wasmBinary; + + // Wasm globals + //======================================== + // Runtime essentials + //======================================== + // whether we are quitting the application. no code should run after this. + // set in exit() and abort() + var ABORT = false; + + // set by exit() and abort(). Passed to 'onExit' handler. + // NOTE: This is also used as the process return code code in shell environments + // but only when noExitRuntime is false. + var EXITSTATUS; + + /** + * Indicates whether filename is delivered via file protocol (as opposed to http/https) + * @noinline + */ var isFileURI = (filename) => filename.startsWith('file://'); + + // include: runtime_common.js + // include: runtime_stack_check.js + // end include: runtime_stack_check.js + // include: runtime_exceptions.js + // end include: runtime_exceptions.js + // include: runtime_debug.js + // end include: runtime_debug.js + // Memory management + var /** @type {!Int8Array} */ HEAP8, + /** @type {!Uint8Array} */ HEAPU8, + /** @type {!Int16Array} */ HEAP16, + /** @type {!Uint16Array} */ HEAPU16, + /** @type {!Int32Array} */ HEAP32, + /** @type {!Uint32Array} */ HEAPU32, + /** @type {!Float32Array} */ HEAPF32, + /** @type {!Float64Array} */ HEAPF64; + + // BigInt64Array type is not correctly defined in closure + var /** not-@type {!BigInt64Array} */ HEAP64, + /* BigUint64Array type is not correctly defined in closure +/** not-@type {!BigUint64Array} */ HEAPU64; + + var runtimeInitialized = false; + + var runtimeExited = false; + + function updateMemoryViews() { + var b = wasmMemory.buffer; + HEAP8 = new Int8Array(b); + HEAP16 = new Int16Array(b); + Module['HEAPU8'] = HEAPU8 = new Uint8Array(b); + HEAPU16 = new Uint16Array(b); + HEAP32 = new Int32Array(b); + Module['HEAPU32'] = HEAPU32 = new Uint32Array(b); + HEAPF32 = new Float32Array(b); + HEAPF64 = new Float64Array(b); + HEAP64 = new BigInt64Array(b); + HEAPU64 = new BigUint64Array(b); + } + + // include: memoryprofiler.js + // end include: memoryprofiler.js + // end include: runtime_common.js + var __RELOC_FUNCS__ = []; + + function preRun() { + if (Module['preRun']) { + if (typeof Module['preRun'] == 'function') + Module['preRun'] = [Module['preRun']]; + while (Module['preRun'].length) { + addOnPreRun(Module['preRun'].shift()); + } + } + // Begin ATPRERUNS hooks + callRuntimeCallbacks(onPreRuns); + } + + function initRuntime() { + runtimeInitialized = true; + callRuntimeCallbacks(__RELOC_FUNCS__); + // Begin ATINITS hooks + callRuntimeCallbacks(onInits); + if (!Module['noFSInit'] && !FS.initialized) FS.init(); + TTY.init(); + SOCKFS.root = FS.mount(SOCKFS, {}, null); + PIPEFS.root = FS.mount(PIPEFS, {}, null); + // End ATINITS hooks + wasmExports['__wasm_call_ctors'](); + // Begin ATPOSTCTORS hooks + callRuntimeCallbacks(onPostCtors); + FS.ignorePermissions = false; + } + + function preMain() {} + + function exitRuntime() { + // PThreads reuse the runtime from the main thread. + ___funcs_on_exit(); + // Native atexit() functions + // Begin ATEXITS hooks + FS.quit(); + TTY.shutdown(); + // End ATEXITS hooks + runtimeExited = true; + } + + function postRun() { + // PThreads reuse the runtime from the main thread. + if (Module['postRun']) { + if (typeof Module['postRun'] == 'function') + Module['postRun'] = [Module['postRun']]; + while (Module['postRun'].length) { + addOnPostRun(Module['postRun'].shift()); + } + } + // Begin ATPOSTRUNS hooks + callRuntimeCallbacks(onPostRuns); + } + + /** @param {string|number=} what */ function abort(what) { + Module['onAbort']?.(what); + what = 'Aborted(' + what + ')'; + // TODO(sbc): Should we remove printing and leave it up to whoever + // catches the exception? + err(what); + ABORT = true; + what += '. Build with -sASSERTIONS for more info.'; + // Use a wasm runtime error, because a JS error might be seen as a foreign + // exception, which means we'd run destructors on it. We need the error to + // simply make the program stop. + // FIXME This approach does not work in Wasm EH because it currently does not assume + // all RuntimeErrors are from traps; it decides whether a RuntimeError is from + // a trap or not based on a hidden field within the object. So at the moment + // we don't have a way of throwing a wasm trap from JS. TODO Make a JS API that + // allows this in the wasm spec. + // Suppress closure compiler warning here. Closure compiler's builtin extern + // definition for WebAssembly.RuntimeError claims it takes no arguments even + // though it can. + // TODO(https://github.com/google/closure-compiler/pull/3913): Remove if/when upstream closure gets fixed. + /** @suppress {checkTypes} */ var e = new WebAssembly.RuntimeError( + what + ); + // Throw the error whether or not MODULARIZE is set because abort is used + // in code paths apart from instantiation where an exception is expected + // to be thrown when abort is called. + throw e; + } + + var wasmBinaryFile; + + function findWasmBinary() { + return locateFile(dependencyFilename); + } + + function getBinarySync(file) { + if (file == wasmBinaryFile && wasmBinary) { + return new Uint8Array(wasmBinary); + } + if (readBinary) { + return readBinary(file); + } + // Throwing a plain string here, even though it not normally adviables since + // this gets turning into an `abort` in instantiateArrayBuffer. + throw 'both async and sync fetching of the wasm failed'; + } + + async function getWasmBinary(binaryFile) { + // If we don't have the binary yet, load it asynchronously using readAsync. + if (!wasmBinary) { + // Fetch the binary using readAsync + try { + var response = await readAsync(binaryFile); + return new Uint8Array(response); + } catch {} + } + // Otherwise, getBinarySync should be able to get it synchronously + return getBinarySync(binaryFile); + } + + async function instantiateArrayBuffer(binaryFile, imports) { + try { + var binary = await getWasmBinary(binaryFile); + var instance = await WebAssembly.instantiate(binary, imports); + return instance; + } catch (reason) { + err(`failed to asynchronously prepare wasm: ${reason}`); + abort(reason); + } + } + + async function instantiateAsync(binary, binaryFile, imports) { + if (!binary && !ENVIRONMENT_IS_NODE) { + try { + var response = fetch(binaryFile, { + credentials: 'same-origin', + }); + var instantiationResult = + await WebAssembly.instantiateStreaming(response, imports); + return instantiationResult; + } catch (reason) { + // We expect the most common failure cause to be a bad MIME type for the binary, + // in which case falling back to ArrayBuffer instantiation should work. + err(`wasm streaming compile failed: ${reason}`); + err('falling back to ArrayBuffer instantiation'); + } + } + return instantiateArrayBuffer(binaryFile, imports); + } + + function getWasmImports() { + // prepare imports + var imports = { + env: wasmImports, + wasi_snapshot_preview1: wasmImports, + 'GOT.mem': new Proxy(wasmImports, GOTHandler), + 'GOT.func': new Proxy(wasmImports, GOTHandler), + }; + return imports; + } + + // Create the wasm instance. + // Receives the wasm imports, returns the exports. + async function createWasm() { + // Load the wasm module and create an instance of using native support in the JS engine. + // handle a generated wasm instance, receiving its exports and + // performing other necessary setup + /** @param {WebAssembly.Module=} module*/ function receiveInstance( + instance, + module + ) { + wasmExports = instance.exports; + // No relocation needed here.. but calling this just so that updateGOT is + // called. + var origExports = (wasmExports = relocateExports(wasmExports)); + wasmExports = Asyncify.instrumentWasmExports(wasmExports); + mergeLibSymbols(wasmExports, 'main'); + var metadata = getDylinkMetadata(module); + if (metadata.neededDynlibs) { + dynamicLibraries = + metadata.neededDynlibs.concat(dynamicLibraries); + } + assignWasmExports(wasmExports); + updateGOT(origExports); + Module['wasmExports'] = wasmExports; + LDSO.init(); + loadDylibs(); + updateMemoryViews(); + removeRunDependency('wasm-instantiate'); + return wasmExports; + } + addRunDependency('wasm-instantiate'); + // Prefer streaming instantiation if available. + function receiveInstantiationResult(result) { + // 'result' is a ResultObject object which has both the module and instance. + // receiveInstance() will swap in the exports (to Module.asm) so they can be called + return receiveInstance(result['instance'], result['module']); + } + var info = getWasmImports(); + // User shell pages can write their own Module.instantiateWasm = function(imports, successCallback) callback + // to manually instantiate the Wasm module themselves. This allows pages to + // run the instantiation parallel to any other async startup actions they are + // performing. + // Also pthreads and wasm workers initialize the wasm instance through this + // path. + if (Module['instantiateWasm']) { + return new Promise((resolve, reject) => { + Module['instantiateWasm'](info, (inst, mod) => { + resolve(receiveInstance(inst, mod)); + }); + }); + } + wasmBinaryFile ??= findWasmBinary(); + var result = await instantiateAsync(wasmBinary, wasmBinaryFile, info); + var exports = receiveInstantiationResult(result); + return exports; + } + + // With MAIN_MODULE + ASYNCIFY the normal method of placing stub functions in + // wasmImports for as-yet-undefined symbols doesn't work since ASYNCIFY then + // wraps these stub functions and we can't then replace them directly. Instead + // the stub functions call into `asyncifyStubs` which gets populated by the + // dynamic linker as symbols are loaded. + var asyncifyStubs = {}; + + // end include: preamble.js + // Begin JS library code + class ExitStatus { + name = 'ExitStatus'; + constructor(status) { + this.message = `Program terminated with exit(${status})`; + this.status = status; + } + } + ExitStatus = class PHPExitStatus extends Error { + constructor(status) { + super(status); + this.name = 'ExitStatus'; + this.message = 'Program terminated with exit(' + status + ')'; + this.status = status; + } + }; + + var GOT = {}; + + var currentModuleWeakSymbols = new Set([]); + + var GOTHandler = { + get(obj, symName) { + var rtn = GOT[symName]; + if (!rtn) { + rtn = GOT[symName] = new WebAssembly.Global( + { + value: 'i32', + mutable: true, + }, + -1 + ); + } + if (!currentModuleWeakSymbols.has(symName)) { + // Any non-weak reference to a symbol marks it as `required`, which + // enabled `reportUndefinedSymbols` to report undefined symbol errors + // correctly. + rtn.required = true; + } + return rtn; + }, + }; + + var callRuntimeCallbacks = (callbacks) => { + while (callbacks.length > 0) { + // Pass the module as the first argument. + callbacks.shift()(Module); + } + }; + + var onPostRuns = []; + + var addOnPostRun = (cb) => onPostRuns.push(cb); + + var onPreRuns = []; + + var addOnPreRun = (cb) => onPreRuns.push(cb); + + var runDependencies = 0; + + var dependenciesFulfilled = null; + + var removeRunDependency = (id) => { + runDependencies--; + Module['monitorRunDependencies']?.(runDependencies); + if (runDependencies == 0) { + if (dependenciesFulfilled) { + var callback = dependenciesFulfilled; + dependenciesFulfilled = null; + callback(); + } + } + }; + + var addRunDependency = (id) => { + runDependencies++; + Module['monitorRunDependencies']?.(runDependencies); + }; + + var dynCalls = {}; + + var dynCallLegacy = (sig, ptr, args) => { + sig = sig.replace(/p/g, 'i'); + var f = dynCalls[sig]; + return f(ptr, ...args); + }; + + var dynCall = (sig, ptr, args = [], promising = false) => { + var rtn = dynCallLegacy(sig, ptr, args); + function convert(rtn) { + return rtn; + } + return convert(rtn); + }; + + var UTF8Decoder = globalThis.TextDecoder && new TextDecoder(); + + var findStringEnd = (heapOrArray, idx, maxBytesToRead, ignoreNul) => { + var maxIdx = idx + maxBytesToRead; + if (ignoreNul) return maxIdx; + // TextDecoder needs to know the byte length in advance, it doesn't stop on + // null terminator by itself. + // As a tiny code save trick, compare idx against maxIdx using a negation, + // so that maxBytesToRead=undefined/NaN means Infinity. + while (heapOrArray[idx] && !(idx >= maxIdx)) ++idx; + return idx; + }; + + /** + * Given a pointer 'idx' to a null-terminated UTF8-encoded string in the given + * array that contains uint8 values, returns a copy of that string as a + * Javascript String object. + * heapOrArray is either a regular array, or a JavaScript typed array view. + * @param {number=} idx + * @param {number=} maxBytesToRead + * @param {boolean=} ignoreNul - If true, the function will not stop on a NUL character. + * @return {string} + */ var UTF8ArrayToString = ( + heapOrArray, + idx = 0, + maxBytesToRead, + ignoreNul + ) => { + var endPtr = findStringEnd(heapOrArray, idx, maxBytesToRead, ignoreNul); + // When using conditional TextDecoder, skip it for short strings as the overhead of the native call is not worth it. + if (endPtr - idx > 16 && heapOrArray.buffer && UTF8Decoder) { + return UTF8Decoder.decode(heapOrArray.subarray(idx, endPtr)); + } + var str = ''; + while (idx < endPtr) { + // For UTF8 byte structure, see: + // http://en.wikipedia.org/wiki/UTF-8#Description + // https://www.ietf.org/rfc/rfc2279.txt + // https://tools.ietf.org/html/rfc3629 + var u0 = heapOrArray[idx++]; + if (!(u0 & 128)) { + str += String.fromCharCode(u0); + continue; + } + var u1 = heapOrArray[idx++] & 63; + if ((u0 & 224) == 192) { + str += String.fromCharCode(((u0 & 31) << 6) | u1); + continue; + } + var u2 = heapOrArray[idx++] & 63; + if ((u0 & 240) == 224) { + u0 = ((u0 & 15) << 12) | (u1 << 6) | u2; + } else { + u0 = + ((u0 & 7) << 18) | + (u1 << 12) | + (u2 << 6) | + (heapOrArray[idx++] & 63); + } + if (u0 < 65536) { + str += String.fromCharCode(u0); + } else { + var ch = u0 - 65536; + str += String.fromCharCode( + 55296 | (ch >> 10), + 56320 | (ch & 1023) + ); + } + } + return str; + }; + + var getDylinkMetadata = (binary) => { + var offset = 0; + var end = 0; + function getU8() { + return binary[offset++]; + } + function getLEB() { + var ret = 0; + var mul = 1; + while (1) { + var byte = binary[offset++]; + ret += (byte & 127) * mul; + mul *= 128; + if (!(byte & 128)) break; + } + return ret; + } + function getString() { + var len = getLEB(); + offset += len; + return UTF8ArrayToString(binary, offset - len, len); + } + function getStringList() { + var count = getLEB(); + var rtn = []; + while (count--) rtn.push(getString()); + return rtn; + } + /** @param {string=} message */ function failIf(condition, message) { + if (condition) throw new Error(message); + } + if (binary instanceof WebAssembly.Module) { + var dylinkSection = WebAssembly.Module.customSections( + binary, + 'dylink.0' + ); + failIf(dylinkSection.length === 0, 'need dylink section'); + binary = new Uint8Array(dylinkSection[0]); + end = binary.length; + } else { + var int32View = new Uint32Array( + new Uint8Array(binary.subarray(0, 24)).buffer + ); + var magicNumberFound = int32View[0] == 1836278016; + failIf(!magicNumberFound, 'need to see wasm magic number'); + // \0asm + // we should see the dylink custom section right after the magic number and wasm version + failIf(binary[8] !== 0, 'need the dylink section to be first'); + offset = 9; + var section_size = getLEB(); + //section size + end = offset + section_size; + var name = getString(); + failIf(name !== 'dylink.0'); + } + var customSection = { + neededDynlibs: [], + tlsExports: new Set(), + weakImports: new Set(), + runtimePaths: [], + }; + var WASM_DYLINK_MEM_INFO = 1; + var WASM_DYLINK_NEEDED = 2; + var WASM_DYLINK_EXPORT_INFO = 3; + var WASM_DYLINK_IMPORT_INFO = 4; + var WASM_DYLINK_RUNTIME_PATH = 5; + var WASM_SYMBOL_TLS = 256; + var WASM_SYMBOL_BINDING_MASK = 3; + var WASM_SYMBOL_BINDING_WEAK = 1; + while (offset < end) { + var subsectionType = getU8(); + var subsectionSize = getLEB(); + if (subsectionType === WASM_DYLINK_MEM_INFO) { + customSection.memorySize = getLEB(); + customSection.memoryAlign = getLEB(); + customSection.tableSize = getLEB(); + customSection.tableAlign = getLEB(); + } else if (subsectionType === WASM_DYLINK_NEEDED) { + customSection.neededDynlibs = getStringList(); + } else if (subsectionType === WASM_DYLINK_EXPORT_INFO) { + var count = getLEB(); + while (count--) { + var symname = getString(); + var flags = getLEB(); + if (flags & WASM_SYMBOL_TLS) { + customSection.tlsExports.add(symname); + } + } + } else if (subsectionType === WASM_DYLINK_IMPORT_INFO) { + var count = getLEB(); + while (count--) { + var modname = getString(); + var symname = getString(); + var flags = getLEB(); + if ( + (flags & WASM_SYMBOL_BINDING_MASK) == + WASM_SYMBOL_BINDING_WEAK + ) { + customSection.weakImports.add(symname); + } + } + } else if (subsectionType === WASM_DYLINK_RUNTIME_PATH) { + customSection.runtimePaths = getStringList(); + } else { + // unknown subsection + offset += subsectionSize; + } + } + return customSection; + }; + + var newDSO = (name, handle, syms) => { + var dso = { + refcount: Infinity, + name, + exports: syms, + global: true, + }; + LDSO.loadedLibsByName[name] = dso; + if (handle != undefined) { + LDSO.loadedLibsByHandle[handle] = dso; + } + return dso; + }; + + var LDSO = { + loadedLibsByName: {}, + loadedLibsByHandle: {}, + init() { + newDSO('__main__', 0, wasmImports); + }, + }; + + var alignMemory = (size, alignment) => + Math.ceil(size / alignment) * alignment; + + var getMemory = (size) => { + // After the runtime is initialized, we must only use sbrk() normally. + if (runtimeInitialized) { + // Currently we don't support freeing of static data when modules are + // unloaded via dlclose. This function is tagged as `noleakcheck` to + // avoid having this reported as leak. + return _calloc(size, 1); + } + var ret = ___heap_base; + // Keep __heap_base stack aligned. + var end = ret + alignMemory(size, 16); + ___heap_base = end; + // After allocating the memory from the start of the heap we need to ensure + // that once the program starts it doesn't use this region. In relocatable + // mode we can just update the __heap_base symbol that we are exporting to + // the main module. + // When not relocatable `__heap_base` is fixed and exported by the main + // module, but we can update the `sbrk_ptr` value instead. We call + // `_emscripten_get_sbrk_ptr` knowing that it is safe to call prior to + // runtime initialization (unlike, the higher level sbrk function) + var sbrk_ptr = _emscripten_get_sbrk_ptr(); + HEAPU32[sbrk_ptr >> 2] = end; + return ret; + }; + + var isInternalSym = (symName) => + [ + 'memory', + '__memory_base', + '__table_base', + '__stack_pointer', + '__indirect_function_table', + '__cpp_exception', + '__c_longjmp', + '__wasm_apply_data_relocs', + '__dso_handle', + '__tls_size', + '__tls_align', + '__set_stack_limits', + '_emscripten_tls_init', + '__wasm_init_tls', + '__wasm_call_ctors', + '__start_em_asm', + '__stop_em_asm', + '__start_em_js', + '__stop_em_js', + ].includes(symName) || symName.startsWith('__em_js__'); + + var wasmTableMirror = []; + + var getWasmTableEntry = (funcPtr) => { + var func = wasmTableMirror[funcPtr]; + if (!func) { + /** @suppress {checkTypes} */ wasmTableMirror[funcPtr] = func = + wasmTable.get(funcPtr); + } + return func; + }; + + var updateTableMap = (offset, count) => { + if (functionsInTableMap) { + for (var i = offset; i < offset + count; i++) { + var item = getWasmTableEntry(i); + // Ignore null values. + if (item) { + functionsInTableMap.set(item, i); + } + } + } + }; + + var functionsInTableMap; + + var getFunctionAddress = (func) => { + // First, create the map if this is the first use. + if (!functionsInTableMap) { + functionsInTableMap = new WeakMap(); + updateTableMap(0, wasmTable.length); + } + return functionsInTableMap.get(func) || 0; + }; + + var freeTableIndexes = []; + + var getEmptyTableSlot = () => { + // Reuse a free index if there is one, otherwise grow. + if (freeTableIndexes.length) { + return freeTableIndexes.pop(); + } + // Grow the table + return wasmTable['grow'](1); + }; + + var setWasmTableEntry = (idx, func) => { + /** @suppress {checkTypes} */ wasmTable.set(idx, func); + // With ABORT_ON_WASM_EXCEPTIONS wasmTable.get is overridden to return wrapped + // functions so we need to call it here to retrieve the potential wrapper correctly + // instead of just storing 'func' directly into wasmTableMirror + /** @suppress {checkTypes} */ wasmTableMirror[idx] = wasmTable.get(idx); + }; + + var uleb128EncodeWithLen = (arr) => { + const n = arr.length; + // Note: this LEB128 length encoding produces extra byte for n < 128, + // but we don't care as it's only used in a temporary representation. + return [(n % 128) | 128, n >> 7, ...arr]; + }; + + var wasmTypeCodes = { + i: 127, + // i32 + p: 127, + // i32 + j: 126, + // i64 + f: 125, + // f32 + d: 124, + // f64 + e: 111, + }; + + var generateTypePack = (types) => + uleb128EncodeWithLen( + Array.from(types, (type) => { + var code = wasmTypeCodes[type]; + return code; + }) + ); + + var convertJsFunctionToWasm = (func, sig) => { + // Rest of the module is static + var bytes = Uint8Array.of( + 0, + 97, + 115, + 109, // magic ("\0asm") + 1, + 0, + 0, + 0, // version: 1 + 1, // Type section code + // The module is static, with the exception of the type section, which is + // generated based on the signature passed in. + ...uleb128EncodeWithLen([ + 1, // count: 1 + 96, // param types + ...generateTypePack(sig.slice(1)), // return types (for now only supporting [] if `void` and single [T] otherwise) + ...generateTypePack(sig[0] === 'v' ? '' : sig[0]), + ]), // The rest of the module is static + 2, + 7, // import section + // (import "e" "f" (func 0 (type 0))) + 1, + 1, + 101, + 1, + 102, + 0, + 0, + 7, + 5, // export section + // (export "f" (func 0 (type 0))) + 1, + 1, + 102, + 0, + 0 + ); + // We can compile this wasm module synchronously because it is very small. + // This accepts an import (at "e.f"), that it reroutes to an export (at "f") + var module = new WebAssembly.Module(bytes); + var instance = new WebAssembly.Instance(module, { + e: { + f: func, + }, + }); + var wrappedFunc = instance.exports['f']; + return wrappedFunc; + }; + + /** @param {string=} sig */ var addFunction = (func, sig) => { + // Check if the function is already in the table, to ensure each function + // gets a unique index. + var rtn = getFunctionAddress(func); + if (rtn) { + return rtn; + } + // It's not in the table, add it now. + var ret = getEmptyTableSlot(); + // Set the new value. + try { + // Attempting to call this with JS function will cause of table.set() to fail + setWasmTableEntry(ret, func); + } catch (err) { + if (!(err instanceof TypeError)) { + throw err; + } + var wrapped = convertJsFunctionToWasm(func, sig); + setWasmTableEntry(ret, wrapped); + } + functionsInTableMap.set(func, ret); + return ret; + }; + + /** @param {boolean=} replace */ var updateGOT = (exports, replace) => { + for (var symName in exports) { + if (isInternalSym(symName)) { + continue; + } + var value = exports[symName]; + var existingEntry = GOT[symName] && GOT[symName].value != -1; + if (replace || !existingEntry) { + var newValue; + if (typeof value == 'function') { + newValue = addFunction(value); + } else if (typeof value == 'number') { + newValue = value; + } else { + // The GOT can only contain addresses (i.e data addresses or function + // addresses so we currently ignore other types export here. + continue; + } + GOT[symName] ??= new WebAssembly.Global({ + value: 'i32', + mutable: true, + }); + GOT[symName].value = newValue; + } + } + }; + + var isImmutableGlobal = (val) => { + if (val instanceof WebAssembly.Global) { + try { + val.value = val.value; + } catch { + return true; + } + } + return false; + }; + + var relocateExports = (exports, memoryBase = 0) => { + function relocateExport(name, value) { + // Detect immuable wasm global exports. These represent data addresses + // which are relative to `memoryBase` + if (isImmutableGlobal(value)) { + return value.value + memoryBase; + } + // Return unmodified value (no relocation required). + return value; + } + var relocated = {}; + for (var e in exports) { + relocated[e] = relocateExport(e, exports[e]); + } + return relocated; + }; + + var isSymbolDefined = (symName) => { + // Ignore 'stub' symbols that are auto-generated as part of the original + // `wasmImports` used to instantiate the main module. + var existing = wasmImports[symName]; + if (!existing || existing.stub) { + return false; + } + // Even if a symbol exists in wasmImports, and is not itself a stub, it + // could be an ASYNCIFY wrapper function that wraps a stub function. + if (symName in asyncifyStubs && !asyncifyStubs[symName]) { + return false; + } + return true; + }; + + var createNamedFunction = (name, func) => + Object.defineProperty(func, 'name', { + value: name, + }); + + var stackSave = () => _emscripten_stack_get_current(); + + var stackRestore = (val) => __emscripten_stack_restore(val); + + var createInvokeFunction = + (sig) => + (ptr, ...args) => { + var sp = stackSave(); + try { + return dynCall(sig, ptr, args); + } catch (e) { + stackRestore(sp); + // Create a try-catch guard that rethrows the Emscripten EH exception. + // Exceptions thrown from C++ will be a pointer (number) and longjmp + // will throw the number Infinity. Use the compact and fast "e !== e+0" + // test to check if e was not a Number. + if (e !== e + 0) throw e; + _setThrew(1, 0); + // In theory this if statement could be done on + // creating the function, but I just added this to + // save wasting code space as it only happens on exception. + if (sig[0] == 'j') return 0n; + } + }; + + var resolveGlobalSymbol = (symName, direct = false) => { + var sym; + if (isSymbolDefined(symName)) { + sym = wasmImports[symName]; + } else if (symName.startsWith('invoke_')) { + // Create (and cache) new invoke_ functions on demand. + sym = wasmImports[symName] = createNamedFunction( + symName, + createInvokeFunction(symName.split('_')[1]) + ); + } + return { + sym, + name: symName, + }; + }; + + var onPostCtors = []; + + var addOnPostCtor = (cb) => onPostCtors.push(cb); + + /** + * Given a pointer 'ptr' to a null-terminated UTF8-encoded string in the + * emscripten HEAP, returns a copy of that string as a Javascript String object. + * + * @param {number} ptr + * @param {number=} maxBytesToRead - An optional length that specifies the + * maximum number of bytes to read. You can omit this parameter to scan the + * string until the first 0 byte. If maxBytesToRead is passed, and the string + * at [ptr, ptr+maxBytesToReadr[ contains a null byte in the middle, then the + * string will cut short at that byte index. + * @param {boolean=} ignoreNul - If true, the function will not stop on a NUL character. + * @return {string} + */ var UTF8ToString = (ptr, maxBytesToRead, ignoreNul) => + ptr ? UTF8ArrayToString(HEAPU8, ptr, maxBytesToRead, ignoreNul) : ''; + + /** + * @param {string=} libName + * @param {Object=} localScope + * @param {number=} handle + */ var loadWebAssemblyModule = ( + binary, + flags, + libName, + localScope, + handle + ) => { + var metadata = getDylinkMetadata(binary); + // loadModule loads the wasm module after all its dependencies have been loaded. + // can be called both sync/async. + function loadModule() { + // alignments are powers of 2 + var memAlign = Math.pow(2, metadata.memoryAlign); + // prepare memory + var memoryBase = metadata.memorySize + ? alignMemory( + getMemory(metadata.memorySize + memAlign), + memAlign + ) + : 0; + // TODO: add to cleanups + var tableBase = metadata.tableSize ? wasmTable.length : 0; + if (handle) { + HEAP8[handle + 8] = 1; + HEAPU32[(handle + 12) >> 2] = memoryBase; + HEAP32[(handle + 16) >> 2] = metadata.memorySize; + HEAPU32[(handle + 20) >> 2] = tableBase; + HEAP32[(handle + 24) >> 2] = metadata.tableSize; + } + if (metadata.tableSize) { + wasmTable.grow(metadata.tableSize); + } + // This is the export map that we ultimately return. We declare it here + // so it can be used within resolveSymbol. We resolve symbols against + // this local symbol map in the case there they are not present on the + // global Module object. We need this fallback because Modules sometime + // need to import their own symbols + var moduleExports; + function resolveSymbol(sym) { + var resolved = resolveGlobalSymbol(sym).sym; + if (!resolved && localScope) { + resolved = localScope[sym]; + } + if (!resolved) { + resolved = moduleExports[sym]; + } + return resolved; + } + // TODO kill ↓↓↓ (except "symbols local to this module", it will likely be + // not needed if we require that if A wants symbols from B it has to link + // to B explicitly: similarly to -Wl,--no-undefined) + // wasm dynamic libraries are pure wasm, so they cannot assist in + // their own loading. When side module A wants to import something + // provided by a side module B that is loaded later, we need to + // add a layer of indirection, but worse, we can't even tell what + // to add the indirection for, without inspecting what A's imports + // are. To do that here, we use a JS proxy (another option would + // be to inspect the binary directly). + var proxyHandler = { + get(stubs, prop) { + // symbols that should be local to this module + switch (prop) { + case '__memory_base': + return memoryBase; + + case '__table_base': + return tableBase; + } + if (prop in wasmImports && !wasmImports[prop].stub) { + // No stub needed, symbol already exists in symbol table + var res = wasmImports[prop]; + // Asyncify wraps exports, and we need to look through those wrappers. + if (res.orig) { + res = res.orig; + } + return res; + } + // Return a stub function that will resolve the symbol + // when first called. + if (!(prop in stubs)) { + var resolved; + stubs[prop] = (...args) => { + resolved ||= resolveSymbol(prop); + return resolved(...args); + }; + } + return stubs[prop]; + }, + }; + var proxy = new Proxy({}, proxyHandler); + currentModuleWeakSymbols = metadata.weakImports; + var info = { + 'GOT.mem': new Proxy({}, GOTHandler), + 'GOT.func': new Proxy({}, GOTHandler), + env: proxy, + wasi_snapshot_preview1: proxy, + }; + function postInstantiation(module, instance) { + // add new entries to functionsInTableMap + updateTableMap(tableBase, metadata.tableSize); + moduleExports = relocateExports(instance.exports, memoryBase); + updateGOT(moduleExports); + moduleExports = Asyncify.instrumentWasmExports(moduleExports); + if (!flags.allowUndefined) { + reportUndefinedSymbols(); + } + function addEmAsm(addr, body) { + var args = []; + for (var arity = 0; ; arity++) { + var argName = '$' + arity; + if (!body.includes(argName)) break; + args.push(argName); + } + args = args.join(','); + var func = `(${args}) => { ${body} };`; + ASM_CONSTS[start] = eval(func); + } + // Add any EM_ASM function that exist in the side module + if ('__start_em_asm' in moduleExports) { + var start = moduleExports['__start_em_asm']; + var stop = moduleExports['__stop_em_asm']; + while (start < stop) { + var jsString = UTF8ToString(start); + addEmAsm(start, jsString); + start = HEAPU8.indexOf(0, start) + 1; + } + } + function addEmJs(name, cSig, body) { + // The signature here is a C signature (e.g. "(int foo, char* bar)"). + // See `create_em_js` in emcc.py` for the build-time version of this + // code. + var jsArgs = []; + cSig = cSig.slice(1, -1); + if (cSig != 'void') { + cSig = cSig.split(','); + for (var arg of cSig) { + var jsArg = arg.split(' ').pop(); + jsArgs.push(jsArg.replace('*', '')); + } + } + var func = `(${jsArgs}) => ${body};`; + moduleExports[name] = eval(func); + } + for (var name in moduleExports) { + if (name.startsWith('__em_js__')) { + var start = moduleExports[name]; + var jsString = UTF8ToString(start); + // EM_JS strings are stored in the data section in the form + // SIG<::>BODY. + var [sig, body] = jsString.split('<::>'); + addEmJs(name.replace('__em_js__', ''), sig, body); + delete moduleExports[name]; + } + } + // initialize the module + var applyRelocs = moduleExports['__wasm_apply_data_relocs']; + if (applyRelocs) { + if (runtimeInitialized) { + applyRelocs(); + } else { + __RELOC_FUNCS__.push(applyRelocs); + } + } + var init = moduleExports['__wasm_call_ctors']; + if (init) { + if (runtimeInitialized) { + init(); + } else { + // we aren't ready to run compiled code yet + addOnPostCtor(init); + } + } + return moduleExports; + } + if (flags.loadAsync) { + return (async () => { + var instance; + if (binary instanceof WebAssembly.Module) { + instance = new WebAssembly.Instance(binary, info); + } else { + // Destructuring assignment without declaration has to be wrapped + // with parens or parser will treat the l-value as an object + // literal instead. + ({ module: binary, instance } = + await WebAssembly.instantiate(binary, info)); + } + return postInstantiation(binary, instance); + })(); + } + var module = + binary instanceof WebAssembly.Module + ? binary + : new WebAssembly.Module(binary); + var instance = new WebAssembly.Instance(module, info); + return postInstantiation(module, instance); + } + // We need to set rpath in flags based on the current library's rpath. + // We can't mutate flags or else if a depends on b and c and b depends on d, + // then c will be loaded with b's rpath instead of a's. + flags = { + ...flags, + rpath: { + parentLibPath: libName, + paths: metadata.runtimePaths, + }, + }; + // now load needed libraries and the module itself. + if (flags.loadAsync) { + return metadata.neededDynlibs + .reduce( + (chain, dynNeeded) => + chain.then(() => + loadDynamicLibrary(dynNeeded, flags, localScope) + ), + Promise.resolve() + ) + .then(loadModule); + } + for (var needed of metadata.neededDynlibs) { + loadDynamicLibrary(needed, flags, localScope); + } + return loadModule(); + }; + + var mergeLibSymbols = (exports, libName) => { + registerDynCallSymbols(exports); + // add symbols into global namespace TODO: weak linking etc. + for (var [sym, exp] of Object.entries(exports)) { + // When RTLD_GLOBAL is enabled, the symbols defined by this shared object + // will be made available for symbol resolution of subsequently loaded + // shared objects. + // We should copy the symbols (which include methods and variables) from + // SIDE_MODULE to MAIN_MODULE. + const setImport = (target) => { + if (target in asyncifyStubs) { + asyncifyStubs[target] = exp; + } + if (!isSymbolDefined(target)) { + wasmImports[target] = exp; + } + }; + setImport(sym); + // Special case for handling of main symbol: If a side module exports + // `main` that also acts a definition for `__main_argc_argv` and vice + // versa. + const main_alias = '__main_argc_argv'; + if (sym == 'main') { + setImport(main_alias); + } + if (sym == main_alias) { + setImport('main'); + } + } + }; + + var asyncLoad = async (url) => { + var arrayBuffer = await readAsync(url); + return new Uint8Array(arrayBuffer); + }; + + var preloadPlugins = []; + + var registerWasmPlugin = () => { + // Use string keys here for public methods to avoid minification since the + // plugin consumer also uses string keys. + var wasmPlugin = { + promiseChainEnd: Promise.resolve(), + canHandle: (name) => + !Module['noWasmDecoding'] && name.endsWith('.so'), + handle: async ( + byteArray, + name // loadWebAssemblyModule can not load modules out-of-order, so rather + ) => + // than just running the promises in parallel, this makes a chain of + // promises to run in series. + (wasmPlugin.promiseChainEnd = wasmPlugin.promiseChainEnd.then( + async () => { + try { + var exports = await loadWebAssemblyModule( + byteArray, + { + loadAsync: true, + nodelete: true, + }, + name, + {} + ); + } catch (error) { + throw new Error( + `failed to instantiate wasm: ${name}: ${error}` + ); + } + preloadedWasm[name] = exports; + return byteArray; + } + )), + }; + preloadPlugins.push(wasmPlugin); + }; + + var preloadedWasm = {}; + + var PATH = { + isAbs: (path) => path.charAt(0) === '/', + splitPath: (filename) => { + var splitPathRe = + /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/; + return splitPathRe.exec(filename).slice(1); + }, + normalizeArray: (parts, allowAboveRoot) => { + // if the path tries to go above the root, `up` ends up > 0 + var up = 0; + for (var i = parts.length - 1; i >= 0; i--) { + var last = parts[i]; + if (last === '.') { + parts.splice(i, 1); + } else if (last === '..') { + parts.splice(i, 1); + up++; + } else if (up) { + parts.splice(i, 1); + up--; + } + } + // if the path is allowed to go above the root, restore leading ..s + if (allowAboveRoot) { + for (; up; up--) { + parts.unshift('..'); + } + } + return parts; + }, + normalize: (path) => { + var isAbsolute = PATH.isAbs(path), + trailingSlash = path.slice(-1) === '/'; + // Normalize the path + path = PATH.normalizeArray( + path.split('/').filter((p) => !!p), + !isAbsolute + ).join('/'); + if (!path && !isAbsolute) { + path = '.'; + } + if (path && trailingSlash) { + path += '/'; + } + return (isAbsolute ? '/' : '') + path; + }, + dirname: (path) => { + var result = PATH.splitPath(path), + root = result[0], + dir = result[1]; + if (!root && !dir) { + // No dirname whatsoever + return '.'; + } + if (dir) { + // It has a dirname, strip trailing slash + dir = dir.slice(0, -1); + } + return root + dir; + }, + basename: (path) => path && path.match(/([^\/]+|\/)\/*$/)[1], + join: (...paths) => PATH.normalize(paths.join('/')), + join2: (l, r) => PATH.normalize(l + '/' + r), + }; + + var replaceORIGIN = (parentLibName, rpath) => { + if (rpath.startsWith('$ORIGIN')) { + // TODO: what to do if we only know the relative path of the file? It will return "." here. + var origin = PATH.dirname(parentLibName); + return rpath.replace('$ORIGIN', origin); + } + return rpath; + }; + + var withStackSave = (f) => { + var stack = stackSave(); + var ret = f(); + stackRestore(stack); + return ret; + }; + + var stackAlloc = (sz) => __emscripten_stack_alloc(sz); + + var lengthBytesUTF8 = (str) => { + var len = 0; + for (var i = 0; i < str.length; ++i) { + // Gotcha: charCodeAt returns a 16-bit word that is a UTF-16 encoded code + // unit, not a Unicode code point of the character! So decode + // UTF16->UTF32->UTF8. + // See http://unicode.org/faq/utf_bom.html#utf16-3 + var c = str.charCodeAt(i); + // possibly a lead surrogate + if (c <= 127) { + len++; + } else if (c <= 2047) { + len += 2; + } else if (c >= 55296 && c <= 57343) { + len += 4; + ++i; + } else { + len += 3; + } + } + return len; + }; + + var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { + // Parameter maxBytesToWrite is not optional. Negative values, 0, null, + // undefined and false each don't write out any bytes. + if (!(maxBytesToWrite > 0)) return 0; + var startIdx = outIdx; + var endIdx = outIdx + maxBytesToWrite - 1; + // -1 for string null terminator. + for (var i = 0; i < str.length; ++i) { + // For UTF8 byte structure, see http://en.wikipedia.org/wiki/UTF-8#Description + // and https://www.ietf.org/rfc/rfc2279.txt + // and https://tools.ietf.org/html/rfc3629 + var u = str.codePointAt(i); + if (u <= 127) { + if (outIdx >= endIdx) break; + heap[outIdx++] = u; + } else if (u <= 2047) { + if (outIdx + 1 >= endIdx) break; + heap[outIdx++] = 192 | (u >> 6); + heap[outIdx++] = 128 | (u & 63); + } else if (u <= 65535) { + if (outIdx + 2 >= endIdx) break; + heap[outIdx++] = 224 | (u >> 12); + heap[outIdx++] = 128 | ((u >> 6) & 63); + heap[outIdx++] = 128 | (u & 63); + } else { + if (outIdx + 3 >= endIdx) break; + heap[outIdx++] = 240 | (u >> 18); + heap[outIdx++] = 128 | ((u >> 12) & 63); + heap[outIdx++] = 128 | ((u >> 6) & 63); + heap[outIdx++] = 128 | (u & 63); + // Gotcha: if codePoint is over 0xFFFF, it is represented as a surrogate pair in UTF-16. + // We need to manually skip over the second code unit for correct iteration. + i++; + } + } + // Null-terminate the pointer to the buffer. + heap[outIdx] = 0; + return outIdx - startIdx; + }; + + var stringToUTF8 = (str, outPtr, maxBytesToWrite) => + stringToUTF8Array(str, HEAPU8, outPtr, maxBytesToWrite); + + var stringToUTF8OnStack = (str) => { + var size = lengthBytesUTF8(str) + 1; + var ret = stackAlloc(size); + stringToUTF8(str, ret, size); + return ret; + }; + + var initRandomFill = () => (view) => crypto.getRandomValues(view); + + var randomFill = (view) => { + // Lazily init on the first invocation. + (randomFill = initRandomFill())(view); + }; + + var PATH_FS = { + resolve: (...args) => { + var resolvedPath = '', + resolvedAbsolute = false; + for (var i = args.length - 1; i >= -1 && !resolvedAbsolute; i--) { + var path = i >= 0 ? args[i] : FS.cwd(); + // Skip empty and invalid entries + if (typeof path != 'string') { + throw new TypeError( + 'Arguments to path.resolve must be strings' + ); + } else if (!path) { + return ''; + } + resolvedPath = path + '/' + resolvedPath; + resolvedAbsolute = PATH.isAbs(path); + } + // At this point the path should be resolved to a full absolute path, but + // handle relative paths to be safe (might happen when process.cwd() fails) + resolvedPath = PATH.normalizeArray( + resolvedPath.split('/').filter((p) => !!p), + !resolvedAbsolute + ).join('/'); + return (resolvedAbsolute ? '/' : '') + resolvedPath || '.'; + }, + relative: (from, to) => { + from = PATH_FS.resolve(from).slice(1); + to = PATH_FS.resolve(to).slice(1); + function trim(arr) { + var start = 0; + for (; start < arr.length; start++) { + if (arr[start] !== '') break; + } + var end = arr.length - 1; + for (; end >= 0; end--) { + if (arr[end] !== '') break; + } + if (start > end) return []; + return arr.slice(start, end - start + 1); + } + var fromParts = trim(from.split('/')); + var toParts = trim(to.split('/')); + var length = Math.min(fromParts.length, toParts.length); + var samePartsLength = length; + for (var i = 0; i < length; i++) { + if (fromParts[i] !== toParts[i]) { + samePartsLength = i; + break; + } + } + var outputParts = []; + for (var i = samePartsLength; i < fromParts.length; i++) { + outputParts.push('..'); + } + outputParts = outputParts.concat(toParts.slice(samePartsLength)); + return outputParts.join('/'); + }, + }; + + var FS_stdin_getChar_buffer = []; + + /** @type {function(string, boolean=, number=)} */ var intArrayFromString = + (stringy, dontAddNull, length) => { + var len = length > 0 ? length : lengthBytesUTF8(stringy) + 1; + var u8array = new Array(len); + var numBytesWritten = stringToUTF8Array( + stringy, + u8array, + 0, + u8array.length + ); + if (dontAddNull) u8array.length = numBytesWritten; + return u8array; + }; + + var FS_stdin_getChar = () => { + if (!FS_stdin_getChar_buffer.length) { + var result = null; + if (ENVIRONMENT_IS_NODE) { + // we will read data by chunks of BUFSIZE + var BUFSIZE = 256; + var buf = Buffer.alloc(BUFSIZE); + var bytesRead = 0; + // For some reason we must suppress a closure warning here, even though + // fd definitely exists on process.stdin, and is even the proper way to + // get the fd of stdin, + // https://github.com/nodejs/help/issues/2136#issuecomment-523649904 + // This started to happen after moving this logic out of library_tty.js, + // so it is related to the surrounding code in some unclear manner. + /** @suppress {missingProperties} */ var fd = process.stdin.fd; + try { + bytesRead = fs.readSync(fd, buf, 0, BUFSIZE); + } catch (e) { + // Cross-platform differences: on Windows, reading EOF throws an + // exception, but on other OSes, reading EOF returns 0. Uniformize + // behavior by treating the EOF exception to return 0. + if (e.toString().includes('EOF')) bytesRead = 0; + else throw e; + } + if (bytesRead > 0) { + result = buf.slice(0, bytesRead).toString('utf-8'); + } + } else { + } + if (!result) { + return null; + } + FS_stdin_getChar_buffer = intArrayFromString(result, true); + } + return FS_stdin_getChar_buffer.shift(); + }; + + var TTY = { + ttys: [], + init() {}, + shutdown() {}, + register(dev, ops) { + TTY.ttys[dev] = { + input: [], + output: [], + ops, + }; + FS.registerDevice(dev, TTY.stream_ops); + }, + stream_ops: { + open(stream) { + var tty = TTY.ttys[stream.node.rdev]; + if (!tty) { + throw new FS.ErrnoError(43); + } + stream.tty = tty; + stream.seekable = false; + }, + close(stream) { + // flush any pending line data + stream.tty.ops.fsync(stream.tty); + }, + fsync(stream) { + stream.tty.ops.fsync(stream.tty); + }, + read(stream, buffer, offset, length, pos) { + if (!stream.tty || !stream.tty.ops.get_char) { + throw new FS.ErrnoError(60); + } + var bytesRead = 0; + for (var i = 0; i < length; i++) { + var result; + try { + result = stream.tty.ops.get_char(stream.tty); + } catch (e) { + throw new FS.ErrnoError(29); + } + if (result === undefined && bytesRead === 0) { + throw new FS.ErrnoError(6); + } + if (result === null || result === undefined) break; + bytesRead++; + buffer[offset + i] = result; + } + if (bytesRead) { + stream.node.atime = Date.now(); + } + return bytesRead; + }, + write(stream, buffer, offset, length, pos) { + if (!stream.tty || !stream.tty.ops.put_char) { + throw new FS.ErrnoError(60); + } + try { + for (var i = 0; i < length; i++) { + stream.tty.ops.put_char(stream.tty, buffer[offset + i]); + } + } catch (e) { + throw new FS.ErrnoError(29); + } + if (length) { + stream.node.mtime = stream.node.ctime = Date.now(); + } + return i; + }, + }, + default_tty_ops: { + get_char(tty) { + return FS_stdin_getChar(); + }, + put_char(tty, val) { + if (val === null || val === 10) { + out(UTF8ArrayToString(tty.output)); + tty.output = []; + } else { + if (val != 0) tty.output.push(val); + } + }, + fsync(tty) { + if (tty.output?.length > 0) { + out(UTF8ArrayToString(tty.output)); + tty.output = []; + } + }, + ioctl_tcgets(tty) { + // typical setting + return { + c_iflag: 25856, + c_oflag: 5, + c_cflag: 191, + c_lflag: 35387, + c_cc: [ + 3, 28, 127, 21, 4, 0, 1, 0, 17, 19, 26, 0, 18, 15, 23, + 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ], + }; + }, + ioctl_tcsets(tty, optional_actions, data) { + // currently just ignore + return 0; + }, + ioctl_tiocgwinsz(tty) { + return [24, 80]; + }, + }, + default_tty1_ops: { + put_char(tty, val) { + if (val === null || val === 10) { + err(UTF8ArrayToString(tty.output)); + tty.output = []; + } else { + if (val != 0) tty.output.push(val); + } + }, + fsync(tty) { + if (tty.output?.length > 0) { + err(UTF8ArrayToString(tty.output)); + tty.output = []; + } + }, + }, + }; + + var zeroMemory = (ptr, size) => HEAPU8.fill(0, ptr, ptr + size); + + var mmapAlloc = (size) => { + size = alignMemory(size, 65536); + var ptr = _emscripten_builtin_memalign(65536, size); + if (ptr) zeroMemory(ptr, size); + return ptr; + }; + + var MEMFS = { + ops_table: null, + mount(mount) { + return MEMFS.createNode(null, '/', 16895, 0); + }, + createNode(parent, name, mode, dev) { + if (FS.isBlkdev(mode) || FS.isFIFO(mode)) { + // no supported + throw new FS.ErrnoError(63); + } + MEMFS.ops_table ||= { + dir: { + node: { + getattr: MEMFS.node_ops.getattr, + setattr: MEMFS.node_ops.setattr, + lookup: MEMFS.node_ops.lookup, + mknod: MEMFS.node_ops.mknod, + rename: MEMFS.node_ops.rename, + unlink: MEMFS.node_ops.unlink, + rmdir: MEMFS.node_ops.rmdir, + readdir: MEMFS.node_ops.readdir, + symlink: MEMFS.node_ops.symlink, + }, + stream: { + llseek: MEMFS.stream_ops.llseek, + }, + }, + file: { + node: { + getattr: MEMFS.node_ops.getattr, + setattr: MEMFS.node_ops.setattr, + }, + stream: { + llseek: MEMFS.stream_ops.llseek, + read: MEMFS.stream_ops.read, + write: MEMFS.stream_ops.write, + mmap: MEMFS.stream_ops.mmap, + msync: MEMFS.stream_ops.msync, + }, + }, + link: { + node: { + getattr: MEMFS.node_ops.getattr, + setattr: MEMFS.node_ops.setattr, + readlink: MEMFS.node_ops.readlink, + }, + stream: {}, + }, + chrdev: { + node: { + getattr: MEMFS.node_ops.getattr, + setattr: MEMFS.node_ops.setattr, + }, + stream: FS.chrdev_stream_ops, + }, + }; + var node = FS.createNode(parent, name, mode, dev); + if (FS.isDir(node.mode)) { + node.node_ops = MEMFS.ops_table.dir.node; + node.stream_ops = MEMFS.ops_table.dir.stream; + node.contents = {}; + } else if (FS.isFile(node.mode)) { + node.node_ops = MEMFS.ops_table.file.node; + node.stream_ops = MEMFS.ops_table.file.stream; + node.usedBytes = 0; + // The actual number of bytes used in the typed array, as opposed to contents.length which gives the whole capacity. + // When the byte data of the file is populated, this will point to either a typed array, or a normal JS array. Typed arrays are preferred + // for performance, and used by default. However, typed arrays are not resizable like normal JS arrays are, so there is a small disk size + // penalty involved for appending file writes that continuously grow a file similar to std::vector capacity vs used -scheme. + node.contents = null; + } else if (FS.isLink(node.mode)) { + node.node_ops = MEMFS.ops_table.link.node; + node.stream_ops = MEMFS.ops_table.link.stream; + } else if (FS.isChrdev(node.mode)) { + node.node_ops = MEMFS.ops_table.chrdev.node; + node.stream_ops = MEMFS.ops_table.chrdev.stream; + } + node.atime = node.mtime = node.ctime = Date.now(); + // add the new node to the parent + if (parent) { + parent.contents[name] = node; + parent.atime = parent.mtime = parent.ctime = node.atime; + } + return node; + }, + getFileDataAsTypedArray(node) { + if (!node.contents) return new Uint8Array(0); + if (node.contents.subarray) + return node.contents.subarray(0, node.usedBytes); + // Make sure to not return excess unused bytes. + return new Uint8Array(node.contents); + }, + expandFileStorage(node, newCapacity) { + var prevCapacity = node.contents ? node.contents.length : 0; + if (prevCapacity >= newCapacity) return; + // No need to expand, the storage was already large enough. + // Don't expand strictly to the given requested limit if it's only a very small increase, but instead geometrically grow capacity. + // For small filesizes (<1MB), perform size*2 geometric increase, but for large sizes, do a much more conservative size*1.125 increase to + // avoid overshooting the allocation cap by a very large margin. + var CAPACITY_DOUBLING_MAX = 1024 * 1024; + newCapacity = Math.max( + newCapacity, + (prevCapacity * + (prevCapacity < CAPACITY_DOUBLING_MAX ? 2 : 1.125)) >>> + 0 + ); + if (prevCapacity != 0) newCapacity = Math.max(newCapacity, 256); + // At minimum allocate 256b for each file when expanding. + var oldContents = node.contents; + node.contents = new Uint8Array(newCapacity); + // Allocate new storage. + if (node.usedBytes > 0) + node.contents.set(oldContents.subarray(0, node.usedBytes), 0); + }, + resizeFileStorage(node, newSize) { + if (node.usedBytes == newSize) return; + if (newSize == 0) { + node.contents = null; + // Fully decommit when requesting a resize to zero. + node.usedBytes = 0; + } else { + var oldContents = node.contents; + node.contents = new Uint8Array(newSize); + // Allocate new storage. + if (oldContents) { + node.contents.set( + oldContents.subarray( + 0, + Math.min(newSize, node.usedBytes) + ) + ); + } + node.usedBytes = newSize; + } + }, + node_ops: { + getattr(node) { + var attr = {}; + // device numbers reuse inode numbers. + attr.dev = FS.isChrdev(node.mode) ? node.id : 1; + attr.ino = node.id; + attr.mode = node.mode; + attr.nlink = 1; + attr.uid = 0; + attr.gid = 0; + attr.rdev = node.rdev; + if (FS.isDir(node.mode)) { + attr.size = 4096; + } else if (FS.isFile(node.mode)) { + attr.size = node.usedBytes; + } else if (FS.isLink(node.mode)) { + attr.size = node.link.length; + } else { + attr.size = 0; + } + attr.atime = new Date(node.atime); + attr.mtime = new Date(node.mtime); + attr.ctime = new Date(node.ctime); + // NOTE: In our implementation, st_blocks = Math.ceil(st_size/st_blksize), + // but this is not required by the standard. + attr.blksize = 4096; + attr.blocks = Math.ceil(attr.size / attr.blksize); + return attr; + }, + setattr(node, attr) { + for (const key of ['mode', 'atime', 'mtime', 'ctime']) { + if (attr[key] != null) { + node[key] = attr[key]; + } + } + if (attr.size !== undefined) { + MEMFS.resizeFileStorage(node, attr.size); + } + }, + lookup(parent, name) { + // This error may happen quite a bit. To avoid overhead we reuse it (and + // suffer a lack of stack info). + if (!MEMFS.doesNotExistError) { + MEMFS.doesNotExistError = new FS.ErrnoError(44); + /** @suppress {checkTypes} */ MEMFS.doesNotExistError.stack = + ''; + } + throw MEMFS.doesNotExistError; + }, + mknod(parent, name, mode, dev) { + return MEMFS.createNode(parent, name, mode, dev); + }, + rename(old_node, new_dir, new_name) { + var new_node; + try { + new_node = FS.lookupNode(new_dir, new_name); + } catch (e) {} + if (new_node) { + if (FS.isDir(old_node.mode)) { + // if we're overwriting a directory at new_name, make sure it's empty. + for (var i in new_node.contents) { + throw new FS.ErrnoError(55); + } + } + FS.hashRemoveNode(new_node); + } + // do the internal rewiring + delete old_node.parent.contents[old_node.name]; + new_dir.contents[new_name] = old_node; + old_node.name = new_name; + new_dir.ctime = + new_dir.mtime = + old_node.parent.ctime = + old_node.parent.mtime = + Date.now(); + }, + unlink(parent, name) { + delete parent.contents[name]; + parent.ctime = parent.mtime = Date.now(); + }, + rmdir(parent, name) { + var node = FS.lookupNode(parent, name); + for (var i in node.contents) { + throw new FS.ErrnoError(55); + } + delete parent.contents[name]; + parent.ctime = parent.mtime = Date.now(); + }, + readdir(node) { + return ['.', '..', ...Object.keys(node.contents)]; + }, + symlink(parent, newname, oldpath) { + var node = MEMFS.createNode(parent, newname, 511 | 40960, 0); + node.link = oldpath; + return node; + }, + readlink(node) { + if (!FS.isLink(node.mode)) { + throw new FS.ErrnoError(28); + } + return node.link; + }, + }, + stream_ops: { + read(stream, buffer, offset, length, position) { + var contents = stream.node.contents; + if (position >= stream.node.usedBytes) return 0; + var size = Math.min(stream.node.usedBytes - position, length); + if (size > 8 && contents.subarray) { + // non-trivial, and typed array + buffer.set( + contents.subarray(position, position + size), + offset + ); + } else { + for (var i = 0; i < size; i++) + buffer[offset + i] = contents[position + i]; + } + return size; + }, + write(stream, buffer, offset, length, position, canOwn) { + // If the buffer is located in main memory (HEAP), and if + // memory can grow, we can't hold on to references of the + // memory buffer, as they may get invalidated. That means we + // need to do copy its contents. + if (buffer.buffer === HEAP8.buffer) { + canOwn = false; + } + if (!length) return 0; + var node = stream.node; + node.mtime = node.ctime = Date.now(); + if ( + buffer.subarray && + (!node.contents || node.contents.subarray) + ) { + // This write is from a typed array to a typed array? + if (canOwn) { + node.contents = buffer.subarray( + offset, + offset + length + ); + node.usedBytes = length; + return length; + } else if (node.usedBytes === 0 && position === 0) { + // If this is a simple first write to an empty file, do a fast set since we don't need to care about old data. + node.contents = buffer.slice(offset, offset + length); + node.usedBytes = length; + return length; + } else if (position + length <= node.usedBytes) { + // Writing to an already allocated and used subrange of the file? + node.contents.set( + buffer.subarray(offset, offset + length), + position + ); + return length; + } + } + // Appending to an existing file and we need to reallocate, or source data did not come as a typed array. + MEMFS.expandFileStorage(node, position + length); + if (node.contents.subarray && buffer.subarray) { + // Use typed array write which is available. + node.contents.set( + buffer.subarray(offset, offset + length), + position + ); + } else { + for (var i = 0; i < length; i++) { + node.contents[position + i] = buffer[offset + i]; + } + } + node.usedBytes = Math.max(node.usedBytes, position + length); + return length; + }, + llseek(stream, offset, whence) { + var position = offset; + if (whence === 1) { + position += stream.position; + } else if (whence === 2) { + if (FS.isFile(stream.node.mode)) { + position += stream.node.usedBytes; + } + } + if (position < 0) { + throw new FS.ErrnoError(28); + } + return position; + }, + mmap(stream, length, position, prot, flags) { + if (!FS.isFile(stream.node.mode)) { + throw new FS.ErrnoError(43); + } + var ptr; + var allocated; + var contents = stream.node.contents; + // Only make a new copy when MAP_PRIVATE is specified. + if ( + !(flags & 2) && + contents && + contents.buffer === HEAP8.buffer + ) { + // We can't emulate MAP_SHARED when the file is not backed by the + // buffer we're mapping to (e.g. the HEAP buffer). + allocated = false; + ptr = contents.byteOffset; + } else { + allocated = true; + ptr = mmapAlloc(length); + if (!ptr) { + throw new FS.ErrnoError(48); + } + if (contents) { + // Try to avoid unnecessary slices. + if ( + position > 0 || + position + length < contents.length + ) { + if (contents.subarray) { + contents = contents.subarray( + position, + position + length + ); + } else { + contents = Array.prototype.slice.call( + contents, + position, + position + length + ); + } + } + HEAP8.set(contents, ptr); + } + } + return { + ptr, + allocated, + }; + }, + msync(stream, buffer, offset, length, mmapFlags) { + MEMFS.stream_ops.write( + stream, + buffer, + 0, + length, + offset, + false + ); + // should we check if bytesWritten and length are the same? + return 0; + }, + }, + }; + + var FS_modeStringToFlags = (str) => { + var flagModes = { + r: 0, + 'r+': 2, + w: 512 | 64 | 1, + 'w+': 512 | 64 | 2, + a: 1024 | 64 | 1, + 'a+': 1024 | 64 | 2, + }; + var flags = flagModes[str]; + if (typeof flags == 'undefined') { + throw new Error(`Unknown file open mode: ${str}`); + } + return flags; + }; + + var FS_getMode = (canRead, canWrite) => { + var mode = 0; + if (canRead) mode |= 292 | 73; + if (canWrite) mode |= 146; + return mode; + }; + + var ERRNO_CODES = { + EPERM: 63, + ENOENT: 44, + ESRCH: 71, + EINTR: 27, + EIO: 29, + ENXIO: 60, + E2BIG: 1, + ENOEXEC: 45, + EBADF: 8, + ECHILD: 12, + EAGAIN: 6, + EWOULDBLOCK: 6, + ENOMEM: 48, + EACCES: 2, + EFAULT: 21, + ENOTBLK: 105, + EBUSY: 10, + EEXIST: 20, + EXDEV: 75, + ENODEV: 43, + ENOTDIR: 54, + EISDIR: 31, + EINVAL: 28, + ENFILE: 41, + EMFILE: 33, + ENOTTY: 59, + ETXTBSY: 74, + EFBIG: 22, + ENOSPC: 51, + ESPIPE: 70, + EROFS: 69, + EMLINK: 34, + EPIPE: 64, + EDOM: 18, + ERANGE: 68, + ENOMSG: 49, + EIDRM: 24, + ECHRNG: 106, + EL2NSYNC: 156, + EL3HLT: 107, + EL3RST: 108, + ELNRNG: 109, + EUNATCH: 110, + ENOCSI: 111, + EL2HLT: 112, + EDEADLK: 16, + ENOLCK: 46, + EBADE: 113, + EBADR: 114, + EXFULL: 115, + ENOANO: 104, + EBADRQC: 103, + EBADSLT: 102, + EDEADLOCK: 16, + EBFONT: 101, + ENOSTR: 100, + ENODATA: 116, + ETIME: 117, + ENOSR: 118, + ENONET: 119, + ENOPKG: 120, + EREMOTE: 121, + ENOLINK: 47, + EADV: 122, + ESRMNT: 123, + ECOMM: 124, + EPROTO: 65, + EMULTIHOP: 36, + EDOTDOT: 125, + EBADMSG: 9, + ENOTUNIQ: 126, + EBADFD: 127, + EREMCHG: 128, + ELIBACC: 129, + ELIBBAD: 130, + ELIBSCN: 131, + ELIBMAX: 132, + ELIBEXEC: 133, + ENOSYS: 52, + ENOTEMPTY: 55, + ENAMETOOLONG: 37, + ELOOP: 32, + EOPNOTSUPP: 138, + EPFNOSUPPORT: 139, + ECONNRESET: 15, + ENOBUFS: 42, + EAFNOSUPPORT: 5, + EPROTOTYPE: 67, + ENOTSOCK: 57, + ENOPROTOOPT: 50, + ESHUTDOWN: 140, + ECONNREFUSED: 14, + EADDRINUSE: 3, + ECONNABORTED: 13, + ENETUNREACH: 40, + ENETDOWN: 38, + ETIMEDOUT: 73, + EHOSTDOWN: 142, + EHOSTUNREACH: 23, + EINPROGRESS: 26, + EALREADY: 7, + EDESTADDRREQ: 17, + EMSGSIZE: 35, + EPROTONOSUPPORT: 66, + ESOCKTNOSUPPORT: 137, + EADDRNOTAVAIL: 4, + ENETRESET: 39, + EISCONN: 30, + ENOTCONN: 53, + ETOOMANYREFS: 141, + EUSERS: 136, + EDQUOT: 19, + ESTALE: 72, + ENOTSUP: 138, + ENOMEDIUM: 148, + EILSEQ: 25, + EOVERFLOW: 61, + ECANCELED: 11, + ENOTRECOVERABLE: 56, + EOWNERDEAD: 62, + ESTRPIPE: 135, + }; + + var NODEFS = { + isWindows: false, + staticInit() { + NODEFS.isWindows = !!process.platform.match(/^win/); + var flags = process.binding('constants')['fs']; + NODEFS.flagsForNodeMap = { + 1024: flags['O_APPEND'], + 64: flags['O_CREAT'], + 128: flags['O_EXCL'], + 256: flags['O_NOCTTY'], + 0: flags['O_RDONLY'], + 2: flags['O_RDWR'], + 4096: flags['O_SYNC'], + 512: flags['O_TRUNC'], + 1: flags['O_WRONLY'], + 131072: flags['O_NOFOLLOW'], + }; + }, + convertNodeCode(e) { + var code = e.code; + return ERRNO_CODES[code]; + }, + tryFSOperation(f) { + try { + return f(); + } catch (e) { + if (!e.code) throw e; + // node under windows can return code 'UNKNOWN' here: + // https://github.com/emscripten-core/emscripten/issues/15468 + if (e.code === 'UNKNOWN') throw new FS.ErrnoError(28); + throw new FS.ErrnoError(NODEFS.convertNodeCode(e)); + } + }, + mount(mount) { + return NODEFS.createNode( + null, + '/', + NODEFS.getMode(mount.opts.root), + 0 + ); + }, + createNode(parent, name, mode, dev) { + if (!FS.isDir(mode) && !FS.isFile(mode) && !FS.isLink(mode)) { + throw new FS.ErrnoError(28); + } + var node = FS.createNode(parent, name, mode); + node.node_ops = NODEFS.node_ops; + node.stream_ops = NODEFS.stream_ops; + return node; + }, + getMode(path) { + return NODEFS.tryFSOperation(() => { + var mode = fs.lstatSync(path).mode; + if (NODEFS.isWindows) { + // Windows does not report the 'x' permission bit, so propagate read + // bits to execute bits. + mode |= (mode & 292) >> 2; + } + return mode; + }); + }, + realPath(node) { + var parts = []; + while (node.parent !== node) { + parts.push(node.name); + node = node.parent; + } + parts.push(node.mount.opts.root); + parts.reverse(); + return PATH.join(...parts); + }, + flagsForNode(flags) { + flags &= ~2097152; + // Ignore this flag from musl, otherwise node.js fails to open the file. + flags &= ~2048; + // Ignore this flag from musl, otherwise node.js fails to open the file. + flags &= ~32768; + // Ignore this flag from musl, otherwise node.js fails to open the file. + flags &= ~524288; + // Some applications may pass it; it makes no sense for a single process. + flags &= ~65536; + // Node.js doesn't need this passed in, it errors. + var newFlags = 0; + for (var k in NODEFS.flagsForNodeMap) { + if (flags & k) { + newFlags |= NODEFS.flagsForNodeMap[k]; + flags ^= k; + } + } + if (flags) { + throw new FS.ErrnoError(28); + } + return newFlags; + }, + getattr(func, node) { + var stat = NODEFS.tryFSOperation(func); + if (NODEFS.isWindows) { + // node.js v0.10.20 doesn't report blksize and blocks on Windows. Fake + // them with default blksize of 4096. + // See http://support.microsoft.com/kb/140365 + if (!stat.blksize) { + stat.blksize = 4096; + } + if (!stat.blocks) { + stat.blocks = + ((stat.size + stat.blksize - 1) / stat.blksize) | 0; + } + // Windows does not report the 'x' permission bit, so propagate read + // bits to execute bits. + stat.mode |= (stat.mode & 292) >> 2; + } + return { + dev: stat.dev, + ino: node.id, + mode: stat.mode, + nlink: stat.nlink, + uid: stat.uid, + gid: stat.gid, + rdev: stat.rdev, + size: stat.size, + atime: stat.atime, + mtime: stat.mtime, + ctime: stat.ctime, + blksize: stat.blksize, + blocks: stat.blocks, + }; + }, + setattr(arg, node, attr, chmod, utimes, truncate, stat) { + NODEFS.tryFSOperation(() => { + if (attr.mode !== undefined) { + var mode = attr.mode; + if (NODEFS.isWindows) { + // Windows only supports S_IREAD / S_IWRITE (S_IRUSR / S_IWUSR) + // https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/chmod-wchmod + mode &= 384; + } + chmod(arg, mode); + // update the common node structure mode as well + node.mode = attr.mode; + } + if (typeof (attr.atime ?? attr.mtime) === 'number') { + // Unfortunately, we have to stat the current value if we don't want + // to change it. On top of that, since the times don't round trip + // this will only keep the value nearly unchanged not exactly + // unchanged. See: + // https://github.com/nodejs/node/issues/56492 + var atime = new Date(attr.atime ?? stat(arg).atime); + var mtime = new Date(attr.mtime ?? stat(arg).mtime); + utimes(arg, atime, mtime); + } + if (attr.size !== undefined) { + truncate(arg, attr.size); + } + }); + }, + node_ops: { + getattr(node) { + var path = NODEFS.realPath(node); + return NODEFS.getattr(() => fs.lstatSync(path), node); + }, + setattr(node, attr) { + var path = NODEFS.realPath(node); + if (attr.mode != null && attr.dontFollow) { + throw new FS.ErrnoError(52); + } + NODEFS.setattr( + path, + node, + attr, + fs.chmodSync, + fs.utimesSync, + fs.truncateSync, + fs.lstatSync + ); + }, + lookup(parent, name) { + var path = PATH.join2(NODEFS.realPath(parent), name); + var mode = NODEFS.getMode(path); + return NODEFS.createNode(parent, name, mode); + }, + mknod(parent, name, mode, dev) { + var node = NODEFS.createNode(parent, name, mode, dev); + // create the backing node for this in the fs root as well + var path = NODEFS.realPath(node); + NODEFS.tryFSOperation(() => { + if (FS.isDir(node.mode)) { + fs.mkdirSync(path, node.mode); + } else { + fs.writeFileSync(path, '', { + mode: node.mode, + }); + } + }); + return node; + }, + rename(oldNode, newDir, newName) { + var oldPath = NODEFS.realPath(oldNode); + var newPath = PATH.join2(NODEFS.realPath(newDir), newName); + try { + FS.unlink(newPath); + } catch (e) {} + NODEFS.tryFSOperation(() => fs.renameSync(oldPath, newPath)); + oldNode.name = newName; + }, + unlink(parent, name) { + var path = PATH.join2(NODEFS.realPath(parent), name); + NODEFS.tryFSOperation(() => fs.unlinkSync(path)); + }, + rmdir(parent, name) { + var path = PATH.join2(NODEFS.realPath(parent), name); + NODEFS.tryFSOperation(() => fs.rmdirSync(path)); + }, + readdir(node) { + var path = NODEFS.realPath(node); + return NODEFS.tryFSOperation(() => fs.readdirSync(path)); + }, + symlink(parent, newName, oldPath) { + var newPath = PATH.join2(NODEFS.realPath(parent), newName); + NODEFS.tryFSOperation(() => fs.symlinkSync(oldPath, newPath)); + }, + readlink(node) { + var path = NODEFS.realPath(node); + return NODEFS.tryFSOperation(() => fs.readlinkSync(path)); + }, + statfs(path) { + var stats = NODEFS.tryFSOperation(() => fs.statfsSync(path)); + // Node.js doesn't provide frsize (fragment size). Set it to bsize (block size) + // as they're often the same in many file systems. May not be accurate for all. + stats.frsize = stats.bsize; + return stats; + }, + }, + stream_ops: { + getattr(stream) { + return NODEFS.getattr( + () => fs.fstatSync(stream.nfd), + stream.node + ); + }, + setattr(stream, attr) { + NODEFS.setattr( + stream.nfd, + stream.node, + attr, + fs.fchmodSync, + fs.futimesSync, + fs.ftruncateSync, + fs.fstatSync + ); + }, + open(stream) { + var path = NODEFS.realPath(stream.node); + NODEFS.tryFSOperation(() => { + stream.shared.refcount = 1; + stream.nfd = fs.openSync( + path, + NODEFS.flagsForNode(stream.flags) + ); + }); + }, + close(stream) { + NODEFS.tryFSOperation(() => { + if (stream.nfd && --stream.shared.refcount === 0) { + fs.closeSync(stream.nfd); + } + }); + }, + dup(stream) { + stream.shared.refcount++; + }, + read(stream, buffer, offset, length, position) { + return NODEFS.tryFSOperation(() => + fs.readSync( + stream.nfd, + new Int8Array(buffer.buffer, offset, length), + 0, + length, + position + ) + ); + }, + write(stream, buffer, offset, length, position) { + return NODEFS.tryFSOperation(() => + fs.writeSync( + stream.nfd, + new Int8Array(buffer.buffer, offset, length), + 0, + length, + position + ) + ); + }, + llseek(stream, offset, whence) { + var position = offset; + if (whence === 1) { + position += stream.position; + } else if (whence === 2) { + if (FS.isFile(stream.node.mode)) { + NODEFS.tryFSOperation(() => { + var stat = fs.fstatSync(stream.nfd); + position += stat.size; + }); + } + } + if (position < 0) { + throw new FS.ErrnoError(28); + } + return position; + }, + mmap(stream, length, position, prot, flags) { + if (!FS.isFile(stream.node.mode)) { + throw new FS.ErrnoError(43); + } + var ptr = mmapAlloc(length); + NODEFS.stream_ops.read(stream, HEAP8, ptr, length, position); + return { + ptr, + allocated: true, + }; + }, + msync(stream, buffer, offset, length, mmapFlags) { + NODEFS.stream_ops.write( + stream, + buffer, + 0, + length, + offset, + false + ); + // should we check if bytesWritten and length are the same? + return 0; + }, + }, + }; + + var PROXYFS = { + mount(mount) { + return PROXYFS.createNode( + null, + '/', + mount.opts.fs.lstat(mount.opts.root).mode, + 0 + ); + }, + createNode(parent, name, mode, dev) { + if (!FS.isDir(mode) && !FS.isFile(mode) && !FS.isLink(mode)) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + var node = FS.createNode(parent, name, mode); + node.node_ops = PROXYFS.node_ops; + node.stream_ops = PROXYFS.stream_ops; + return node; + }, + realPath(node) { + var parts = []; + while (node.parent !== node) { + parts.push(node.name); + node = node.parent; + } + parts.push(node.mount.opts.root); + parts.reverse(); + return PATH.join(...parts); + }, + node_ops: { + getattr(node) { + var path = PROXYFS.realPath(node); + var stat; + try { + stat = node.mount.opts.fs.lstat(path); + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + return { + dev: stat.dev, + ino: stat.ino, + mode: stat.mode, + nlink: stat.nlink, + uid: stat.uid, + gid: stat.gid, + rdev: stat.rdev, + size: stat.size, + atime: stat.atime, + mtime: stat.mtime, + ctime: stat.ctime, + blksize: stat.blksize, + blocks: stat.blocks, + }; + }, + setattr(node, attr) { + var path = PROXYFS.realPath(node); + try { + if (attr.mode !== undefined) { + node.mount.opts.fs.chmod(path, attr.mode); + // update the common node structure mode as well + node.mode = attr.mode; + } + if (attr.atime || attr.mtime) { + var atime = new Date(attr.atime || attr.mtime); + var mtime = new Date(attr.mtime || attr.atime); + node.mount.opts.fs.utime(path, atime, mtime); + } + if (attr.size !== undefined) { + node.mount.opts.fs.truncate(path, attr.size); + } + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + lookup(parent, name) { + try { + var path = PATH.join2(PROXYFS.realPath(parent), name); + var mode = parent.mount.opts.fs.lstat(path).mode; + var node = PROXYFS.createNode(parent, name, mode); + return node; + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + mknod(parent, name, mode, dev) { + var node = PROXYFS.createNode(parent, name, mode, dev); + // create the backing node for this in the fs root as well + var path = PROXYFS.realPath(node); + try { + if (FS.isDir(node.mode)) { + node.mount.opts.fs.mkdir(path, node.mode); + } else { + node.mount.opts.fs.writeFile(path, '', { + mode: node.mode, + }); + } + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + return node; + }, + rename(oldNode, newDir, newName) { + var oldPath = PROXYFS.realPath(oldNode); + var newPath = PATH.join2(PROXYFS.realPath(newDir), newName); + try { + oldNode.mount.opts.fs.rename(oldPath, newPath); + oldNode.name = newName; + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + unlink(parent, name) { + var path = PATH.join2(PROXYFS.realPath(parent), name); + try { + parent.mount.opts.fs.unlink(path); + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + rmdir(parent, name) { + var path = PATH.join2(PROXYFS.realPath(parent), name); + try { + parent.mount.opts.fs.rmdir(path); + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + readdir(node) { + var path = PROXYFS.realPath(node); + try { + return node.mount.opts.fs.readdir(path); + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + symlink(parent, newName, oldPath) { + var newPath = PATH.join2(PROXYFS.realPath(parent), newName); + try { + parent.mount.opts.fs.symlink(oldPath, newPath); + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + readlink(node) { + var path = PROXYFS.realPath(node); + try { + return node.mount.opts.fs.readlink(path); + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + }, + stream_ops: { + open(stream) { + var path = PROXYFS.realPath(stream.node); + try { + stream.nfd = stream.node.mount.opts.fs.open( + path, + stream.flags + ); + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + close(stream) { + try { + stream.node.mount.opts.fs.close(stream.nfd); + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + read(stream, buffer, offset, length, position) { + try { + return stream.node.mount.opts.fs.read( + stream.nfd, + buffer, + offset, + length, + position + ); + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + write(stream, buffer, offset, length, position) { + try { + return stream.node.mount.opts.fs.write( + stream.nfd, + buffer, + offset, + length, + position + ); + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + llseek(stream, offset, whence) { + var position = offset; + if (whence === 1) { + position += stream.position; + } else if (whence === 2) { + if (FS.isFile(stream.node.mode)) { + try { + var stat = stream.node.node_ops.getattr( + stream.node + ); + position += stat.size; + } catch (e) { + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + } + } + if (position < 0) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + return position; + }, + }, + }; + + var FS_createDataFile = (...args) => FS.createDataFile(...args); + + var getUniqueRunDependency = (id) => id; + + var FS_handledByPreloadPlugin = async (byteArray, fullname) => { + // Ensure plugins are ready. + if (typeof Browser != 'undefined') Browser.init(); + for (var plugin of preloadPlugins) { + if (plugin['canHandle'](fullname)) { + return plugin['handle'](byteArray, fullname); + } + } + // In no plugin handled this file then return the original/unmodified + // byteArray. + return byteArray; + }; + + var FS_preloadFile = async ( + parent, + name, + url, + canRead, + canWrite, + dontCreateFile, + canOwn, + preFinish + ) => { + // TODO we should allow people to just pass in a complete filename instead + // of parent and name being that we just join them anyways + var fullname = name + ? PATH_FS.resolve(PATH.join2(parent, name)) + : parent; + var dep = getUniqueRunDependency(`cp ${fullname}`); + // might have several active requests for the same fullname + addRunDependency(dep); + try { + var byteArray = url; + if (typeof url == 'string') { + byteArray = await asyncLoad(url); + } + byteArray = await FS_handledByPreloadPlugin(byteArray, fullname); + preFinish?.(); + if (!dontCreateFile) { + FS_createDataFile( + parent, + name, + byteArray, + canRead, + canWrite, + canOwn + ); + } + } finally { + removeRunDependency(dep); + } + }; + + var FS_createPreloadedFile = ( + parent, + name, + url, + canRead, + canWrite, + onload, + onerror, + dontCreateFile, + canOwn, + preFinish + ) => { + FS_preloadFile( + parent, + name, + url, + canRead, + canWrite, + dontCreateFile, + canOwn, + preFinish + ) + .then(onload) + .catch(onerror); + }; + + var FS = { + root: null, + mounts: [], + devices: {}, + streams: [], + nextInode: 1, + nameTable: null, + currentPath: '/', + initialized: false, + ignorePermissions: true, + filesystems: null, + syncFSRequests: 0, + readFiles: {}, + ErrnoError: class { + name = 'ErrnoError'; + // We set the `name` property to be able to identify `FS.ErrnoError` + // - the `name` is a standard ECMA-262 property of error objects. Kind of good to have it anyway. + // - when using PROXYFS, an error can come from an underlying FS + // as different FS objects have their own FS.ErrnoError each, + // the test `err instanceof FS.ErrnoError` won't detect an error coming from another filesystem, causing bugs. + // we'll use the reliable test `err.name == "ErrnoError"` instead + constructor(errno) { + this.errno = errno; + } + }, + FSStream: class { + shared = {}; + get object() { + return this.node; + } + set object(val) { + this.node = val; + } + get isRead() { + return (this.flags & 2097155) !== 1; + } + get isWrite() { + return (this.flags & 2097155) !== 0; + } + get isAppend() { + return this.flags & 1024; + } + get flags() { + return this.shared.flags; + } + set flags(val) { + this.shared.flags = val; + } + get position() { + return this.shared.position; + } + set position(val) { + this.shared.position = val; + } + }, + FSNode: class { + node_ops = {}; + stream_ops = {}; + readMode = 292 | 73; + writeMode = 146; + mounted = null; + constructor(parent, name, mode, rdev) { + if (!parent) { + parent = this; + } + this.parent = parent; + this.mount = parent.mount; + this.id = FS.nextInode++; + this.name = name; + this.mode = mode; + this.rdev = rdev; + this.atime = this.mtime = this.ctime = Date.now(); + } + get read() { + return (this.mode & this.readMode) === this.readMode; + } + set read(val) { + val + ? (this.mode |= this.readMode) + : (this.mode &= ~this.readMode); + } + get write() { + return (this.mode & this.writeMode) === this.writeMode; + } + set write(val) { + val + ? (this.mode |= this.writeMode) + : (this.mode &= ~this.writeMode); + } + get isFolder() { + return FS.isDir(this.mode); + } + get isDevice() { + return FS.isChrdev(this.mode); + } + }, + lookupPath(path, opts = {}) { + if (!path) { + throw new FS.ErrnoError(44); + } + opts.follow_mount ??= true; + if (!PATH.isAbs(path)) { + path = FS.cwd() + '/' + path; + } + // limit max consecutive symlinks to 40 (SYMLOOP_MAX). + linkloop: for (var nlinks = 0; nlinks < 40; nlinks++) { + // split the absolute path + var parts = path.split('/').filter((p) => !!p); + // start at the root + var current = FS.root; + var current_path = '/'; + for (var i = 0; i < parts.length; i++) { + var islast = i === parts.length - 1; + if (islast && opts.parent) { + // stop resolving + break; + } + if (parts[i] === '.') { + continue; + } + if (parts[i] === '..') { + current_path = PATH.dirname(current_path); + if (FS.isRoot(current)) { + path = + current_path + + '/' + + parts.slice(i + 1).join('/'); + // We're making progress here, don't let many consecutive ..'s + // lead to ELOOP + nlinks--; + continue linkloop; + } else { + current = current.parent; + } + continue; + } + current_path = PATH.join2(current_path, parts[i]); + try { + current = FS.lookupNode(current, parts[i]); + } catch (e) { + // if noent_okay is true, suppress a ENOENT in the last component + // and return an object with an undefined node. This is needed for + // resolving symlinks in the path when creating a file. + if (e?.errno === 44 && islast && opts.noent_okay) { + return { + path: current_path, + }; + } + throw e; + } + // jump to the mount's root node if this is a mountpoint + if ( + FS.isMountpoint(current) && + (!islast || opts.follow_mount) + ) { + current = current.mounted.root; + } + // by default, lookupPath will not follow a symlink if it is the final path component. + // setting opts.follow = true will override this behavior. + if (FS.isLink(current.mode) && (!islast || opts.follow)) { + if (!current.node_ops.readlink) { + throw new FS.ErrnoError(52); + } + var link = current.node_ops.readlink(current); + if (!PATH.isAbs(link)) { + link = PATH.dirname(current_path) + '/' + link; + } + path = link + '/' + parts.slice(i + 1).join('/'); + continue linkloop; + } + } + return { + path: current_path, + node: current, + }; + } + throw new FS.ErrnoError(32); + }, + getPath(node) { + var path; + while (true) { + if (FS.isRoot(node)) { + var mount = node.mount.mountpoint; + if (!path) return mount; + return mount[mount.length - 1] !== '/' + ? `${mount}/${path}` + : mount + path; + } + path = path ? `${node.name}/${path}` : node.name; + node = node.parent; + } + }, + hashName(parentid, name) { + var hash = 0; + for (var i = 0; i < name.length; i++) { + hash = ((hash << 5) - hash + name.charCodeAt(i)) | 0; + } + return ((parentid + hash) >>> 0) % FS.nameTable.length; + }, + hashAddNode(node) { + var hash = FS.hashName(node.parent.id, node.name); + node.name_next = FS.nameTable[hash]; + FS.nameTable[hash] = node; + }, + hashRemoveNode(node) { + var hash = FS.hashName(node.parent.id, node.name); + if (FS.nameTable[hash] === node) { + FS.nameTable[hash] = node.name_next; + } else { + var current = FS.nameTable[hash]; + while (current) { + if (current.name_next === node) { + current.name_next = node.name_next; + break; + } + current = current.name_next; + } + } + }, + lookupNode(parent, name) { + var errCode = FS.mayLookup(parent); + if (errCode) { + throw new FS.ErrnoError(errCode); + } + var hash = FS.hashName(parent.id, name); + for (var node = FS.nameTable[hash]; node; node = node.name_next) { + var nodeName = node.name; + if (node.parent.id === parent.id && nodeName === name) { + return node; + } + } + // if we failed to find it in the cache, call into the VFS + return FS.lookup(parent, name); + }, + createNode(parent, name, mode, rdev) { + var node = new FS.FSNode(parent, name, mode, rdev); + FS.hashAddNode(node); + return node; + }, + destroyNode(node) { + FS.hashRemoveNode(node); + }, + isRoot(node) { + return node === node.parent; + }, + isMountpoint(node) { + return !!node.mounted; + }, + isFile(mode) { + return (mode & 61440) === 32768; + }, + isDir(mode) { + return (mode & 61440) === 16384; + }, + isLink(mode) { + return (mode & 61440) === 40960; + }, + isChrdev(mode) { + return (mode & 61440) === 8192; + }, + isBlkdev(mode) { + return (mode & 61440) === 24576; + }, + isFIFO(mode) { + return (mode & 61440) === 4096; + }, + isSocket(mode) { + return (mode & 49152) === 49152; + }, + flagsToPermissionString(flag) { + var perms = ['r', 'w', 'rw'][flag & 3]; + if (flag & 512) { + perms += 'w'; + } + return perms; + }, + nodePermissions(node, perms) { + if (FS.ignorePermissions) { + return 0; + } + // return 0 if any user, group or owner bits are set. + if (perms.includes('r') && !(node.mode & 292)) { + return 2; + } else if (perms.includes('w') && !(node.mode & 146)) { + return 2; + } else if (perms.includes('x') && !(node.mode & 73)) { + return 2; + } + return 0; + }, + mayLookup(dir) { + if (!FS.isDir(dir.mode)) return 54; + var errCode = FS.nodePermissions(dir, 'x'); + if (errCode) return errCode; + if (!dir.node_ops.lookup) return 2; + return 0; + }, + mayCreate(dir, name) { + if (!FS.isDir(dir.mode)) { + return 54; + } + try { + var node = FS.lookupNode(dir, name); + return 20; + } catch (e) {} + return FS.nodePermissions(dir, 'wx'); + }, + mayDelete(dir, name, isdir) { + var node; + try { + node = FS.lookupNode(dir, name); + } catch (e) { + return e.errno; + } + var errCode = FS.nodePermissions(dir, 'wx'); + if (errCode) { + return errCode; + } + if (isdir) { + if (!FS.isDir(node.mode)) { + return 54; + } + if (FS.isRoot(node) || FS.getPath(node) === FS.cwd()) { + return 10; + } + } else { + if (FS.isDir(node.mode)) { + return 31; + } + } + return 0; + }, + mayOpen(node, flags) { + if (!node) { + return 44; + } + if (FS.isLink(node.mode)) { + return 32; + } else if (FS.isDir(node.mode)) { + if ( + FS.flagsToPermissionString(flags) !== 'r' || + flags & (512 | 64) + ) { + // TODO: check for O_SEARCH? (== search for dir only) + return 31; + } + } + return FS.nodePermissions(node, FS.flagsToPermissionString(flags)); + }, + checkOpExists(op, err) { + if (!op) { + throw new FS.ErrnoError(err); + } + return op; + }, + MAX_OPEN_FDS: 4096, + nextfd() { + for (var fd = 0; fd <= FS.MAX_OPEN_FDS; fd++) { + if (!FS.streams[fd]) { + return fd; + } + } + throw new FS.ErrnoError(33); + }, + getStreamChecked(fd) { + var stream = FS.getStream(fd); + if (!stream) { + throw new FS.ErrnoError(8); + } + return stream; + }, + getStream: (fd) => FS.streams[fd], + createStream(stream, fd = -1) { + // clone it, so we can return an instance of FSStream + stream = Object.assign(new FS.FSStream(), stream); + if (fd == -1) { + fd = FS.nextfd(); + } + stream.fd = fd; + FS.streams[fd] = stream; + return stream; + }, + closeStream(fd) { + FS.streams[fd] = null; + }, + dupStream(origStream, fd = -1) { + var stream = FS.createStream(origStream, fd); + stream.stream_ops?.dup?.(stream); + return stream; + }, + doSetAttr(stream, node, attr) { + var setattr = stream?.stream_ops.setattr; + var arg = setattr ? stream : node; + setattr ??= node.node_ops.setattr; + FS.checkOpExists(setattr, 63); + setattr(arg, attr); + }, + chrdev_stream_ops: { + open(stream) { + var device = FS.getDevice(stream.node.rdev); + // override node's stream ops with the device's + stream.stream_ops = device.stream_ops; + // forward the open call + stream.stream_ops.open?.(stream); + }, + llseek() { + throw new FS.ErrnoError(70); + }, + }, + major: (dev) => dev >> 8, + minor: (dev) => dev & 255, + makedev: (ma, mi) => (ma << 8) | mi, + registerDevice(dev, ops) { + FS.devices[dev] = { + stream_ops: ops, + }; + }, + getDevice: (dev) => FS.devices[dev], + getMounts(mount) { + var mounts = []; + var check = [mount]; + while (check.length) { + var m = check.pop(); + mounts.push(m); + check.push(...m.mounts); + } + return mounts; + }, + syncfs(populate, callback) { + if (typeof populate == 'function') { + callback = populate; + populate = false; + } + FS.syncFSRequests++; + if (FS.syncFSRequests > 1) { + err( + `warning: ${FS.syncFSRequests} FS.syncfs operations in flight at once, probably just doing extra work` + ); + } + var mounts = FS.getMounts(FS.root.mount); + var completed = 0; + function doCallback(errCode) { + FS.syncFSRequests--; + return callback(errCode); + } + function done(errCode) { + if (errCode) { + if (!done.errored) { + done.errored = true; + return doCallback(errCode); + } + return; + } + if (++completed >= mounts.length) { + doCallback(null); + } + } + // sync all mounts + for (var mount of mounts) { + if (mount.type.syncfs) { + mount.type.syncfs(mount, populate, done); + } else { + done(null); + } + } + }, + mount(type, opts, mountpoint) { + var root = mountpoint === '/'; + var pseudo = !mountpoint; + var node; + if (root && FS.root) { + throw new FS.ErrnoError(10); + } else if (!root && !pseudo) { + var lookup = FS.lookupPath(mountpoint, { + follow_mount: false, + }); + mountpoint = lookup.path; + // use the absolute path + node = lookup.node; + if (FS.isMountpoint(node)) { + throw new FS.ErrnoError(10); + } + } + var mount = { + type, + opts, + mountpoint, + mounts: [], + }; + // create a root node for the fs + var mountRoot = type.mount(mount); + mountRoot.mount = mount; + mount.root = mountRoot; + if (root) { + FS.root = mountRoot; + } else if (node) { + // set as a mountpoint + node.mounted = mount; + // add the new mount to the current mount's children + if (node.mount) { + node.mount.mounts.push(mount); + } + } + return mountRoot; + }, + unmount(mountpoint) { + var lookup = FS.lookupPath(mountpoint, { + follow_mount: false, + }); + if (!FS.isMountpoint(lookup.node)) { + throw new FS.ErrnoError(28); + } + // destroy the nodes for this mount, and all its child mounts + var node = lookup.node; + var mount = node.mounted; + var mounts = FS.getMounts(mount); + for (var [hash, current] of Object.entries(FS.nameTable)) { + while (current) { + var next = current.name_next; + if (mounts.includes(current.mount)) { + FS.destroyNode(current); + } + current = next; + } + } + // no longer a mountpoint + node.mounted = null; + // remove this mount from the child mounts + var idx = node.mount.mounts.indexOf(mount); + node.mount.mounts.splice(idx, 1); + }, + lookup(parent, name) { + return parent.node_ops.lookup(parent, name); + }, + mknod(path, mode, dev) { + var lookup = FS.lookupPath(path, { + parent: true, + }); + var parent = lookup.node; + var name = PATH.basename(path); + if (!name) { + throw new FS.ErrnoError(28); + } + if (name === '.' || name === '..') { + throw new FS.ErrnoError(20); + } + var errCode = FS.mayCreate(parent, name); + if (errCode) { + throw new FS.ErrnoError(errCode); + } + if (!parent.node_ops.mknod) { + throw new FS.ErrnoError(63); + } + return parent.node_ops.mknod(parent, name, mode, dev); + }, + statfs(path) { + return FS.statfsNode( + FS.lookupPath(path, { + follow: true, + }).node + ); + }, + statfsStream(stream) { + // We keep a separate statfsStream function because noderawfs overrides + // it. In noderawfs, stream.node is sometimes null. Instead, we need to + // look at stream.path. + return FS.statfsNode(stream.node); + }, + statfsNode(node) { + // NOTE: None of the defaults here are true. We're just returning safe and + // sane values. Currently nodefs and rawfs replace these defaults, + // other file systems leave them alone. + var rtn = { + bsize: 4096, + frsize: 4096, + blocks: 1e6, + bfree: 5e5, + bavail: 5e5, + files: FS.nextInode, + ffree: FS.nextInode - 1, + fsid: 42, + flags: 2, + namelen: 255, + }; + if (node.node_ops.statfs) { + Object.assign(rtn, node.node_ops.statfs(node.mount.opts.root)); + } + return rtn; + }, + create(path, mode = 438) { + mode &= 4095; + mode |= 32768; + return FS.mknod(path, mode, 0); + }, + mkdir(path, mode = 511) { + mode &= 511 | 512; + mode |= 16384; + return FS.mknod(path, mode, 0); + }, + mkdirTree(path, mode) { + var dirs = path.split('/'); + var d = ''; + for (var dir of dirs) { + if (!dir) continue; + if (d || PATH.isAbs(path)) d += '/'; + d += dir; + try { + FS.mkdir(d, mode); + } catch (e) { + if (e.errno != 20) throw e; + } + } + }, + mkdev(path, mode, dev) { + if (typeof dev == 'undefined') { + dev = mode; + mode = 438; + } + mode |= 8192; + return FS.mknod(path, mode, dev); + }, + symlink(oldpath, newpath) { + if (!PATH_FS.resolve(oldpath)) { + throw new FS.ErrnoError(44); + } + var lookup = FS.lookupPath(newpath, { + parent: true, + }); + var parent = lookup.node; + if (!parent) { + throw new FS.ErrnoError(44); + } + var newname = PATH.basename(newpath); + var errCode = FS.mayCreate(parent, newname); + if (errCode) { + throw new FS.ErrnoError(errCode); + } + if (!parent.node_ops.symlink) { + throw new FS.ErrnoError(63); + } + return parent.node_ops.symlink(parent, newname, oldpath); + }, + rename(old_path, new_path) { + var old_dirname = PATH.dirname(old_path); + var new_dirname = PATH.dirname(new_path); + var old_name = PATH.basename(old_path); + var new_name = PATH.basename(new_path); + // parents must exist + var lookup, old_dir, new_dir; + // let the errors from non existent directories percolate up + lookup = FS.lookupPath(old_path, { + parent: true, + }); + old_dir = lookup.node; + lookup = FS.lookupPath(new_path, { + parent: true, + }); + new_dir = lookup.node; + if (!old_dir || !new_dir) throw new FS.ErrnoError(44); + // need to be part of the same mount + if (old_dir.mount !== new_dir.mount) { + throw new FS.ErrnoError(75); + } + // source must exist + var old_node = FS.lookupNode(old_dir, old_name); + // old path should not be an ancestor of the new path + var relative = PATH_FS.relative(old_path, new_dirname); + if (relative.charAt(0) !== '.') { + throw new FS.ErrnoError(28); + } + // new path should not be an ancestor of the old path + relative = PATH_FS.relative(new_path, old_dirname); + if (relative.charAt(0) !== '.') { + throw new FS.ErrnoError(55); + } + // see if the new path already exists + var new_node; + try { + new_node = FS.lookupNode(new_dir, new_name); + } catch (e) {} + // early out if nothing needs to change + if (old_node === new_node) { + return; + } + // we'll need to delete the old entry + var isdir = FS.isDir(old_node.mode); + var errCode = FS.mayDelete(old_dir, old_name, isdir); + if (errCode) { + throw new FS.ErrnoError(errCode); + } + // need delete permissions if we'll be overwriting. + // need create permissions if new doesn't already exist. + errCode = new_node + ? FS.mayDelete(new_dir, new_name, isdir) + : FS.mayCreate(new_dir, new_name); + if (errCode) { + throw new FS.ErrnoError(errCode); + } + if (!old_dir.node_ops.rename) { + throw new FS.ErrnoError(63); + } + if ( + FS.isMountpoint(old_node) || + (new_node && FS.isMountpoint(new_node)) + ) { + throw new FS.ErrnoError(10); + } + // if we are going to change the parent, check write permissions + if (new_dir !== old_dir) { + errCode = FS.nodePermissions(old_dir, 'w'); + if (errCode) { + throw new FS.ErrnoError(errCode); + } + } + // remove the node from the lookup hash + FS.hashRemoveNode(old_node); + // do the underlying fs rename + try { + old_dir.node_ops.rename(old_node, new_dir, new_name); + // update old node (we do this here to avoid each backend + // needing to) + old_node.parent = new_dir; + } catch (e) { + throw e; + } finally { + // add the node back to the hash (in case node_ops.rename + // changed its name) + FS.hashAddNode(old_node); + } + }, + rmdir(path) { + var lookup = FS.lookupPath(path, { + parent: true, + }); + var parent = lookup.node; + var name = PATH.basename(path); + var node = FS.lookupNode(parent, name); + var errCode = FS.mayDelete(parent, name, true); + if (errCode) { + throw new FS.ErrnoError(errCode); + } + if (!parent.node_ops.rmdir) { + throw new FS.ErrnoError(63); + } + if (FS.isMountpoint(node)) { + throw new FS.ErrnoError(10); + } + parent.node_ops.rmdir(parent, name); + FS.destroyNode(node); + }, + readdir(path) { + var lookup = FS.lookupPath(path, { + follow: true, + }); + var node = lookup.node; + var readdir = FS.checkOpExists(node.node_ops.readdir, 54); + return readdir(node); + }, + unlink(path) { + var lookup = FS.lookupPath(path, { + parent: true, + }); + var parent = lookup.node; + if (!parent) { + throw new FS.ErrnoError(44); + } + var name = PATH.basename(path); + var node = FS.lookupNode(parent, name); + var errCode = FS.mayDelete(parent, name, false); + if (errCode) { + // According to POSIX, we should map EISDIR to EPERM, but + // we instead do what Linux does (and we must, as we use + // the musl linux libc). + throw new FS.ErrnoError(errCode); + } + if (!parent.node_ops.unlink) { + throw new FS.ErrnoError(63); + } + if (FS.isMountpoint(node)) { + throw new FS.ErrnoError(10); + } + parent.node_ops.unlink(parent, name); + FS.destroyNode(node); + }, + readlink(path) { + var lookup = FS.lookupPath(path); + var link = lookup.node; + if (!link) { + throw new FS.ErrnoError(44); + } + if (!link.node_ops.readlink) { + throw new FS.ErrnoError(28); + } + return link.node_ops.readlink(link); + }, + stat(path, dontFollow) { + var lookup = FS.lookupPath(path, { + follow: !dontFollow, + }); + var node = lookup.node; + var getattr = FS.checkOpExists(node.node_ops.getattr, 63); + return getattr(node); + }, + fstat(fd) { + var stream = FS.getStreamChecked(fd); + var node = stream.node; + var getattr = stream.stream_ops.getattr; + var arg = getattr ? stream : node; + getattr ??= node.node_ops.getattr; + FS.checkOpExists(getattr, 63); + return getattr(arg); + }, + lstat(path) { + return FS.stat(path, true); + }, + doChmod(stream, node, mode, dontFollow) { + FS.doSetAttr(stream, node, { + mode: (mode & 4095) | (node.mode & ~4095), + ctime: Date.now(), + dontFollow, + }); + }, + chmod(path, mode, dontFollow) { + var node; + if (typeof path == 'string') { + var lookup = FS.lookupPath(path, { + follow: !dontFollow, + }); + node = lookup.node; + } else { + node = path; + } + FS.doChmod(null, node, mode, dontFollow); + }, + lchmod(path, mode) { + FS.chmod(path, mode, true); + }, + fchmod(fd, mode) { + var stream = FS.getStreamChecked(fd); + FS.doChmod(stream, stream.node, mode, false); + }, + doChown(stream, node, dontFollow) { + FS.doSetAttr(stream, node, { + timestamp: Date.now(), + dontFollow, + }); + }, + chown(path, uid, gid, dontFollow) { + var node; + if (typeof path == 'string') { + var lookup = FS.lookupPath(path, { + follow: !dontFollow, + }); + node = lookup.node; + } else { + node = path; + } + FS.doChown(null, node, dontFollow); + }, + lchown(path, uid, gid) { + FS.chown(path, uid, gid, true); + }, + fchown(fd, uid, gid) { + var stream = FS.getStreamChecked(fd); + FS.doChown(stream, stream.node, false); + }, + doTruncate(stream, node, len) { + if (FS.isDir(node.mode)) { + throw new FS.ErrnoError(31); + } + if (!FS.isFile(node.mode)) { + throw new FS.ErrnoError(28); + } + var errCode = FS.nodePermissions(node, 'w'); + if (errCode) { + throw new FS.ErrnoError(errCode); + } + FS.doSetAttr(stream, node, { + size: len, + timestamp: Date.now(), + }); + }, + truncate(path, len) { + if (len < 0) { + throw new FS.ErrnoError(28); + } + var node; + if (typeof path == 'string') { + var lookup = FS.lookupPath(path, { + follow: true, + }); + node = lookup.node; + } else { + node = path; + } + FS.doTruncate(null, node, len); + }, + ftruncate(fd, len) { + var stream = FS.getStreamChecked(fd); + if (len < 0 || (stream.flags & 2097155) === 0) { + throw new FS.ErrnoError(28); + } + FS.doTruncate(stream, stream.node, len); + }, + utime(path, atime, mtime) { + var lookup = FS.lookupPath(path, { + follow: true, + }); + var node = lookup.node; + var setattr = FS.checkOpExists(node.node_ops.setattr, 63); + setattr(node, { + atime, + mtime, + }); + }, + open(path, flags, mode = 438) { + if (path === '') { + throw new FS.ErrnoError(44); + } + flags = + typeof flags == 'string' ? FS_modeStringToFlags(flags) : flags; + if (flags & 64) { + mode = (mode & 4095) | 32768; + } else { + mode = 0; + } + var node; + var isDirPath; + if (typeof path == 'object') { + node = path; + } else { + isDirPath = path.endsWith('/'); + // noent_okay makes it so that if the final component of the path + // doesn't exist, lookupPath returns `node: undefined`. `path` will be + // updated to point to the target of all symlinks. + var lookup = FS.lookupPath(path, { + follow: !(flags & 131072), + noent_okay: true, + }); + node = lookup.node; + path = lookup.path; + } + // perhaps we need to create the node + var created = false; + if (flags & 64) { + if (node) { + // if O_CREAT and O_EXCL are set, error out if the node already exists + if (flags & 128) { + throw new FS.ErrnoError(20); + } + } else if (isDirPath) { + throw new FS.ErrnoError(31); + } else { + // node doesn't exist, try to create it + // Ignore the permission bits here to ensure we can `open` this new + // file below. We use chmod below the apply the permissions once the + // file is open. + node = FS.mknod(path, mode | 511, 0); + created = true; + } + } + if (!node) { + throw new FS.ErrnoError(44); + } + // can't truncate a device + if (FS.isChrdev(node.mode)) { + flags &= ~512; + } + // if asked only for a directory, then this must be one + if (flags & 65536 && !FS.isDir(node.mode)) { + throw new FS.ErrnoError(54); + } + // check permissions, if this is not a file we just created now (it is ok to + // create and write to a file with read-only permissions; it is read-only + // for later use) + if (!created) { + var errCode = FS.mayOpen(node, flags); + if (errCode) { + throw new FS.ErrnoError(errCode); + } + } + // do truncation if necessary + if (flags & 512 && !created) { + FS.truncate(node, 0); + } + // we've already handled these, don't pass down to the underlying vfs + flags &= ~(128 | 512 | 131072); + // register the stream with the filesystem + var stream = FS.createStream({ + node, + path: FS.getPath(node), + // we want the absolute path to the node + flags, + seekable: true, + position: 0, + stream_ops: node.stream_ops, + // used by the file family libc calls (fopen, fwrite, ferror, etc.) + ungotten: [], + error: false, + }); + // call the new stream's open function + if (stream.stream_ops.open) { + stream.stream_ops.open(stream); + } + if (created) { + FS.chmod(node, mode & 511); + } + if (Module['logReadFiles'] && !(flags & 1)) { + if (!(path in FS.readFiles)) { + FS.readFiles[path] = 1; + } + } + return stream; + }, + close(stream) { + if (FS.isClosed(stream)) { + throw new FS.ErrnoError(8); + } + if (stream.getdents) stream.getdents = null; + // free readdir state + try { + if (stream.stream_ops.close) { + stream.stream_ops.close(stream); + } + } catch (e) { + throw e; + } finally { + FS.closeStream(stream.fd); + } + stream.fd = null; + }, + isClosed(stream) { + return stream.fd === null; + }, + llseek(stream, offset, whence) { + if (FS.isClosed(stream)) { + throw new FS.ErrnoError(8); + } + if (!stream.seekable || !stream.stream_ops.llseek) { + throw new FS.ErrnoError(70); + } + if (whence != 0 && whence != 1 && whence != 2) { + throw new FS.ErrnoError(28); + } + stream.position = stream.stream_ops.llseek(stream, offset, whence); + stream.ungotten = []; + return stream.position; + }, + read(stream, buffer, offset, length, position) { + if (length < 0 || position < 0) { + throw new FS.ErrnoError(28); + } + if (FS.isClosed(stream)) { + throw new FS.ErrnoError(8); + } + if ((stream.flags & 2097155) === 1) { + throw new FS.ErrnoError(8); + } + if (FS.isDir(stream.node.mode)) { + throw new FS.ErrnoError(31); + } + if (!stream.stream_ops.read) { + throw new FS.ErrnoError(28); + } + var seeking = typeof position != 'undefined'; + if (!seeking) { + position = stream.position; + } else if (!stream.seekable) { + throw new FS.ErrnoError(70); + } + var bytesRead = stream.stream_ops.read( + stream, + buffer, + offset, + length, + position + ); + if (!seeking) stream.position += bytesRead; + return bytesRead; + }, + write(stream, buffer, offset, length, position, canOwn) { + if (length < 0 || position < 0) { + throw new FS.ErrnoError(28); + } + if (FS.isClosed(stream)) { + throw new FS.ErrnoError(8); + } + if ((stream.flags & 2097155) === 0) { + throw new FS.ErrnoError(8); + } + if (FS.isDir(stream.node.mode)) { + throw new FS.ErrnoError(31); + } + if (!stream.stream_ops.write) { + throw new FS.ErrnoError(28); + } + if (stream.seekable && stream.flags & 1024) { + // seek to the end before writing in append mode + FS.llseek(stream, 0, 2); + } + var seeking = typeof position != 'undefined'; + if (!seeking) { + position = stream.position; + } else if (!stream.seekable) { + throw new FS.ErrnoError(70); + } + var bytesWritten = stream.stream_ops.write( + stream, + buffer, + offset, + length, + position, + canOwn + ); + if (!seeking) stream.position += bytesWritten; + return bytesWritten; + }, + mmap(stream, length, position, prot, flags) { + // User requests writing to file (prot & PROT_WRITE != 0). + // Checking if we have permissions to write to the file unless + // MAP_PRIVATE flag is set. According to POSIX spec it is possible + // to write to file opened in read-only mode with MAP_PRIVATE flag, + // as all modifications will be visible only in the memory of + // the current process. + if ( + (prot & 2) !== 0 && + (flags & 2) === 0 && + (stream.flags & 2097155) !== 2 + ) { + throw new FS.ErrnoError(2); + } + if ((stream.flags & 2097155) === 1) { + throw new FS.ErrnoError(2); + } + if (!stream.stream_ops.mmap) { + throw new FS.ErrnoError(43); + } + if (!length) { + throw new FS.ErrnoError(28); + } + return stream.stream_ops.mmap( + stream, + length, + position, + prot, + flags + ); + }, + msync(stream, buffer, offset, length, mmapFlags) { + if (!stream.stream_ops.msync) { + return 0; + } + return stream.stream_ops.msync( + stream, + buffer, + offset, + length, + mmapFlags + ); + }, + ioctl(stream, cmd, arg) { + if (!stream.stream_ops.ioctl) { + throw new FS.ErrnoError(59); + } + return stream.stream_ops.ioctl(stream, cmd, arg); + }, + readFile(path, opts = {}) { + opts.flags = opts.flags || 0; + opts.encoding = opts.encoding || 'binary'; + if (opts.encoding !== 'utf8' && opts.encoding !== 'binary') { + abort(`Invalid encoding type "${opts.encoding}"`); + } + var stream = FS.open(path, opts.flags); + var stat = FS.stat(path); + var length = stat.size; + var buf = new Uint8Array(length); + FS.read(stream, buf, 0, length, 0); + if (opts.encoding === 'utf8') { + buf = UTF8ArrayToString(buf); + } + FS.close(stream); + return buf; + }, + writeFile(path, data, opts = {}) { + opts.flags = opts.flags || 577; + var stream = FS.open(path, opts.flags, opts.mode); + if (typeof data == 'string') { + data = new Uint8Array(intArrayFromString(data, true)); + } + if (ArrayBuffer.isView(data)) { + FS.write( + stream, + data, + 0, + data.byteLength, + undefined, + opts.canOwn + ); + } else { + abort('Unsupported data type'); + } + FS.close(stream); + }, + cwd: () => FS.currentPath, + chdir(path) { + var lookup = FS.lookupPath(path, { + follow: true, + }); + if (lookup.node === null) { + throw new FS.ErrnoError(44); + } + if (!FS.isDir(lookup.node.mode)) { + throw new FS.ErrnoError(54); + } + var errCode = FS.nodePermissions(lookup.node, 'x'); + if (errCode) { + throw new FS.ErrnoError(errCode); + } + FS.currentPath = lookup.path; + }, + createDefaultDirectories() { + FS.mkdir('/tmp'); + FS.mkdir('/home'); + FS.mkdir('/home/web_user'); + }, + createDefaultDevices() { + // create /dev + FS.mkdir('/dev'); + // setup /dev/null + FS.registerDevice(FS.makedev(1, 3), { + read: () => 0, + write: (stream, buffer, offset, length, pos) => length, + llseek: () => 0, + }); + FS.mkdev('/dev/null', FS.makedev(1, 3)); + // setup /dev/tty and /dev/tty1 + // stderr needs to print output using err() rather than out() + // so we register a second tty just for it. + TTY.register(FS.makedev(5, 0), TTY.default_tty_ops); + TTY.register(FS.makedev(6, 0), TTY.default_tty1_ops); + FS.mkdev('/dev/tty', FS.makedev(5, 0)); + FS.mkdev('/dev/tty1', FS.makedev(6, 0)); + // setup /dev/[u]random + // use a buffer to avoid overhead of individual crypto calls per byte + var randomBuffer = new Uint8Array(1024), + randomLeft = 0; + var randomByte = () => { + if (randomLeft === 0) { + randomFill(randomBuffer); + randomLeft = randomBuffer.byteLength; + } + return randomBuffer[--randomLeft]; + }; + FS.createDevice('/dev', 'random', randomByte); + FS.createDevice('/dev', 'urandom', randomByte); + // we're not going to emulate the actual shm device, + // just create the tmp dirs that reside in it commonly + FS.mkdir('/dev/shm'); + FS.mkdir('/dev/shm/tmp'); + }, + createSpecialDirectories() { + // create /proc/self/fd which allows /proc/self/fd/6 => readlink gives the + // name of the stream for fd 6 (see test_unistd_ttyname) + FS.mkdir('/proc'); + var proc_self = FS.mkdir('/proc/self'); + FS.mkdir('/proc/self/fd'); + FS.mount( + { + mount() { + var node = FS.createNode(proc_self, 'fd', 16895, 73); + node.stream_ops = { + llseek: MEMFS.stream_ops.llseek, + }; + node.node_ops = { + lookup(parent, name) { + var fd = +name; + var stream = FS.getStreamChecked(fd); + var ret = { + parent: null, + mount: { + mountpoint: 'fake', + }, + node_ops: { + readlink: () => stream.path, + }, + id: fd + 1, + }; + ret.parent = ret; + // make it look like a simple root node + return ret; + }, + readdir() { + return Array.from(FS.streams.entries()) + .filter(([k, v]) => v) + .map(([k, v]) => k.toString()); + }, + }; + return node; + }, + }, + {}, + '/proc/self/fd' + ); + }, + createStandardStreams(input, output, error) { + // TODO deprecate the old functionality of a single + // input / output callback and that utilizes FS.createDevice + // and instead require a unique set of stream ops + // by default, we symlink the standard streams to the + // default tty devices. however, if the standard streams + // have been overwritten we create a unique device for + // them instead. + if (input) { + FS.createDevice('/dev', 'stdin', input); + } else { + FS.symlink('/dev/tty', '/dev/stdin'); + } + if (output) { + FS.createDevice('/dev', 'stdout', null, output); + } else { + FS.symlink('/dev/tty', '/dev/stdout'); + } + if (error) { + FS.createDevice('/dev', 'stderr', null, error); + } else { + FS.symlink('/dev/tty1', '/dev/stderr'); + } + // open default streams for the stdin, stdout and stderr devices + var stdin = FS.open('/dev/stdin', 0); + var stdout = FS.open('/dev/stdout', 1); + var stderr = FS.open('/dev/stderr', 1); + }, + staticInit() { + FS.nameTable = new Array(4096); + FS.mount(MEMFS, {}, '/'); + FS.createDefaultDirectories(); + FS.createDefaultDevices(); + FS.createSpecialDirectories(); + FS.filesystems = { + MEMFS: MEMFS, + NODEFS: NODEFS, + PROXYFS: PROXYFS, + }; + }, + init(input, output, error) { + FS.initialized = true; + // Allow Module.stdin etc. to provide defaults, if none explicitly passed to us here + input ??= Module['stdin']; + output ??= Module['stdout']; + error ??= Module['stderr']; + FS.createStandardStreams(input, output, error); + }, + quit() { + FS.initialized = false; + // force-flush all streams, so we get musl std streams printed out + _fflush(0); + // close all of our streams + for (var stream of FS.streams) { + if (stream) { + FS.close(stream); + } + } + }, + findObject(path, dontResolveLastLink) { + var ret = FS.analyzePath(path, dontResolveLastLink); + if (!ret.exists) { + return null; + } + return ret.object; + }, + analyzePath(path, dontResolveLastLink) { + // operate from within the context of the symlink's target + try { + var lookup = FS.lookupPath(path, { + follow: !dontResolveLastLink, + }); + path = lookup.path; + } catch (e) {} + var ret = { + isRoot: false, + exists: false, + error: 0, + name: null, + path: null, + object: null, + parentExists: false, + parentPath: null, + parentObject: null, + }; + try { + var lookup = FS.lookupPath(path, { + parent: true, + }); + ret.parentExists = true; + ret.parentPath = lookup.path; + ret.parentObject = lookup.node; + ret.name = PATH.basename(path); + lookup = FS.lookupPath(path, { + follow: !dontResolveLastLink, + }); + ret.exists = true; + ret.path = lookup.path; + ret.object = lookup.node; + ret.name = lookup.node.name; + ret.isRoot = lookup.path === '/'; + } catch (e) { + ret.error = e.errno; + } + return ret; + }, + createPath(parent, path, canRead, canWrite) { + parent = typeof parent == 'string' ? parent : FS.getPath(parent); + var parts = path.split('/').reverse(); + while (parts.length) { + var part = parts.pop(); + if (!part) continue; + var current = PATH.join2(parent, part); + try { + FS.mkdir(current); + } catch (e) { + if (e.errno != 20) throw e; + } + parent = current; + } + return current; + }, + createFile(parent, name, properties, canRead, canWrite) { + var path = PATH.join2( + typeof parent == 'string' ? parent : FS.getPath(parent), + name + ); + var mode = FS_getMode(canRead, canWrite); + return FS.create(path, mode); + }, + createDataFile(parent, name, data, canRead, canWrite, canOwn) { + var path = name; + if (parent) { + parent = + typeof parent == 'string' ? parent : FS.getPath(parent); + path = name ? PATH.join2(parent, name) : parent; + } + var mode = FS_getMode(canRead, canWrite); + var node = FS.create(path, mode); + if (data) { + if (typeof data == 'string') { + var arr = new Array(data.length); + for (var i = 0, len = data.length; i < len; ++i) + arr[i] = data.charCodeAt(i); + data = arr; + } + // make sure we can write to the file + FS.chmod(node, mode | 146); + var stream = FS.open(node, 577); + FS.write(stream, data, 0, data.length, 0, canOwn); + FS.close(stream); + FS.chmod(node, mode); + } + }, + createDevice(parent, name, input, output) { + var path = PATH.join2( + typeof parent == 'string' ? parent : FS.getPath(parent), + name + ); + var mode = FS_getMode(!!input, !!output); + FS.createDevice.major ??= 64; + var dev = FS.makedev(FS.createDevice.major++, 0); + // Create a fake device that a set of stream ops to emulate + // the old behavior. + FS.registerDevice(dev, { + open(stream) { + stream.seekable = false; + }, + close(stream) { + // flush any pending line data + if (output?.buffer?.length) { + output(10); + } + }, + read(stream, buffer, offset, length, pos) { + var bytesRead = 0; + for (var i = 0; i < length; i++) { + var result; + try { + result = input(); + } catch (e) { + throw new FS.ErrnoError(29); + } + if (result === undefined && bytesRead === 0) { + throw new FS.ErrnoError(6); + } + if (result === null || result === undefined) break; + bytesRead++; + buffer[offset + i] = result; + } + if (bytesRead) { + stream.node.atime = Date.now(); + } + return bytesRead; + }, + write(stream, buffer, offset, length, pos) { + for (var i = 0; i < length; i++) { + try { + output(buffer[offset + i]); + } catch (e) { + throw new FS.ErrnoError(29); + } + } + if (length) { + stream.node.mtime = stream.node.ctime = Date.now(); + } + return i; + }, + }); + return FS.mkdev(path, mode, dev); + }, + forceLoadFile(obj) { + if (obj.isDevice || obj.isFolder || obj.link || obj.contents) + return true; + if (globalThis.XMLHttpRequest) { + abort( + 'Lazy loading should have been performed (contents set) in createLazyFile, but it was not. Lazy loading only works in web workers. Use --embed-file or --preload-file in emcc on the main thread.' + ); + } else { + // Command-line. + try { + obj.contents = readBinary(obj.url); + } catch (e) { + throw new FS.ErrnoError(29); + } + } + }, + createLazyFile(parent, name, url, canRead, canWrite) { + // Lazy chunked Uint8Array (implements get and length from Uint8Array). + // Actual getting is abstracted away for eventual reuse. + class LazyUint8Array { + lengthKnown = false; + chunks = []; + // Loaded chunks. Index is the chunk number + get(idx) { + if (idx > this.length - 1 || idx < 0) { + return undefined; + } + var chunkOffset = idx % this.chunkSize; + var chunkNum = (idx / this.chunkSize) | 0; + return this.getter(chunkNum)[chunkOffset]; + } + setDataGetter(getter) { + this.getter = getter; + } + cacheLength() { + // Find length + var xhr = new XMLHttpRequest(); + xhr.open('HEAD', url, false); + xhr.send(null); + if ( + !( + (xhr.status >= 200 && xhr.status < 300) || + xhr.status === 304 + ) + ) + abort( + "Couldn't load " + url + '. Status: ' + xhr.status + ); + var datalength = Number( + xhr.getResponseHeader('Content-length') + ); + var header; + var hasByteServing = + (header = xhr.getResponseHeader('Accept-Ranges')) && + header === 'bytes'; + var usesGzip = + (header = xhr.getResponseHeader('Content-Encoding')) && + header === 'gzip'; + var chunkSize = 1024 * 1024; + // Chunk size in bytes + if (!hasByteServing) chunkSize = datalength; + // Function to get a range from the remote URL. + var doXHR = (from, to) => { + if (from > to) + abort( + 'invalid range (' + + from + + ', ' + + to + + ') or no bytes requested!' + ); + if (to > datalength - 1) + abort( + 'only ' + + datalength + + ' bytes available! programmer error!' + ); + // TODO: Use mozResponseArrayBuffer, responseStream, etc. if available. + var xhr = new XMLHttpRequest(); + xhr.open('GET', url, false); + if (datalength !== chunkSize) + xhr.setRequestHeader( + 'Range', + 'bytes=' + from + '-' + to + ); + // Some hints to the browser that we want binary data. + xhr.responseType = 'arraybuffer'; + if (xhr.overrideMimeType) { + xhr.overrideMimeType( + 'text/plain; charset=x-user-defined' + ); + } + xhr.send(null); + if ( + !( + (xhr.status >= 200 && xhr.status < 300) || + xhr.status === 304 + ) + ) + abort( + "Couldn't load " + + url + + '. Status: ' + + xhr.status + ); + if (xhr.response !== undefined) { + return new Uint8Array( + /** @type{Array} */ (xhr.response || []) + ); + } + return intArrayFromString(xhr.responseText || '', true); + }; + var lazyArray = this; + lazyArray.setDataGetter((chunkNum) => { + var start = chunkNum * chunkSize; + var end = (chunkNum + 1) * chunkSize - 1; + // including this byte + end = Math.min(end, datalength - 1); + // if datalength-1 is selected, this is the last block + if (typeof lazyArray.chunks[chunkNum] == 'undefined') { + lazyArray.chunks[chunkNum] = doXHR(start, end); + } + if (typeof lazyArray.chunks[chunkNum] == 'undefined') + abort('doXHR failed!'); + return lazyArray.chunks[chunkNum]; + }); + if (usesGzip || !datalength) { + // if the server uses gzip or doesn't supply the length, we have to download the whole file to get the (uncompressed) length + chunkSize = datalength = 1; + // this will force getter(0)/doXHR do download the whole file + datalength = this.getter(0).length; + chunkSize = datalength; + out( + 'LazyFiles on gzip forces download of the whole file when length is accessed' + ); + } + this._length = datalength; + this._chunkSize = chunkSize; + this.lengthKnown = true; + } + get length() { + if (!this.lengthKnown) { + this.cacheLength(); + } + return this._length; + } + get chunkSize() { + if (!this.lengthKnown) { + this.cacheLength(); + } + return this._chunkSize; + } + } + if (globalThis.XMLHttpRequest) { + if (!ENVIRONMENT_IS_WORKER) + abort( + 'Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc' + ); + var lazyArray = new LazyUint8Array(); + var properties = { + isDevice: false, + contents: lazyArray, + }; + } else { + var properties = { + isDevice: false, + url, + }; + } + var node = FS.createFile( + parent, + name, + properties, + canRead, + canWrite + ); + // This is a total hack, but I want to get this lazy file code out of the + // core of MEMFS. If we want to keep this lazy file concept I feel it should + // be its own thin LAZYFS proxying calls to MEMFS. + if (properties.contents) { + node.contents = properties.contents; + } else if (properties.url) { + node.contents = null; + node.url = properties.url; + } + // Add a function that defers querying the file size until it is asked the first time. + Object.defineProperties(node, { + usedBytes: { + get: function () { + return this.contents.length; + }, + }, + }); + // override each stream op with one that tries to force load the lazy file first + var stream_ops = {}; + for (const [key, fn] of Object.entries(node.stream_ops)) { + stream_ops[key] = (...args) => { + FS.forceLoadFile(node); + return fn(...args); + }; + } + function writeChunks(stream, buffer, offset, length, position) { + var contents = stream.node.contents; + if (position >= contents.length) return 0; + var size = Math.min(contents.length - position, length); + if (contents.slice) { + // normal array + for (var i = 0; i < size; i++) { + buffer[offset + i] = contents[position + i]; + } + } else { + for (var i = 0; i < size; i++) { + // LazyUint8Array from sync binary XHR + buffer[offset + i] = contents.get(position + i); + } + } + return size; + } + // use a custom read function + stream_ops.read = (stream, buffer, offset, length, position) => { + FS.forceLoadFile(node); + return writeChunks(stream, buffer, offset, length, position); + }; + // use a custom mmap function + stream_ops.mmap = (stream, length, position, prot, flags) => { + FS.forceLoadFile(node); + var ptr = mmapAlloc(length); + if (!ptr) { + throw new FS.ErrnoError(48); + } + writeChunks(stream, HEAP8, ptr, length, position); + return { + ptr, + allocated: true, + }; + }; + node.stream_ops = stream_ops; + return node; + }, + }; + + var findLibraryFS = (libName, rpath) => { + // If we're preloading a dynamic library, the runtime is not ready to call + // __wasmfs_identify or __emscripten_find_dylib. So just quit out. + // This means that DT_NEEDED for the main module and transitive dependencies + // of it won't work with this code path. Similarly, it means that calling + // loadDynamicLibrary in a preRun hook can't use this code path. + if (!runtimeInitialized) { + return undefined; + } + if (PATH.isAbs(libName)) { + try { + FS.lookupPath(libName); + return libName; + } catch (e) { + return undefined; + } + } + var rpathResolved = (rpath?.paths || []).map((p) => + replaceORIGIN(rpath?.parentLibPath, p) + ); + return withStackSave(() => { + // In dylink.c we use: `char buf[2*NAME_MAX+2];` and NAME_MAX is 255. + // So we use the same size here. + var bufSize = 2 * 255 + 2; + var buf = stackAlloc(bufSize); + var rpathC = stringToUTF8OnStack(rpathResolved.join(':')); + var libNameC = stringToUTF8OnStack(libName); + var resLibNameC = __emscripten_find_dylib( + buf, + rpathC, + libNameC, + bufSize + ); + return resLibNameC ? UTF8ToString(resLibNameC) : undefined; + }); + }; + + var registerDynCallSymbols = (exports) => { + for (var [sym, exp] of Object.entries(exports)) { + if (sym.startsWith('dynCall_')) { + var sig = sym.substring(8); + if (!dynCalls.hasOwnProperty(sig)) { + dynCalls[sig] = exp; + } + } + } + }; + + /** + * @param {number=} handle + * @param {Object=} localScope + */ function loadDynamicLibrary( + libName, + flags = { + global: true, + nodelete: true, + }, + localScope, + handle + ) { + // when loadDynamicLibrary did not have flags, libraries were loaded + // globally & permanently + var dso = LDSO.loadedLibsByName[libName]; + if (dso) { + // the library is being loaded or has been loaded already. + if (!flags.global) { + if (localScope) { + Object.assign(localScope, dso.exports); + } + registerDynCallSymbols(dso.exports); + } else if (!dso.global) { + // The library was previously loaded only locally but not + // we have a request with global=true. + dso.global = true; + mergeLibSymbols(dso.exports, libName); + } + // same for "nodelete" + if (flags.nodelete && dso.refcount !== Infinity) { + dso.refcount = Infinity; + } + dso.refcount++; + if (handle) { + LDSO.loadedLibsByHandle[handle] = dso; + } + return flags.loadAsync ? Promise.resolve(true) : true; + } + // allocate new DSO + dso = newDSO(libName, handle, 'loading'); + dso.refcount = flags.nodelete ? Infinity : 1; + dso.global = flags.global; + // libName -> libData + function loadLibData() { + // for wasm, we can use fetch for async, but for fs mode we can only imitate it + if (handle) { + var data = HEAPU32[(handle + 28) >> 2]; + var dataSize = HEAPU32[(handle + 32) >> 2]; + if (data && dataSize) { + var libData = HEAP8.slice(data, data + dataSize); + return flags.loadAsync ? Promise.resolve(libData) : libData; + } + } + var f = findLibraryFS(libName, flags.rpath); + if (f) { + var libData = FS.readFile(f, { + encoding: 'binary', + }); + return flags.loadAsync ? Promise.resolve(libData) : libData; + } + var libFile = locateFile(libName); + if (flags.loadAsync) { + return asyncLoad(libFile); + } + // load the binary synchronously + if (!readBinary) { + throw new Error( + `${libFile}: file not found, and synchronous loading of external files is not available` + ); + } + return readBinary(libFile); + } + // libName -> exports + function getExports() { + // lookup preloaded cache first + var preloaded = preloadedWasm[libName]; + if (preloaded) { + return flags.loadAsync ? Promise.resolve(preloaded) : preloaded; + } + // module not preloaded - load lib data and create new module from it + if (flags.loadAsync) { + return loadLibData().then((libData) => + loadWebAssemblyModule( + libData, + flags, + libName, + localScope, + handle + ) + ); + } + return loadWebAssemblyModule( + loadLibData(), + flags, + libName, + localScope, + handle + ); + } + // module for lib is loaded - update the dso & global namespace + function moduleLoaded(exports) { + if (dso.global) { + mergeLibSymbols(exports, libName); + } else if (localScope) { + Object.assign(localScope, exports); + registerDynCallSymbols(exports); + } + dso.exports = exports; + } + if (flags.loadAsync) { + return getExports().then((exports) => { + moduleLoaded(exports); + return true; + }); + } + moduleLoaded(getExports()); + return true; + } + + var reportUndefinedSymbols = () => { + for (var [symName, entry] of Object.entries(GOT)) { + if (entry.value == -1) { + var value = resolveGlobalSymbol(symName, true).sym; + if (!value && !entry.required) { + // Ignore undefined symbols that are imported as weak. + entry.value = 0; + continue; + } + if (typeof value == 'function') { + /** @suppress {checkTypes} */ entry.value = addFunction( + value, + value.sig + ); + } else if (typeof value == 'number') { + entry.value = value; + } else { + throw new Error( + `bad export type for '${symName}': ${typeof value} (${value})` + ); + } + } + } + }; + + var loadDylibs = async () => { + if (!dynamicLibraries.length) { + reportUndefinedSymbols(); + return; + } + addRunDependency('loadDylibs'); + // Load binaries asynchronously + for (var lib of dynamicLibraries) { + await loadDynamicLibrary(lib, { + loadAsync: true, + global: true, + nodelete: true, + allowUndefined: true, + }); + } + // we got them all, wonderful + reportUndefinedSymbols(); + removeRunDependency('loadDylibs'); + }; + + var noExitRuntime = false; + + var ___assert_fail = (condition, filename, line, func) => + abort( + `Assertion failed: ${UTF8ToString(condition)}, at: ` + + [ + filename ? UTF8ToString(filename) : 'unknown filename', + line, + func ? UTF8ToString(func) : 'unknown function', + ] + ); + + ___assert_fail.sig = 'vppip'; + + var ___asyncify_data = new WebAssembly.Global( + { + value: 'i32', + mutable: true, + }, + 0 + ); + + var ___asyncify_state = new WebAssembly.Global( + { + value: 'i32', + mutable: true, + }, + 0 + ); + + var ___call_sighandler = (fp, sig) => ((a1) => dynCall_vi(fp, a1))(sig); + + ___call_sighandler.sig = 'vpi'; + + var SOCKFS = { + websocketArgs: {}, + callbacks: {}, + on(event, callback) { + SOCKFS.callbacks[event] = callback; + }, + emit(event, param) { + SOCKFS.callbacks[event]?.(param); + }, + mount(mount) { + // The incomming Module['websocket'] can be used for configuring + // configuring subprotocol/url, etc + SOCKFS.websocketArgs = Module['websocket'] || {}; + // Add the Event registration mechanism to the exported websocket configuration + // object so we can register network callbacks from native JavaScript too. + // For more documentation see system/include/emscripten/emscripten.h + (Module['websocket'] ??= {})['on'] = SOCKFS.on; + return FS.createNode(null, '/', 16895, 0); + }, + createSocket(family, type, protocol) { + // Emscripten only supports AF_INET + if (family != 2) { + throw new FS.ErrnoError(5); + } + type &= ~526336; + // Some applications may pass it; it makes no sense for a single process. + // Emscripten only supports SOCK_STREAM and SOCK_DGRAM + if (type != 1 && type != 2) { + throw new FS.ErrnoError(28); + } + var streaming = type == 1; + if (streaming && protocol && protocol != 6) { + throw new FS.ErrnoError(66); + } + // create our internal socket structure + var sock = { + family, + type, + protocol, + server: null, + error: null, + // Used in getsockopt for SOL_SOCKET/SO_ERROR test + peers: {}, + pending: [], + recv_queue: [], + sock_ops: SOCKFS.websocket_sock_ops, + }; + // create the filesystem node to store the socket structure + var name = SOCKFS.nextname(); + var node = FS.createNode(SOCKFS.root, name, 49152, 0); + node.sock = sock; + // and the wrapping stream that enables library functions such + // as read and write to indirectly interact with the socket + var stream = FS.createStream({ + path: name, + node, + flags: 2, + seekable: false, + stream_ops: SOCKFS.stream_ops, + }); + // map the new stream to the socket structure (sockets have a 1:1 + // relationship with a stream) + sock.stream = stream; + return sock; + }, + getSocket(fd) { + var stream = FS.getStream(fd); + if (!stream || !FS.isSocket(stream.node.mode)) { + return null; + } + return stream.node.sock; + }, + stream_ops: { + poll(stream) { + var sock = stream.node.sock; + return sock.sock_ops.poll(sock); + }, + ioctl(stream, request, varargs) { + var sock = stream.node.sock; + return sock.sock_ops.ioctl(sock, request, varargs); + }, + read(stream, buffer, offset, length, position) { + var sock = stream.node.sock; + var msg = sock.sock_ops.recvmsg(sock, length); + if (!msg) { + // socket is closed + return 0; + } + buffer.set(msg.buffer, offset); + return msg.buffer.length; + }, + write(stream, buffer, offset, length, position) { + var sock = stream.node.sock; + return sock.sock_ops.sendmsg(sock, buffer, offset, length); + }, + close(stream) { + var sock = stream.node.sock; + sock.sock_ops.close(sock); + }, + }, + nextname() { + if (!SOCKFS.nextname.current) { + SOCKFS.nextname.current = 0; + } + return `socket[${SOCKFS.nextname.current++}]`; + }, + websocket_sock_ops: { + createPeer(sock, addr, port) { + var ws; + if (typeof addr == 'object') { + ws = addr; + addr = null; + port = null; + } + if (ws) { + // for sockets that've already connected (e.g. we're the server) + // we can inspect the _socket property for the address + if (ws._socket) { + addr = ws._socket.remoteAddress; + port = ws._socket.remotePort; + } else { + var result = /ws[s]?:\/\/([^:]+):(\d+)/.exec(ws.url); + if (!result) { + throw new Error( + 'WebSocket URL must be in the format ws(s)://address:port' + ); + } + addr = result[1]; + port = parseInt(result[2], 10); + } + } else { + // create the actual websocket object and connect + try { + // The default value is 'ws://' the replace is needed because the compiler replaces '//' comments with '#' + // comments without checking context, so we'd end up with ws:#, the replace swaps the '#' for '//' again. + var url = 'ws://'.replace('#', '//'); + // Make the WebSocket subprotocol (Sec-WebSocket-Protocol) default to binary if no configuration is set. + var subProtocols = 'binary'; + // The default value is 'binary' + // The default WebSocket options + var opts = undefined; + // Fetch runtime WebSocket URL config. + if ('function' === typeof SOCKFS.websocketArgs['url']) { + url = SOCKFS.websocketArgs['url'](...arguments); + } else if ( + 'string' === typeof SOCKFS.websocketArgs['url'] + ) { + url = SOCKFS.websocketArgs['url']; + } + // Fetch runtime WebSocket subprotocol config. + if (SOCKFS.websocketArgs['subprotocol']) { + subProtocols = SOCKFS.websocketArgs['subprotocol']; + } else if ( + SOCKFS.websocketArgs['subprotocol'] === null + ) { + subProtocols = 'null'; + } + if (url === 'ws://' || url === 'wss://') { + // Is the supplied URL config just a prefix, if so complete it. + var parts = addr.split('/'); + url = + url + + parts[0] + + ':' + + port + + '/' + + parts.slice(1).join('/'); + } + if (subProtocols !== 'null') { + // The regex trims the string (removes spaces at the beginning and end, then splits the string by + // , into an Array. Whitespace removal is important for Websockify and ws. + subProtocols = subProtocols + .replace(/^ +| +$/g, '') + .split(/ *, */); + opts = subProtocols; + } + // If node we use the ws library. + var WebSocketConstructor; + if (ENVIRONMENT_IS_NODE) { + WebSocketConstructor = + /** @type{(typeof WebSocket)} */ ( + require('ws') + ); + } else { + WebSocketConstructor = WebSocket; + } + if (Module['websocket']['decorator']) { + WebSocketConstructor = + Module['websocket']['decorator']( + WebSocketConstructor + ); + } + ws = new WebSocketConstructor(url, opts); + ws.binaryType = 'arraybuffer'; + } catch (e) { + throw new FS.ErrnoError(23); + } + } + var peer = { + addr, + port, + socket: ws, + msg_send_queue: [], + }; + SOCKFS.websocket_sock_ops.addPeer(sock, peer); + SOCKFS.websocket_sock_ops.handlePeerEvents(sock, peer); + // if this is a bound dgram socket, send the port number first to allow + // us to override the ephemeral port reported to us by remotePort on the + // remote end. + if (sock.type === 2 && typeof sock.sport != 'undefined') { + peer.msg_send_queue.push( + new Uint8Array([ + 255, + 255, + 255, + 255, + 'p'.charCodeAt(0), + 'o'.charCodeAt(0), + 'r'.charCodeAt(0), + 't'.charCodeAt(0), + (sock.sport & 65280) >> 8, + sock.sport & 255, + ]) + ); + } + return peer; + }, + getPeer(sock, addr, port) { + return sock.peers[addr + ':' + port]; + }, + addPeer(sock, peer) { + sock.peers[peer.addr + ':' + peer.port] = peer; + }, + removePeer(sock, peer) { + delete sock.peers[peer.addr + ':' + peer.port]; + }, + handlePeerEvents(sock, peer) { + var first = true; + var handleOpen = function () { + sock.connecting = false; + SOCKFS.emit('open', sock.stream.fd); + try { + var queued = peer.msg_send_queue.shift(); + while (queued) { + peer.socket.send(queued); + queued = peer.msg_send_queue.shift(); + } + } catch (e) { + // not much we can do here in the way of proper error handling as we've already + // lied and said this data was sent. shut it down. + peer.socket.close(); + } + }; + function handleMessage(data) { + if (typeof data == 'string') { + var encoder = new TextEncoder(); + // should be utf-8 + data = encoder.encode(data); + } else { + if (data.byteLength == 0) { + // An empty ArrayBuffer will emit a pseudo disconnect event + // as recv/recvmsg will return zero which indicates that a socket + // has performed a shutdown although the connection has not been disconnected yet. + return; + } + data = new Uint8Array(data); + } + // if this is the port message, override the peer's port with it + var wasfirst = first; + first = false; + if ( + wasfirst && + data.length === 10 && + data[0] === 255 && + data[1] === 255 && + data[2] === 255 && + data[3] === 255 && + data[4] === 'p'.charCodeAt(0) && + data[5] === 'o'.charCodeAt(0) && + data[6] === 'r'.charCodeAt(0) && + data[7] === 't'.charCodeAt(0) + ) { + // update the peer's port and it's key in the peer map + var newport = (data[8] << 8) | data[9]; + SOCKFS.websocket_sock_ops.removePeer(sock, peer); + peer.port = newport; + SOCKFS.websocket_sock_ops.addPeer(sock, peer); + return; + } + sock.recv_queue.push({ + addr: peer.addr, + port: peer.port, + data, + }); + SOCKFS.emit('message', sock.stream.fd); + } + if (ENVIRONMENT_IS_NODE) { + peer.socket.on('open', handleOpen); + peer.socket.on('message', function (data, isBinary) { + if (!isBinary) { + return; + } + handleMessage(new Uint8Array(data).buffer); + }); + peer.socket.on('close', function () { + SOCKFS.emit('close', sock.stream.fd); + }); + peer.socket.on('error', function (error) { + // Although the ws library may pass errors that may be more descriptive than + // ECONNREFUSED they are not necessarily the expected error code e.g. + // ENOTFOUND on getaddrinfo seems to be node.js specific, so using ECONNREFUSED + // is still probably the most useful thing to do. + sock.error = 14; + // Used in getsockopt for SOL_SOCKET/SO_ERROR test. + SOCKFS.emit('error', [ + sock.stream.fd, + sock.error, + 'ECONNREFUSED: Connection refused', + ]); + }); + } else { + peer.socket.onopen = handleOpen; + peer.socket.onclose = function () { + SOCKFS.emit('close', sock.stream.fd); + }; + peer.socket.onmessage = function peer_socket_onmessage( + event + ) { + handleMessage(event.data); + }; + peer.socket.onerror = function (error) { + // The WebSocket spec only allows a 'simple event' to be thrown on error, + // so we only really know as much as ECONNREFUSED. + sock.error = 14; + // Used in getsockopt for SOL_SOCKET/SO_ERROR test. + SOCKFS.emit('error', [ + sock.stream.fd, + sock.error, + 'ECONNREFUSED: Connection refused', + ]); + }; + } + }, + poll(sock) { + if (sock.type === 1 && sock.server) { + // listen sockets should only say they're available for reading + // if there are pending clients. + return sock.pending.length ? 64 | 1 : 0; + } + var mask = 0; + var dest = + sock.type === 1 // we only care about the socket state for connection-based sockets + ? SOCKFS.websocket_sock_ops.getPeer( + sock, + sock.daddr, + sock.dport + ) + : null; + if ( + sock.recv_queue.length || + !dest || // connection-less sockets are always ready to read + (dest && dest.socket.readyState === dest.socket.CLOSING) || + (dest && dest.socket.readyState === dest.socket.CLOSED) + ) { + // let recv return 0 once closed + mask |= 64 | 1; + } + if ( + !dest || // connection-less sockets are always ready to write + (dest && dest.socket.readyState === dest.socket.OPEN) + ) { + mask |= 4; + } + if ( + (dest && dest.socket.readyState === dest.socket.CLOSING) || + (dest && dest.socket.readyState === dest.socket.CLOSED) + ) { + // When an non-blocking connect fails mark the socket as writable. + // Its up to the calling code to then use getsockopt with SO_ERROR to + // retrieve the error. + // See https://man7.org/linux/man-pages/man2/connect.2.html + if (sock.connecting) { + mask |= 4; + } else { + mask |= 16; + } + } + return mask; + }, + ioctl(sock, request, arg) { + switch (request) { + case 21531: + var bytes = 0; + if (sock.recv_queue.length) { + bytes = sock.recv_queue[0].data.length; + } + HEAP32[arg >> 2] = bytes; + return 0; + + case 21537: + var on = HEAP32[arg >> 2]; + if (on) { + sock.stream.flags |= 2048; + } else { + sock.stream.flags &= ~2048; + } + return 0; + + default: + return 28; + } + }, + close(sock) { + // if we've spawned a listen server, close it + if (sock.server) { + try { + sock.server.close(); + } catch (e) {} + sock.server = null; + } + // close any peer connections + for (var peer of Object.values(sock.peers)) { + try { + peer.socket.close(); + } catch (e) {} + SOCKFS.websocket_sock_ops.removePeer(sock, peer); + } + return 0; + }, + bind(sock, addr, port) { + if ( + typeof sock.saddr != 'undefined' || + typeof sock.sport != 'undefined' + ) { + throw new FS.ErrnoError(28); + } + sock.saddr = addr; + sock.sport = port; + // in order to emulate dgram sockets, we need to launch a listen server when + // binding on a connection-less socket + // note: this is only required on the server side + if (sock.type === 2) { + // close the existing server if it exists + if (sock.server) { + sock.server.close(); + sock.server = null; + } + // swallow error operation not supported error that occurs when binding in the + // browser where this isn't supported + try { + sock.sock_ops.listen(sock, 0); + } catch (e) { + if (!(e.name === 'ErrnoError')) throw e; + if (e.errno !== 138) throw e; + } + } + }, + connect(sock, addr, port) { + if (sock.server) { + throw new FS.ErrnoError(138); + } + // TODO autobind + // if (!sock.addr && sock.type == 2) { + // } + // early out if we're already connected / in the middle of connecting + if ( + typeof sock.daddr != 'undefined' && + typeof sock.dport != 'undefined' + ) { + var dest = SOCKFS.websocket_sock_ops.getPeer( + sock, + sock.daddr, + sock.dport + ); + if (dest) { + if (dest.socket.readyState === dest.socket.CONNECTING) { + throw new FS.ErrnoError(7); + } else { + throw new FS.ErrnoError(30); + } + } + } + // add the socket to our peer list and set our + // destination address / port to match + var peer = SOCKFS.websocket_sock_ops.createPeer( + sock, + addr, + port + ); + sock.daddr = peer.addr; + sock.dport = peer.port; + // because we cannot synchronously block to wait for the WebSocket + // connection to complete, we return here pretending that the connection + // was a success. + sock.connecting = true; + }, + listen(sock, backlog) { + if (!ENVIRONMENT_IS_NODE) { + throw new FS.ErrnoError(138); + } + if (sock.server) { + throw new FS.ErrnoError(28); + } + var WebSocketServer = require('ws').Server; + var host = sock.saddr; + if (Module['websocket']['serverDecorator']) { + WebSocketServer = + Module['websocket']['serverDecorator'](WebSocketServer); + } + sock.server = new WebSocketServer({ + host, + port: sock.sport, + }); + SOCKFS.emit('listen', sock.stream.fd); + // Send Event with listen fd. + sock.server.on('connection', function (ws) { + if (sock.type === 1) { + var newsock = SOCKFS.createSocket( + sock.family, + sock.type, + sock.protocol + ); + // create a peer on the new socket + var peer = SOCKFS.websocket_sock_ops.createPeer( + newsock, + ws + ); + newsock.daddr = peer.addr; + newsock.dport = peer.port; + // push to queue for accept to pick up + sock.pending.push(newsock); + SOCKFS.emit('connection', newsock.stream.fd); + } else { + // create a peer on the listen socket so calling sendto + // with the listen socket and an address will resolve + // to the correct client + SOCKFS.websocket_sock_ops.createPeer(sock, ws); + SOCKFS.emit('connection', sock.stream.fd); + } + }); + sock.server.on('close', function () { + SOCKFS.emit('close', sock.stream.fd); + sock.server = null; + }); + sock.server.on('error', function (error) { + // Although the ws library may pass errors that may be more descriptive than + // ECONNREFUSED they are not necessarily the expected error code e.g. + // ENOTFOUND on getaddrinfo seems to be node.js specific, so using EHOSTUNREACH + // is still probably the most useful thing to do. This error shouldn't + // occur in a well written app as errors should get trapped in the compiled + // app's own getaddrinfo call. + sock.error = 23; + // Used in getsockopt for SOL_SOCKET/SO_ERROR test. + SOCKFS.emit('error', [ + sock.stream.fd, + sock.error, + 'EHOSTUNREACH: Host is unreachable', + ]); + }); + }, + accept(listensock) { + if (!listensock.server || !listensock.pending.length) { + throw new FS.ErrnoError(28); + } + var newsock = listensock.pending.shift(); + newsock.stream.flags = listensock.stream.flags; + return newsock; + }, + getname(sock, peer) { + var addr, port; + if (peer) { + if (sock.daddr === undefined || sock.dport === undefined) { + throw new FS.ErrnoError(53); + } + addr = sock.daddr; + port = sock.dport; + } else { + // TODO saddr and sport will be set for bind()'d UDP sockets, but what + // should we be returning for TCP sockets that've been connect()'d? + addr = sock.saddr || 0; + port = sock.sport || 0; + } + return { + addr, + port, + }; + }, + sendmsg(sock, buffer, offset, length, addr, port) { + if (sock.type === 2) { + // connection-less sockets will honor the message address, + // and otherwise fall back to the bound destination address + if (addr === undefined || port === undefined) { + addr = sock.daddr; + port = sock.dport; + } + // if there was no address to fall back to, error out + if (addr === undefined || port === undefined) { + throw new FS.ErrnoError(17); + } + } else { + // connection-based sockets will only use the bound + addr = sock.daddr; + port = sock.dport; + } + // find the peer for the destination address + var dest = SOCKFS.websocket_sock_ops.getPeer(sock, addr, port); + // early out if not connected with a connection-based socket + if (sock.type === 1) { + if ( + !dest || + dest.socket.readyState === dest.socket.CLOSING || + dest.socket.readyState === dest.socket.CLOSED + ) { + throw new FS.ErrnoError(53); + } + } + // create a copy of the incoming data to send, as the WebSocket API + // doesn't work entirely with an ArrayBufferView, it'll just send + // the entire underlying buffer + if (ArrayBuffer.isView(buffer)) { + offset += buffer.byteOffset; + buffer = buffer.buffer; + } + var data = buffer.slice(offset, offset + length); + // if we don't have a cached connectionless UDP datagram connection, or + // the TCP socket is still connecting, queue the message to be sent upon + // connect, and lie, saying the data was sent now. + if (!dest || dest.socket.readyState !== dest.socket.OPEN) { + // if we're not connected, open a new connection + if (sock.type === 2) { + if ( + !dest || + dest.socket.readyState === dest.socket.CLOSING || + dest.socket.readyState === dest.socket.CLOSED + ) { + dest = SOCKFS.websocket_sock_ops.createPeer( + sock, + addr, + port + ); + } + } + dest.msg_send_queue.push(data); + return length; + } + try { + // send the actual data + dest.socket.send(data); + return length; + } catch (e) { + throw new FS.ErrnoError(28); + } + }, + recvmsg(sock, length, flags) { + // http://pubs.opengroup.org/onlinepubs/7908799/xns/recvmsg.html + if (sock.type === 1 && sock.server) { + // tcp servers should not be recv()'ing on the listen socket + throw new FS.ErrnoError(53); + } + var queued = sock.recv_queue.shift(); + if (!queued) { + if (sock.type === 1) { + var dest = SOCKFS.websocket_sock_ops.getPeer( + sock, + sock.daddr, + sock.dport + ); + if (!dest) { + // if we have a destination address but are not connected, error out + throw new FS.ErrnoError(53); + } + if ( + dest.socket.readyState === dest.socket.CLOSING || + dest.socket.readyState === dest.socket.CLOSED + ) { + // return null if the socket has closed + return null; + } + // else, our socket is in a valid state but truly has nothing available + throw new FS.ErrnoError(6); + } + throw new FS.ErrnoError(6); + } + // queued.data will be an ArrayBuffer if it's unadulterated, but if it's + // requeued TCP data it'll be an ArrayBufferView + var queuedLength = queued.data.byteLength || queued.data.length; + var queuedOffset = queued.data.byteOffset || 0; + var queuedBuffer = queued.data.buffer || queued.data; + var bytesRead = Math.min(length, queuedLength); + var res = { + buffer: new Uint8Array( + queuedBuffer, + queuedOffset, + bytesRead + ), + addr: queued.addr, + port: queued.port, + }; + // push back any unread data for TCP connections + if (flags & 2) { + bytesRead = 0; + } + if (sock.type === 1 && bytesRead < queuedLength) { + var bytesRemaining = queuedLength - bytesRead; + queued.data = new Uint8Array( + queuedBuffer, + queuedOffset + bytesRead, + bytesRemaining + ); + sock.recv_queue.unshift(queued); + } + return res; + }, + }, + }; + + var getSocketFromFD = (fd) => { + var socket = SOCKFS.getSocket(fd); + if (!socket) throw new FS.ErrnoError(8); + return socket; + }; + + var inetPton4 = (str) => { + var b = str.split('.'); + for (var i = 0; i < 4; i++) { + var tmp = Number(b[i]); + if (isNaN(tmp)) return null; + b[i] = tmp; + } + return (b[0] | (b[1] << 8) | (b[2] << 16) | (b[3] << 24)) >>> 0; + }; + + var inetPton6 = (str) => { + var words; + var w, offset, z; + /* http://home.deds.nl/~aeron/regex/ */ var valid6regx = + /^((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\3)::|:\b|$))|(?!\2\3)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})$/i; + var parts = []; + if (!valid6regx.test(str)) { + return null; + } + if (str === '::') { + return [0, 0, 0, 0, 0, 0, 0, 0]; + } + // Z placeholder to keep track of zeros when splitting the string on ":" + if (str.startsWith('::')) { + str = str.replace('::', 'Z:'); + } else { + str = str.replace('::', ':Z:'); + } + if (str.indexOf('.') > 0) { + // parse IPv4 embedded stress + str = str.replace(new RegExp('[.]', 'g'), ':'); + words = str.split(':'); + words[words.length - 4] = + Number(words[words.length - 4]) + + Number(words[words.length - 3]) * 256; + words[words.length - 3] = + Number(words[words.length - 2]) + + Number(words[words.length - 1]) * 256; + words = words.slice(0, words.length - 2); + } else { + words = str.split(':'); + } + offset = 0; + z = 0; + for (w = 0; w < words.length; w++) { + if (typeof words[w] == 'string') { + if (words[w] === 'Z') { + // compressed zeros - write appropriate number of zero words + for (z = 0; z < 8 - words.length + 1; z++) { + parts[w + z] = 0; + } + offset = z - 1; + } else { + // parse hex to field to 16-bit value and write it in network byte-order + parts[w + offset] = _htons(parseInt(words[w], 16)); + } + } else { + // parsed IPv4 words + parts[w + offset] = words[w]; + } + } + return [ + (parts[1] << 16) | parts[0], + (parts[3] << 16) | parts[2], + (parts[5] << 16) | parts[4], + (parts[7] << 16) | parts[6], + ]; + }; + + /** @param {number=} addrlen */ var writeSockaddr = ( + sa, + family, + addr, + port, + addrlen + ) => { + switch (family) { + case 2: + addr = inetPton4(addr); + zeroMemory(sa, 16); + if (addrlen) { + HEAP32[addrlen >> 2] = 16; + } + HEAP16[sa >> 1] = family; + HEAP32[(sa + 4) >> 2] = addr; + HEAP16[(sa + 2) >> 1] = _htons(port); + break; + + case 10: + addr = inetPton6(addr); + zeroMemory(sa, 28); + if (addrlen) { + HEAP32[addrlen >> 2] = 28; + } + HEAP32[sa >> 2] = family; + HEAP32[(sa + 8) >> 2] = addr[0]; + HEAP32[(sa + 12) >> 2] = addr[1]; + HEAP32[(sa + 16) >> 2] = addr[2]; + HEAP32[(sa + 20) >> 2] = addr[3]; + HEAP16[(sa + 2) >> 1] = _htons(port); + break; + + default: + return 5; + } + return 0; + }; + + var DNS = { + address_map: { + id: 1, + addrs: {}, + names: {}, + }, + lookup_name(name) { + // If the name is already a valid ipv4 / ipv6 address, don't generate a fake one. + var res = inetPton4(name); + if (res !== null) { + return name; + } + res = inetPton6(name); + if (res !== null) { + return name; + } + // See if this name is already mapped. + var addr; + if (DNS.address_map.addrs[name]) { + addr = DNS.address_map.addrs[name]; + } else { + var id = DNS.address_map.id++; + addr = '172.29.' + (id & 255) + '.' + (id & 65280); + DNS.address_map.names[addr] = name; + DNS.address_map.addrs[name] = addr; + } + return addr; + }, + lookup_addr(addr) { + if (DNS.address_map.names[addr]) { + return DNS.address_map.names[addr]; + } + return null; + }, + }; + + function ___syscall_accept4(fd, addr, addrlen, flags, d1, d2) { + try { + var sock = getSocketFromFD(fd); + var newsock = sock.sock_ops.accept(sock); + if (addr) { + var errno = writeSockaddr( + addr, + newsock.family, + DNS.lookup_name(newsock.daddr), + newsock.dport, + addrlen + ); + } + return newsock.stream.fd; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + + ___syscall_accept4.sig = 'iippiii'; + + var inetNtop4 = (addr) => + (addr & 255) + + '.' + + ((addr >> 8) & 255) + + '.' + + ((addr >> 16) & 255) + + '.' + + ((addr >> 24) & 255); + + var inetNtop6 = (ints) => { + // ref: http://www.ietf.org/rfc/rfc2373.txt - section 2.5.4 + // Format for IPv4 compatible and mapped 128-bit IPv6 Addresses + // 128-bits are split into eight 16-bit words + // stored in network byte order (big-endian) + // | 80 bits | 16 | 32 bits | + // +-----------------------------------------------------------------+ + // | 10 bytes | 2 | 4 bytes | + // +--------------------------------------+--------------------------+ + // + 5 words | 1 | 2 words | + // +--------------------------------------+--------------------------+ + // |0000..............................0000|0000| IPv4 ADDRESS | (compatible) + // +--------------------------------------+----+---------------------+ + // |0000..............................0000|FFFF| IPv4 ADDRESS | (mapped) + // +--------------------------------------+----+---------------------+ + var str = ''; + var word = 0; + var longest = 0; + var lastzero = 0; + var zstart = 0; + var len = 0; + var i = 0; + var parts = [ + ints[0] & 65535, + ints[0] >> 16, + ints[1] & 65535, + ints[1] >> 16, + ints[2] & 65535, + ints[2] >> 16, + ints[3] & 65535, + ints[3] >> 16, + ]; + // Handle IPv4-compatible, IPv4-mapped, loopback and any/unspecified addresses + var hasipv4 = true; + var v4part = ''; + // check if the 10 high-order bytes are all zeros (first 5 words) + for (i = 0; i < 5; i++) { + if (parts[i] !== 0) { + hasipv4 = false; + break; + } + } + if (hasipv4) { + // low-order 32-bits store an IPv4 address (bytes 13 to 16) (last 2 words) + v4part = inetNtop4(parts[6] | (parts[7] << 16)); + // IPv4-mapped IPv6 address if 16-bit value (bytes 11 and 12) == 0xFFFF (6th word) + if (parts[5] === -1) { + str = '::ffff:'; + str += v4part; + return str; + } + // IPv4-compatible IPv6 address if 16-bit value (bytes 11 and 12) == 0x0000 (6th word) + if (parts[5] === 0) { + str = '::'; + //special case IPv6 addresses + if (v4part === '0.0.0.0') v4part = ''; + // any/unspecified address + if (v4part === '0.0.0.1') v4part = '1'; + // loopback address + str += v4part; + return str; + } + } + // Handle all other IPv6 addresses + // first run to find the longest contiguous zero words + for (word = 0; word < 8; word++) { + if (parts[word] === 0) { + if (word - lastzero > 1) { + len = 0; + } + lastzero = word; + len++; + } + if (len > longest) { + longest = len; + zstart = word - longest + 1; + } + } + for (word = 0; word < 8; word++) { + if (longest > 1) { + // compress contiguous zeros - to produce "::" + if ( + parts[word] === 0 && + word >= zstart && + word < zstart + longest + ) { + if (word === zstart) { + str += ':'; + if (zstart === 0) str += ':'; + } + continue; + } + } + // converts 16-bit words from big-endian to little-endian before converting to hex string + str += Number(_ntohs(parts[word] & 65535)).toString(16); + str += word < 7 ? ':' : ''; + } + return str; + }; + + var readSockaddr = (sa, salen) => { + // family / port offsets are common to both sockaddr_in and sockaddr_in6 + var family = HEAP16[sa >> 1]; + var port = _ntohs(HEAPU16[(sa + 2) >> 1]); + var addr; + switch (family) { + case 2: + if (salen !== 16) { + return { + errno: 28, + }; + } + addr = HEAP32[(sa + 4) >> 2]; + addr = inetNtop4(addr); + break; + + case 10: + if (salen !== 28) { + return { + errno: 28, + }; + } + addr = [ + HEAP32[(sa + 8) >> 2], + HEAP32[(sa + 12) >> 2], + HEAP32[(sa + 16) >> 2], + HEAP32[(sa + 20) >> 2], + ]; + addr = inetNtop6(addr); + break; + + default: + return { + errno: 5, + }; + } + return { + family, + addr, + port, + }; + }; + + var getSocketAddress = (addrp, addrlen) => { + var info = readSockaddr(addrp, addrlen); + if (info.errno) throw new FS.ErrnoError(info.errno); + info.addr = DNS.lookup_addr(info.addr) || info.addr; + return info; + }; + + function ___syscall_bind(fd, addr, addrlen, d1, d2, d3) { + try { + var sock = getSocketFromFD(fd); + var info = getSocketAddress(addr, addrlen); + sock.sock_ops.bind(sock, info.addr, info.port); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + + ___syscall_bind.sig = 'iippiii'; + + var SYSCALLS = { + DEFAULT_POLLMASK: 5, + calculateAt(dirfd, path, allowEmpty) { + if (PATH.isAbs(path)) { + return path; + } + // relative path + var dir; + if (dirfd === -100) { + dir = FS.cwd(); + } else { + var dirstream = SYSCALLS.getStreamFromFD(dirfd); + dir = dirstream.path; + } + if (path.length == 0) { + if (!allowEmpty) { + throw new FS.ErrnoError(44); + } + return dir; + } + return dir + '/' + path; + }, + writeStat(buf, stat) { + HEAPU32[buf >> 2] = stat.dev; + HEAPU32[(buf + 4) >> 2] = stat.mode; + HEAPU32[(buf + 8) >> 2] = stat.nlink; + HEAPU32[(buf + 12) >> 2] = stat.uid; + HEAPU32[(buf + 16) >> 2] = stat.gid; + HEAPU32[(buf + 20) >> 2] = stat.rdev; + HEAP64[(buf + 24) >> 3] = BigInt(stat.size); + HEAP32[(buf + 32) >> 2] = 4096; + HEAP32[(buf + 36) >> 2] = stat.blocks; + var atime = stat.atime.getTime(); + var mtime = stat.mtime.getTime(); + var ctime = stat.ctime.getTime(); + HEAP64[(buf + 40) >> 3] = BigInt(Math.floor(atime / 1e3)); + HEAPU32[(buf + 48) >> 2] = (atime % 1e3) * 1e3 * 1e3; + HEAP64[(buf + 56) >> 3] = BigInt(Math.floor(mtime / 1e3)); + HEAPU32[(buf + 64) >> 2] = (mtime % 1e3) * 1e3 * 1e3; + HEAP64[(buf + 72) >> 3] = BigInt(Math.floor(ctime / 1e3)); + HEAPU32[(buf + 80) >> 2] = (ctime % 1e3) * 1e3 * 1e3; + HEAP64[(buf + 88) >> 3] = BigInt(stat.ino); + return 0; + }, + writeStatFs(buf, stats) { + HEAPU32[(buf + 4) >> 2] = stats.bsize; + HEAPU32[(buf + 60) >> 2] = stats.bsize; + HEAP64[(buf + 8) >> 3] = BigInt(stats.blocks); + HEAP64[(buf + 16) >> 3] = BigInt(stats.bfree); + HEAP64[(buf + 24) >> 3] = BigInt(stats.bavail); + HEAP64[(buf + 32) >> 3] = BigInt(stats.files); + HEAP64[(buf + 40) >> 3] = BigInt(stats.ffree); + HEAPU32[(buf + 48) >> 2] = stats.fsid; + HEAPU32[(buf + 64) >> 2] = stats.flags; + // ST_NOSUID + HEAPU32[(buf + 56) >> 2] = stats.namelen; + }, + doMsync(addr, stream, len, flags, offset) { + if (!FS.isFile(stream.node.mode)) { + throw new FS.ErrnoError(43); + } + if (flags & 2) { + // MAP_PRIVATE calls need not to be synced back to underlying fs + return 0; + } + var buffer = HEAPU8.slice(addr, addr + len); + FS.msync(stream, buffer, offset, len, flags); + }, + getStreamFromFD(fd) { + var stream = FS.getStreamChecked(fd); + return stream; + }, + varargs: undefined, + getStr(ptr) { + var ret = UTF8ToString(ptr); + return ret; + }, + }; + + function ___syscall_chdir(path) { + try { + path = SYSCALLS.getStr(path); + FS.chdir(path); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + + ___syscall_chdir.sig = 'ip'; + + function ___syscall_chmod(path, mode) { + try { + path = SYSCALLS.getStr(path); + FS.chmod(path, mode); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + + ___syscall_chmod.sig = 'ipi'; + + var allocateUTF8OnStack = (...args) => stringToUTF8OnStack(...args); + + var onInits = []; + + var addOnInit = (cb) => onInits.push(cb); + + function _js_getpid() { + return PHPLoader.processId ?? 42; + } + + function _js_wasm_trace(format, ...args) { + if (PHPLoader.trace instanceof Function) { + PHPLoader.trace(_js_getpid(), format, ...args); + } + } + + var PHPWASM = { + O_APPEND: 1024, + O_NONBLOCK: 2048, + POLLHUP: 16, + SETFL_MASK: 3072, + init: function () { + // TODO: Move this to a library function that is made an onInit callback by the `__postset` suffix. + if (PHPLoader.bindUserSpace) { + /** + * We need to add an onInit callback to bind the user-space API + * because some dependencies like wasmImports and wasmExports + * are not yet assigned. + */ addOnInit(() => { + if (typeof PHPLoader.processId !== 'number') { + throw new Error( + 'PHPLoader.processId must be set before init' + ); + } + Module['userSpace'] = PHPLoader.bindUserSpace({ + pid: PHPLoader.processId, + constants: { + F_GETFL: Number('3'), + O_ACCMODE: Number('2097155'), + O_RDONLY: Number('0'), + O_WRONLY: Number('1'), + O_APPEND: Number('1024'), + O_NONBLOCK: Number('2048'), + F_SETFL: Number('4'), + F_GETLK: Number('12'), + F_SETLK: Number('13'), + F_SETLKW: Number('14'), + SEEK_SET: Number('0'), + SEEK_CUR: Number('1'), + SEEK_END: Number('2'), + F_GETFL: Number('3'), + O_ACCMODE: Number('2097155'), + O_RDONLY: Number('0'), + O_WRONLY: Number('1'), + O_APPEND: Number('1024'), + O_NONBLOCK: Number('2048'), + F_SETFL: Number('4'), + F_GETLK: Number('12'), + F_SETLK: Number('13'), + F_SETLKW: Number('14'), + SEEK_SET: Number('0'), + SEEK_CUR: Number('1'), + SEEK_END: Number('2'), + // From: + // https://github.com/emscripten-core/emscripten/blob/66d2137b0381ac35f7e2346b2d6a90abd0f1211a/system/lib/libc/musl/include/fcntl.h#L58-L60 + F_RDLCK: 0, + F_WRLCK: 1, + F_UNLCK: 2, + // From: + // https://github.com/emscripten-core/emscripten/blob/81bbaa42a7827d88a71bd89701245052c622428c/system/lib/libc/musl/include/sys/file.h#L7-L10 + LOCK_SH: 1, + LOCK_EX: 2, + LOCK_NB: 4, + // Non-blocking lock + LOCK_UN: 8, + }, + errnoCodes: ERRNO_CODES, + // Use get/set closures instead of exposing + // typed arrays directly. After memory.grow(), + // Emscripten's updateMemoryViews() reassigns + // the module-scoped HEAP* variables. Closures + // always reference the current value, so + // accesses are never stale. The get/set + // interface also prevents callers from + // capturing a typed array reference that + // could become stale. + memory: { + HEAP8: { + get(offset) { + return HEAP8[offset]; + }, + set(offset, value) { + HEAP8[offset] = value; + }, + }, + HEAPU8: { + get(offset) { + return HEAPU8[offset]; + }, + set(offset, value) { + HEAPU8[offset] = value; + }, + }, + HEAP16: { + get(offset) { + return HEAP16[offset]; + }, + set(offset, value) { + HEAP16[offset] = value; + }, + }, + HEAPU16: { + get(offset) { + return HEAPU16[offset]; + }, + set(offset, value) { + HEAPU16[offset] = value; + }, + }, + HEAP32: { + get(offset) { + return HEAP32[offset]; + }, + set(offset, value) { + HEAP32[offset] = value; + }, + }, + HEAPU32: { + get(offset) { + return HEAPU32[offset]; + }, + set(offset, value) { + HEAPU32[offset] = value; + }, + }, + HEAPF32: { + get(offset) { + return HEAPF32[offset]; + }, + set(offset, value) { + HEAPF32[offset] = value; + }, + }, + HEAP64: { + get(offset) { + return HEAP64[offset]; + }, + set(offset, value) { + HEAP64[offset] = value; + }, + }, + HEAPU64: { + get(offset) { + return HEAPU64[offset]; + }, + set(offset, value) { + HEAPU64[offset] = value; + }, + }, + HEAPF64: { + get(offset) { + return HEAPF64[offset]; + }, + set(offset, value) { + HEAPF64[offset] = value; + }, + }, + }, + wasmImports: Object.assign( + {}, + wasmImports, + typeof _builtin_fd_close === 'function' + ? { + builtin_fd_close: _builtin_fd_close, + } + : {}, + typeof _builtin_fcntl64 === 'function' + ? { + builtin_fcntl64: _builtin_fcntl64, + } + : {} + ), + wasmExports, + syscalls: SYSCALLS, + FS, + PROXYFS, + NODEFS, + }); + }); + } + Module['ENV'] = Module['ENV'] || {}; + // Ensure a platform-level bin directory for a fallback `php` binary. + Module['ENV']['PATH'] = [ + Module['ENV']['PATH'], + '/internal/shared/bin', + ] + .filter(Boolean) + .join(':'); + // The /request directory is required by the C module. It's where the + // stdout, stderr, and headers information are written for the JavaScript + // code to read later on. This is per-request state that is isolated to a + // single PHP process. + FS.mkdir('/request'); + // The /internal directory is shared amongst all PHP processes + // and contains the php.ini, constants definitions, etc. + FS.mkdir('/internal'); + if (PHPLoader.nativeInternalDirPath) { + FS.mount( + FS.filesystems.NODEFS, + { + root: PHPLoader.nativeInternalDirPath, + }, + '/internal' + ); + } + // The files from the shared directory are shared between all the + // PHP processes managed by PHPProcessManager. + FS.mkdirTree('/internal/shared'); + // The files from the preload directory are preloaded using the + // auto_prepend_file php.ini directive. + FS.mkdirTree('/internal/shared/preload'); + // Platform-level bin directory for a fallback `php` binary. Without it, + // PHP may not populate the PHP_BINARY constant. + FS.mkdirTree('/internal/shared/bin'); + const originalOnRuntimeInitialized = Module['onRuntimeInitialized']; + Module['onRuntimeInitialized'] = () => { + const { node: phpBinaryNode } = FS.lookupPath( + '/internal/shared/bin/php', + { + noent_okay: true, + } + ); + if (!phpBinaryNode) { + // Dummy PHP binary for PHP to populate the PHP_BINARY constant. + FS.writeFile( + '/internal/shared/bin/php', + new TextEncoder().encode('#!/bin/sh\nphp "$@"') + ); + // It must be executable to be used by PHP. + FS.chmod('/internal/shared/bin/php', 493); + } + originalOnRuntimeInitialized(); + }; + // Create stdout and stderr devices. We can't just use Emscripten's + // default stdout and stderr devices because they stop processing data + // on the first null byte. However, when dealing with binary data, + // null bytes are valid and common. + FS.registerDevice(FS.makedev(64, 0), { + open: () => {}, + close: () => {}, + read: () => 0, + write: (stream, buffer, offset, length, pos) => { + const chunk = buffer.subarray(offset, offset + length); + PHPWASM.onStdout(chunk); + return length; + }, + }); + FS.mkdev('/request/stdout', FS.makedev(64, 0)); + FS.registerDevice(FS.makedev(63, 0), { + open: () => {}, + close: () => {}, + read: () => 0, + write: (stream, buffer, offset, length, pos) => { + const chunk = buffer.subarray(offset, offset + length); + PHPWASM.onStderr(chunk); + return length; + }, + }); + FS.mkdev('/request/stderr', FS.makedev(63, 0)); + FS.registerDevice(FS.makedev(62, 0), { + open: () => {}, + close: () => {}, + read: () => 0, + write: (stream, buffer, offset, length, pos) => { + const chunk = buffer.subarray(offset, offset + length); + PHPWASM.onHeaders(chunk); + return length; + }, + }); + FS.mkdev('/request/headers', FS.makedev(62, 0)); + // Handle events. + PHPWASM.EventEmitter = ENVIRONMENT_IS_NODE + ? require('events').EventEmitter + : class EventEmitter { + constructor() { + this.listeners = {}; + } + emit(eventName, data) { + if (this.listeners[eventName]) { + this.listeners[eventName].forEach( + (callback) => { + callback(data); + } + ); + } + } + once(eventName, callback) { + const self = this; + function removedCallback() { + callback(...arguments); + self.removeListener(eventName, removedCallback); + } + this.on(eventName, removedCallback); + } + removeAllListeners(eventName) { + if (eventName) { + delete this.listeners[eventName]; + } else { + this.listeners = {}; + } + } + removeListener(eventName, callback) { + if (this.listeners[eventName]) { + const idx = + this.listeners[eventName].indexOf(callback); + if (idx !== -1) { + this.listeners[eventName].splice(idx, 1); + } + } + } + }; + PHPWASM.processTable = {}; + PHPWASM.input_devices = {}; + const originalWrite = TTY.stream_ops.write; + TTY.stream_ops.write = function (stream, ...rest) { + const retval = originalWrite(stream, ...rest); + // Implicit flush since PHP's fflush() doesn't seem to trigger the fsync event + // @TODO: Fix this at the wasm level + stream.tty.ops.fsync(stream.tty); + return retval; + }; + const originalPutChar = TTY.stream_ops.put_char; + TTY.stream_ops.put_char = function (tty, val) { + /** + * Buffer newlines that Emscripten normally ignores. + * + * Emscripten doesn't do it by default because its default + * print function is console.log that implicitly adds a newline. We are overwriting + * it with an environment-specific function that outputs exaclty what it was given, + * e.g. in Node.js it's process.stdout.write(). Therefore, we need to mak sure + * all the newlines make it to the output buffer. + */ if (val === 10) tty.output.push(val); + return originalPutChar(tty, val); + }; + }, + onHeaders: function (chunk) { + if (Module['onHeaders']) { + Module['onHeaders'](chunk); + return; + } + console.log('headers', { + chunk, + }); + }, + onStdout: function (chunk) { + if (Module['onStdout']) { + Module['onStdout'](chunk); + return; + } + if (ENVIRONMENT_IS_NODE) { + process.stdout.write(chunk); + } else { + console.log('stdout', { + chunk, + }); + } + }, + onStderr: function (chunk) { + if (Module['onStderr']) { + Module['onStderr'](chunk); + return; + } + if (ENVIRONMENT_IS_NODE) { + process.stderr.write(chunk); + } else { + console.warn('stderr', { + chunk, + }); + } + }, + getAllWebSockets: function (sock) { + const webSockets = new Set(); + if (sock.server) { + sock.server.clients.forEach((ws) => { + webSockets.add(ws); + }); + } + for (const peer of PHPWASM.getAllPeers(sock)) { + webSockets.add(peer.socket); + } + return Array.from(webSockets); + }, + getAllPeers: function (sock) { + const peers = new Set(); + if (sock.server) { + sock.pending + .filter((pending) => pending.peers) + .forEach((pending) => { + for (const peer of Object.values(pending.peers)) { + peers.add(peer); + } + }); + } + if (sock.peers) { + for (const peer of Object.values(sock.peers)) { + peers.add(peer); + } + } + return Array.from(peers); + }, + awaitData: function (ws) { + return PHPWASM.awaitEvent(ws, 'message'); + }, + awaitConnection: function (ws) { + if (ws.OPEN === ws.readyState) { + return [Promise.resolve(), PHPWASM.noop]; + } + return PHPWASM.awaitEvent(ws, 'open'); + }, + awaitClose: function (ws) { + if ([ws.CLOSING, ws.CLOSED].includes(ws.readyState)) { + return [Promise.resolve(), PHPWASM.noop]; + } + return PHPWASM.awaitEvent(ws, 'close'); + }, + awaitError: function (ws) { + if ([ws.CLOSING, ws.CLOSED].includes(ws.readyState)) { + return [Promise.resolve(), PHPWASM.noop]; + } + return PHPWASM.awaitEvent(ws, 'error'); + }, + awaitEvent: function (ws, event) { + let resolve; + const listener = () => { + resolve(); + }; + const promise = new Promise(function (_resolve) { + resolve = _resolve; + ws.once(event, listener); + }); + const cancel = () => { + ws.removeListener(event, listener); + // Rejecting the promises bubbles up and kills the entire + // node process. Let's resolve them on the next tick instead + // to give the caller some space to unbind any handlers. + setTimeout(resolve); + }; + return [promise, cancel]; + }, + noop: function () {}, + spawnProcess: function (command, args, options) { + if (Module['spawnProcess']) { + const spawned = Module['spawnProcess']( + command, + args, + /** + * We're providing the same extra options we would pass to child_process.spawn(). + * + * Why? + * + * spawnProcess() follows the same interface as child_process.spawn() + * and some consumers pass `child_process.spawn` directly to php.setSpawnHandler() + */ { + ...options, + shell: true, + stdio: ['pipe', 'pipe', 'pipe'], + } + ); + if (spawned && !('then' in spawned) && 'on' in spawned) { + /** + * If we get the child process directly, return it immediately. + * Delaying it to the next tick via Promise.resolve() would create + * a race condition where it might emit some events before the + * caller has a chance to bind event listeners to them. + * + * Without this condition, this callback would be at least flaky: + * + * php.setSpawnHandler(require('child_process').spawn); + */ return spawned; + } + return Promise.resolve(spawned).then(function (spawned) { + if (!spawned || !spawned.on) { + throw new Error( + 'spawnProcess() must return an EventEmitter but returned a different type.' + ); + } + return spawned; + }); + } + const e = new Error( + 'popen(), proc_open() etc. are unsupported on this PHP instance. Call php.setSpawnHandler() ' + + 'and provide a callback to handle spawning processes, or disable a popen(), proc_open() ' + + 'and similar functions via php.ini.' + ); + e.code = 'SPAWN_UNSUPPORTED'; + throw e; + }, + shutdownSocket: function (socketd, how) { + // This implementation only supports websockets at the moment + const sock = getSocketFromFD(socketd); + const peer = Object.values(sock.peers)[0]; + if (!peer) { + return -1; + } + try { + peer.socket.close(); + SOCKFS.websocket_sock_ops.removePeer(sock, peer); + return 0; + } catch (e) { + console.log('Socket shutdown error', e); + return -1; + } + }, + }; + + function _wasm_connect(sockfd, addr, addrlen) { + /** + * Use a synchronous connect() call when Asyncify is used. + * + * The async version was originally introduced to support the Memcached and Redis extensions, + * and both are only available with JSPI. Asyncify is too difficult to maintain and + * it's not getting that upgrade. + */ if (!('Suspending' in WebAssembly)) { + var sock = getSocketFromFD(sockfd); + var info = getSocketAddress(addr, addrlen); + sock.sock_ops.connect(sock, info.addr, info.port); + return 0; + } + return Asyncify.handleSleep((wakeUp) => { + // Get the socket + let sock; + try { + sock = getSocketFromFD(sockfd); + } catch (e) { + wakeUp(-ERRNO_CODES.EBADF); + return; + } + if (!sock) { + wakeUp(-ERRNO_CODES.EBADF); + return; + } + // Parse the address + let info; + try { + info = getSocketAddress(addr, addrlen); + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) { + wakeUp(-ERRNO_CODES.EFAULT); + return; + } + wakeUp(-e.errno); + return; + } + // Perform the connect (this creates the WebSocket but doesn't wait) + try { + sock.sock_ops.connect(sock, info.addr, info.port); + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) { + wakeUp(-ERRNO_CODES.ECONNREFUSED); + return; + } + wakeUp(-e.errno); + return; + } + // Get all websockets for this socket + const webSockets = PHPWASM.getAllWebSockets(sock); + if (!webSockets.length) { + // No WebSocket yet, this shouldn't happen after connect + wakeUp(-ERRNO_CODES.ECONNREFUSED); + return; + } + const ws = webSockets[0]; + // If already connected, return success + if (ws.readyState === ws.OPEN) { + wakeUp(0); + return; + } + // If already closed or closing, return error + if (ws.readyState === ws.CLOSING || ws.readyState === ws.CLOSED) { + wakeUp(-ERRNO_CODES.ECONNREFUSED); + return; + } + // Wait for the connection to be established + const timeout = 3e4; + // 30 second timeout + let resolved = false; + const timeoutId = setTimeout(() => { + if (!resolved) { + resolved = true; + wakeUp(-ERRNO_CODES.ETIMEDOUT); + } + }, timeout); + const handleOpen = () => { + if (!resolved) { + resolved = true; + clearTimeout(timeoutId); + ws.removeEventListener('error', handleError); + ws.removeEventListener('close', handleClose); + wakeUp(0); + } + }; + const handleError = () => { + if (!resolved) { + resolved = true; + clearTimeout(timeoutId); + ws.removeEventListener('open', handleOpen); + ws.removeEventListener('close', handleClose); + wakeUp(-ERRNO_CODES.ECONNREFUSED); + } + }; + const handleClose = () => { + if (!resolved) { + resolved = true; + clearTimeout(timeoutId); + ws.removeEventListener('open', handleOpen); + ws.removeEventListener('error', handleError); + wakeUp(-ERRNO_CODES.ECONNREFUSED); + } + }; + ws.addEventListener('open', handleOpen); + ws.addEventListener('error', handleError); + ws.addEventListener('close', handleClose); + }); + } + + function ___syscall_connect(sockfd, addr, addrlen, d1, d2, d3) { + return _wasm_connect(sockfd, addr, addrlen); + } + + ___syscall_connect.sig = 'iippiii'; + + function ___syscall_dup(fd) { + try { + var old = SYSCALLS.getStreamFromFD(fd); + return FS.dupStream(old).fd; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + + ___syscall_dup.sig = 'ii'; + + function ___syscall_dup3(fd, newfd, flags) { + try { + var old = SYSCALLS.getStreamFromFD(fd); + if (old.fd === newfd) return -28; + // Check newfd is within range of valid open file descriptors. + if (newfd < 0 || newfd >= FS.MAX_OPEN_FDS) return -8; + var existing = FS.getStream(newfd); + if (existing) FS.close(existing); + return FS.dupStream(old, newfd).fd; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + + ___syscall_dup3.sig = 'iiii'; + + function ___syscall_faccessat(dirfd, path, amode, flags) { + try { + path = SYSCALLS.getStr(path); + path = SYSCALLS.calculateAt(dirfd, path); + if (amode & ~7) { + // need a valid mode + return -28; + } + var lookup = FS.lookupPath(path, { + follow: true, + }); + var node = lookup.node; + if (!node) { + return -44; + } + var perms = ''; + if (amode & 4) perms += 'r'; + if (amode & 2) perms += 'w'; + if (amode & 1) perms += 'x'; + if (perms && FS.nodePermissions(node, perms)) { + return -2; + } + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + + ___syscall_faccessat.sig = 'iipii'; + + function ___syscall_fchmod(fd, mode) { + try { + FS.fchmod(fd, mode); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + + ___syscall_fchmod.sig = 'iii'; + + function ___syscall_fchown32(fd, owner, group) { + try { + FS.fchown(fd, owner, group); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + + ___syscall_fchown32.sig = 'iiii'; + + function ___syscall_fchownat(dirfd, path, owner, group, flags) { + try { + path = SYSCALLS.getStr(path); + var nofollow = flags & 256; + flags = flags & ~256; + path = SYSCALLS.calculateAt(dirfd, path); + (nofollow ? FS.lchown : FS.chown)(path, owner, group); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + + ___syscall_fchownat.sig = 'iipiii'; + + var syscallGetVarargI = () => { + // the `+` prepended here is necessary to convince the JSCompiler that varargs is indeed a number. + var ret = HEAP32[+SYSCALLS.varargs >> 2]; + SYSCALLS.varargs += 4; + return ret; + }; + + var syscallGetVarargP = syscallGetVarargI; + + function _fd_close(fd) { + if (typeof Module['userSpace'] === 'undefined') { + return _builtin_fd_close(fd); + } + return Module['userSpace'].fd_close(fd); + } + + _fd_close.sig = 'ii'; + + function _builtin_fd_close(fd) { + try { + var stream = SYSCALLS.getStreamFromFD(fd); + FS.close(stream); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return e.errno; + } + } + + function _builtin_fcntl64(fd, cmd, varargs) { + SYSCALLS.varargs = varargs; + try { + var stream = SYSCALLS.getStreamFromFD(fd); + switch (cmd) { + case 0: { + var arg = syscallGetVarargI(); + if (arg < 0) { + return -28; + } + while (FS.streams[arg]) { + arg++; + } + var newStream; + newStream = FS.dupStream(stream, arg); + return newStream.fd; + } + + case 1: + case 2: + return 0; + + // FD_CLOEXEC makes no sense for a single process. + case 3: + return stream.flags; + + case 4: { + var arg = syscallGetVarargI(); + stream.flags |= arg; + return 0; + } + + case 12: { + var arg = syscallGetVarargP(); + var offset = 0; + // We're always unlocked. + HEAP16[(arg + offset) >> 1] = 2; + return 0; + } + + case 13: + case 14: + // Pretend that the locking is successful. These are process-level locks, + // and Emscripten programs are a single process. If we supported linking a + // filesystem between programs, we'd need to do more here. + // See https://github.com/emscripten-core/emscripten/issues/23697 + return 0; + } + return -28; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + + function ___syscall_fcntl64(fd, cmd, varargs) { + if (typeof Module['userSpace'] === 'undefined') { + return _builtin_fcntl64(fd, cmd, varargs); + } + return Module['userSpace'].fcntl64(fd, cmd, varargs); + } + + ___syscall_fcntl64.sig = 'iiip'; + + function ___syscall_fstat64(fd, buf) { + try { + return SYSCALLS.writeStat(buf, FS.fstat(fd)); + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + + ___syscall_fstat64.sig = 'iip'; + + var INT53_MAX = 9007199254740992; + + var INT53_MIN = -9007199254740992; + + var bigintToI53Checked = (num) => + num < INT53_MIN || num > INT53_MAX ? NaN : Number(num); + + function ___syscall_ftruncate64(fd, length) { + length = bigintToI53Checked(length); + try { + if (isNaN(length)) return -61; + FS.ftruncate(fd, length); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + + ___syscall_ftruncate64.sig = 'iij'; + + function ___syscall_getcwd(buf, size) { + try { + if (size === 0) return -28; + var cwd = FS.cwd(); + var cwdLengthInBytes = lengthBytesUTF8(cwd) + 1; + if (size < cwdLengthInBytes) return -68; + stringToUTF8(cwd, buf, size); + return cwdLengthInBytes; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + + ___syscall_getcwd.sig = 'ipp'; + + function ___syscall_getdents64(fd, dirp, count) { + try { + var stream = SYSCALLS.getStreamFromFD(fd); + stream.getdents ||= FS.readdir(stream.path); + var struct_size = 280; + var pos = 0; + var off = FS.llseek(stream, 0, 1); + var startIdx = Math.floor(off / struct_size); + var endIdx = Math.min( + stream.getdents.length, + startIdx + Math.floor(count / struct_size) + ); + for (var idx = startIdx; idx < endIdx; idx++) { + var id; + var type; + var name = stream.getdents[idx]; + if (name === '.') { + id = stream.node.id; + type = 4; + } else if (name === '..') { + var lookup = FS.lookupPath(stream.path, { + parent: true, + }); + id = lookup.node.id; + type = 4; + } else { + var child; + try { + child = FS.lookupNode(stream.node, name); + } catch (e) { + // If the entry is not a directory, file, or symlink, nodefs + // lookupNode will raise EINVAL. Skip these and continue. + if (e?.errno === 28) { + continue; + } + throw e; + } + id = child.id; + type = FS.isChrdev(child.mode) + ? 2 // DT_CHR, character device. + : FS.isDir(child.mode) + ? 4 // DT_DIR, directory. + : FS.isLink(child.mode) + ? 10 // DT_LNK, symbolic link. + : 8; + } + HEAP64[(dirp + pos) >> 3] = BigInt(id); + HEAP64[(dirp + pos + 8) >> 3] = BigInt((idx + 1) * struct_size); + HEAP16[(dirp + pos + 16) >> 1] = 280; + HEAP8[dirp + pos + 18] = type; + stringToUTF8(name, dirp + pos + 19, 256); + pos += struct_size; + } + FS.llseek(stream, idx * struct_size, 0); + return pos; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + + ___syscall_getdents64.sig = 'iipp'; + + function ___syscall_getpeername(fd, addr, addrlen, d1, d2, d3) { + try { + var sock = getSocketFromFD(fd); + if (!sock.daddr) { + return -53; + } + var errno = writeSockaddr( + addr, + sock.family, + DNS.lookup_name(sock.daddr), + sock.dport, + addrlen + ); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + + ___syscall_getpeername.sig = 'iippiii'; + + function ___syscall_getsockname(fd, addr, addrlen, d1, d2, d3) { + try { + var sock = getSocketFromFD(fd); + // TODO: sock.saddr should never be undefined, see TODO in websocket_sock_ops.getname + var errno = writeSockaddr( + addr, + sock.family, + DNS.lookup_name(sock.saddr || '0.0.0.0'), + sock.sport, + addrlen + ); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + + ___syscall_getsockname.sig = 'iippiii'; + + function ___syscall_getsockopt(fd, level, optname, optval, optlen, d1) { + try { + var sock = getSocketFromFD(fd); + // Minimal getsockopt aimed at resolving https://github.com/emscripten-core/emscripten/issues/2211 + // so only supports SOL_SOCKET with SO_ERROR. + if (level === 1) { + if (optname === 4) { + HEAP32[optval >> 2] = sock.error; + HEAP32[optlen >> 2] = 4; + sock.error = null; + // Clear the error (The SO_ERROR option obtains and then clears this field). + return 0; + } + } + return -50; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + + ___syscall_getsockopt.sig = 'iiiippi'; + + function ___syscall_ioctl(fd, op, varargs) { + SYSCALLS.varargs = varargs; + try { + var stream = SYSCALLS.getStreamFromFD(fd); + switch (op) { + case 21509: { + if (!stream.tty) return -59; + return 0; + } + + case 21505: { + if (!stream.tty) return -59; + if (stream.tty.ops.ioctl_tcgets) { + var termios = stream.tty.ops.ioctl_tcgets(stream); + var argp = syscallGetVarargP(); + HEAP32[argp >> 2] = termios.c_iflag || 0; + HEAP32[(argp + 4) >> 2] = termios.c_oflag || 0; + HEAP32[(argp + 8) >> 2] = termios.c_cflag || 0; + HEAP32[(argp + 12) >> 2] = termios.c_lflag || 0; + for (var i = 0; i < 32; i++) { + HEAP8[argp + i + 17] = termios.c_cc[i] || 0; + } + return 0; + } + return 0; + } + + case 21510: + case 21511: + case 21512: { + if (!stream.tty) return -59; + return 0; + } + + case 21506: + case 21507: + case 21508: { + if (!stream.tty) return -59; + if (stream.tty.ops.ioctl_tcsets) { + var argp = syscallGetVarargP(); + var c_iflag = HEAP32[argp >> 2]; + var c_oflag = HEAP32[(argp + 4) >> 2]; + var c_cflag = HEAP32[(argp + 8) >> 2]; + var c_lflag = HEAP32[(argp + 12) >> 2]; + var c_cc = []; + for (var i = 0; i < 32; i++) { + c_cc.push(HEAP8[argp + i + 17]); + } + return stream.tty.ops.ioctl_tcsets(stream.tty, op, { + c_iflag, + c_oflag, + c_cflag, + c_lflag, + c_cc, + }); + } + return 0; + } + + case 21519: { + if (!stream.tty) return -59; + var argp = syscallGetVarargP(); + HEAP32[argp >> 2] = 0; + return 0; + } + + case 21520: { + if (!stream.tty) return -59; + return -28; + } + + case 21537: + case 21531: { + var argp = syscallGetVarargP(); + return FS.ioctl(stream, op, argp); + } + + case 21523: { + // TODO: in theory we should write to the winsize struct that gets + // passed in, but for now musl doesn't read anything on it + if (!stream.tty) return -59; + if (stream.tty.ops.ioctl_tiocgwinsz) { + var winsize = stream.tty.ops.ioctl_tiocgwinsz( + stream.tty + ); + var argp = syscallGetVarargP(); + HEAP16[argp >> 1] = winsize[0]; + HEAP16[(argp + 2) >> 1] = winsize[1]; + } + return 0; + } + + case 21524: { + // TODO: technically, this ioctl call should change the window size. + // but, since emscripten doesn't have any concept of a terminal window + // yet, we'll just silently throw it away as we do TIOCGWINSZ + if (!stream.tty) return -59; + return 0; + } + + case 21515: { + if (!stream.tty) return -59; + return 0; + } + + default: + return -28; + } + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + + ___syscall_ioctl.sig = 'iiip'; + + function ___syscall_listen(fd, backlog) { + try { + var sock = getSocketFromFD(fd); + sock.sock_ops.listen(sock, backlog); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + + ___syscall_listen.sig = 'iiiiiii'; + + function ___syscall_lstat64(path, buf) { + try { + path = SYSCALLS.getStr(path); + return SYSCALLS.writeStat(buf, FS.lstat(path)); + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + + ___syscall_lstat64.sig = 'ipp'; + + function ___syscall_mkdirat(dirfd, path, mode) { + try { + path = SYSCALLS.getStr(path); + path = SYSCALLS.calculateAt(dirfd, path); + FS.mkdir(path, mode, 0); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + + ___syscall_mkdirat.sig = 'iipi'; + + function ___syscall_newfstatat(dirfd, path, buf, flags) { + try { + path = SYSCALLS.getStr(path); + var nofollow = flags & 256; + var allowEmpty = flags & 4096; + flags = flags & ~6400; + path = SYSCALLS.calculateAt(dirfd, path, allowEmpty); + return SYSCALLS.writeStat( + buf, + nofollow ? FS.lstat(path) : FS.stat(path) + ); + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + + ___syscall_newfstatat.sig = 'iippi'; + + function ___syscall_openat(dirfd, path, flags, varargs) { + SYSCALLS.varargs = varargs; + try { + path = SYSCALLS.getStr(path); + path = SYSCALLS.calculateAt(dirfd, path); + var mode = varargs ? syscallGetVarargI() : 0; + return FS.open(path, flags, mode).fd; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + + ___syscall_openat.sig = 'iipip'; + + var PIPEFS = { + BUCKET_BUFFER_SIZE: 8192, + mount(mount) { + // Do not pollute the real root directory or its child nodes with pipes + // Looks like it is OK to create another pseudo-root node not linked to the FS.root hierarchy this way + return FS.createNode(null, '/', 16384 | 511, 0); + }, + createPipe() { + var pipe = { + buckets: [], + // refcnt 2 because pipe has a read end and a write end. We need to be + // able to read from the read end after write end is closed. + refcnt: 2, + timestamp: new Date(), + }; + pipe.buckets.push({ + buffer: new Uint8Array(PIPEFS.BUCKET_BUFFER_SIZE), + offset: 0, + roffset: 0, + }); + var rName = PIPEFS.nextname(); + var wName = PIPEFS.nextname(); + var rNode = FS.createNode(PIPEFS.root, rName, 4096, 0); + var wNode = FS.createNode(PIPEFS.root, wName, 4096, 0); + rNode.pipe = pipe; + wNode.pipe = pipe; + var readableStream = FS.createStream({ + path: rName, + node: rNode, + flags: 0, + seekable: false, + stream_ops: PIPEFS.stream_ops, + }); + rNode.stream = readableStream; + var writableStream = FS.createStream({ + path: wName, + node: wNode, + flags: 1, + seekable: false, + stream_ops: PIPEFS.stream_ops, + }); + wNode.stream = writableStream; + return { + readable_fd: readableStream.fd, + writable_fd: writableStream.fd, + }; + }, + stream_ops: { + getattr(stream) { + var node = stream.node; + var timestamp = node.pipe.timestamp; + return { + dev: 14, + ino: node.id, + mode: 4480, + nlink: 1, + uid: 0, + gid: 0, + rdev: 0, + size: 0, + atime: timestamp, + mtime: timestamp, + ctime: timestamp, + blksize: 4096, + blocks: 0, + }; + }, + poll(stream) { + var pipe = stream.node.pipe; + if ((stream.flags & 2097155) === 1) { + return 256 | 4; + } + for (var bucket of pipe.buckets) { + if (bucket.offset - bucket.roffset > 0) { + return 64 | 1; + } + } + return 0; + }, + dup(stream) { + stream.node.pipe.refcnt++; + }, + ioctl(stream, request, varargs) { + return 28; + }, + fsync(stream) { + return 28; + }, + read(stream, buffer, offset, length, position) { + var pipe = stream.node.pipe; + var currentLength = 0; + for (var bucket of pipe.buckets) { + currentLength += bucket.offset - bucket.roffset; + } + var data = buffer.subarray(offset, offset + length); + if (length <= 0) { + return 0; + } + if (currentLength == 0) { + if (pipe.refcnt < 2) { + return 0; + } + throw new FS.ErrnoError(6); + } + var toRead = Math.min(currentLength, length); + var totalRead = toRead; + var toRemove = 0; + for (var bucket of pipe.buckets) { + var bucketSize = bucket.offset - bucket.roffset; + if (toRead <= bucketSize) { + var tmpSlice = bucket.buffer.subarray( + bucket.roffset, + bucket.offset + ); + if (toRead < bucketSize) { + tmpSlice = tmpSlice.subarray(0, toRead); + bucket.roffset += toRead; + } else { + toRemove++; + } + data.set(tmpSlice); + break; + } else { + var tmpSlice = bucket.buffer.subarray( + bucket.roffset, + bucket.offset + ); + data.set(tmpSlice); + data = data.subarray(tmpSlice.byteLength); + toRead -= tmpSlice.byteLength; + toRemove++; + } + } + if (toRemove && toRemove == pipe.buckets.length) { + // Do not generate excessive garbage in use cases such as + // write several bytes, read everything, write several bytes, read everything... + toRemove--; + pipe.buckets[toRemove].offset = 0; + pipe.buckets[toRemove].roffset = 0; + } + pipe.buckets.splice(0, toRemove); + return totalRead; + }, + write(stream, buffer, offset, length, position) { + var pipe = stream.node.pipe; + var data = buffer.subarray(offset, offset + length); + var dataLen = data.byteLength; + if (dataLen <= 0) { + return 0; + } + var currBucket = null; + if (pipe.buckets.length == 0) { + currBucket = { + buffer: new Uint8Array(PIPEFS.BUCKET_BUFFER_SIZE), + offset: 0, + roffset: 0, + }; + pipe.buckets.push(currBucket); + } else { + currBucket = pipe.buckets[pipe.buckets.length - 1]; + } + var freeBytesInCurrBuffer = + PIPEFS.BUCKET_BUFFER_SIZE - currBucket.offset; + if (freeBytesInCurrBuffer >= dataLen) { + currBucket.buffer.set(data, currBucket.offset); + currBucket.offset += dataLen; + return dataLen; + } else if (freeBytesInCurrBuffer > 0) { + currBucket.buffer.set( + data.subarray(0, freeBytesInCurrBuffer), + currBucket.offset + ); + currBucket.offset += freeBytesInCurrBuffer; + data = data.subarray( + freeBytesInCurrBuffer, + data.byteLength + ); + } + var numBuckets = + (data.byteLength / PIPEFS.BUCKET_BUFFER_SIZE) | 0; + var remElements = data.byteLength % PIPEFS.BUCKET_BUFFER_SIZE; + for (var i = 0; i < numBuckets; i++) { + var newBucket = { + buffer: new Uint8Array(PIPEFS.BUCKET_BUFFER_SIZE), + offset: PIPEFS.BUCKET_BUFFER_SIZE, + roffset: 0, + }; + pipe.buckets.push(newBucket); + newBucket.buffer.set( + data.subarray(0, PIPEFS.BUCKET_BUFFER_SIZE) + ); + data = data.subarray( + PIPEFS.BUCKET_BUFFER_SIZE, + data.byteLength + ); + } + if (remElements > 0) { + var newBucket = { + buffer: new Uint8Array(PIPEFS.BUCKET_BUFFER_SIZE), + offset: data.byteLength, + roffset: 0, + }; + pipe.buckets.push(newBucket); + newBucket.buffer.set(data); + } + return dataLen; + }, + close(stream) { + var pipe = stream.node.pipe; + pipe.refcnt--; + if (pipe.refcnt === 0) { + pipe.buckets = null; + } + }, + }, + nextname() { + if (!PIPEFS.nextname.current) { + PIPEFS.nextname.current = 0; + } + return 'pipe[' + PIPEFS.nextname.current++ + ']'; + }, + }; + + function ___syscall_pipe(fdPtr) { + try { + if (fdPtr == 0) { + throw new FS.ErrnoError(21); + } + var res = PIPEFS.createPipe(); + HEAP32[fdPtr >> 2] = res.readable_fd; + HEAP32[(fdPtr + 4) >> 2] = res.writable_fd; + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + + ___syscall_pipe.sig = 'ip'; + + function ___syscall_poll(fds, nfds, timeout) { + try { + var nonzero = 0; + for (var i = 0; i < nfds; i++) { + var pollfd = fds + 8 * i; + var fd = HEAP32[pollfd >> 2]; + var events = HEAP16[(pollfd + 4) >> 1]; + var mask = 32; + var stream = FS.getStream(fd); + if (stream) { + mask = SYSCALLS.DEFAULT_POLLMASK; + if (stream.stream_ops?.poll) { + mask = stream.stream_ops.poll(stream, -1); + } + } + mask &= events | 8 | 16; + if (mask) nonzero++; + HEAP16[(pollfd + 6) >> 1] = mask; + } + return nonzero; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + + ___syscall_poll.sig = 'ipii'; + + function ___syscall_readlinkat(dirfd, path, buf, bufsize) { + try { + path = SYSCALLS.getStr(path); + path = SYSCALLS.calculateAt(dirfd, path); + if (bufsize <= 0) return -28; + var ret = FS.readlink(path); + var len = Math.min(bufsize, lengthBytesUTF8(ret)); + var endChar = HEAP8[buf + len]; + stringToUTF8(ret, buf, bufsize + 1); + // readlink is one of the rare functions that write out a C string, but does never append a null to the output buffer(!) + // stringToUTF8() always appends a null byte, so restore the character under the null byte after the write. + HEAP8[buf + len] = endChar; + return len; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + + ___syscall_readlinkat.sig = 'iippp'; + + function ___syscall_recvfrom(fd, buf, len, flags, addr, addrlen) { + try { + var sock = getSocketFromFD(fd); + var msg = sock.sock_ops.recvmsg( + sock, + len, + typeof flags !== 'undefined' ? flags : 0 + ); + if (!msg) return 0; + // socket is closed + if (addr) { + var errno = writeSockaddr( + addr, + sock.family, + DNS.lookup_name(msg.addr), + msg.port, + addrlen + ); + } + HEAPU8.set(msg.buffer, buf); + return msg.buffer.byteLength; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + + ___syscall_recvfrom.sig = 'iippipp'; + + function ___syscall_renameat(olddirfd, oldpath, newdirfd, newpath) { + try { + oldpath = SYSCALLS.getStr(oldpath); + newpath = SYSCALLS.getStr(newpath); + oldpath = SYSCALLS.calculateAt(olddirfd, oldpath); + newpath = SYSCALLS.calculateAt(newdirfd, newpath); + FS.rename(oldpath, newpath); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + + ___syscall_renameat.sig = 'iipip'; + + function ___syscall_rmdir(path) { + try { + path = SYSCALLS.getStr(path); + FS.rmdir(path); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + + ___syscall_rmdir.sig = 'ip'; + + function ___syscall_sendto(fd, message, length, flags, addr, addr_len) { + try { + var sock = getSocketFromFD(fd); + if (!addr) { + // send, no address provided + return FS.write(sock.stream, HEAP8, message, length); + } + var dest = getSocketAddress(addr, addr_len); + // sendto an address + return sock.sock_ops.sendmsg( + sock, + HEAP8, + message, + length, + dest.addr, + dest.port + ); + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + + ___syscall_sendto.sig = 'iippipp'; + + function ___syscall_socket(domain, type, protocol) { + try { + var sock = SOCKFS.createSocket(domain, type, protocol); + return sock.stream.fd; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + + ___syscall_socket.sig = 'iiiiiii'; + + function ___syscall_stat64(path, buf) { + try { + path = SYSCALLS.getStr(path); + return SYSCALLS.writeStat(buf, FS.stat(path)); + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + + ___syscall_stat64.sig = 'ipp'; + + function ___syscall_statfs64(path, size, buf) { + try { + SYSCALLS.writeStatFs(buf, FS.statfs(SYSCALLS.getStr(path))); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + + ___syscall_statfs64.sig = 'ippp'; + + function ___syscall_symlinkat(target, dirfd, linkpath) { + try { + target = SYSCALLS.getStr(target); + linkpath = SYSCALLS.getStr(linkpath); + linkpath = SYSCALLS.calculateAt(dirfd, linkpath); + FS.symlink(target, linkpath); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + + ___syscall_symlinkat.sig = 'ipip'; + + function ___syscall_unlinkat(dirfd, path, flags) { + try { + path = SYSCALLS.getStr(path); + path = SYSCALLS.calculateAt(dirfd, path); + if (!flags) { + FS.unlink(path); + } else if (flags === 512) { + FS.rmdir(path); + } else { + return -28; + } + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + + ___syscall_unlinkat.sig = 'iipi'; + + var readI53FromI64 = (ptr) => + HEAPU32[ptr >> 2] + HEAP32[(ptr + 4) >> 2] * 4294967296; + + function ___syscall_utimensat(dirfd, path, times, flags) { + try { + path = SYSCALLS.getStr(path); + path = SYSCALLS.calculateAt(dirfd, path, true); + var now = Date.now(), + atime, + mtime; + if (!times) { + atime = now; + mtime = now; + } else { + var seconds = readI53FromI64(times); + var nanoseconds = HEAP32[(times + 8) >> 2]; + if (nanoseconds == 1073741823) { + atime = now; + } else if (nanoseconds == 1073741822) { + atime = null; + } else { + atime = seconds * 1e3 + nanoseconds / (1e3 * 1e3); + } + times += 16; + seconds = readI53FromI64(times); + nanoseconds = HEAP32[(times + 8) >> 2]; + if (nanoseconds == 1073741823) { + mtime = now; + } else if (nanoseconds == 1073741822) { + mtime = null; + } else { + mtime = seconds * 1e3 + nanoseconds / (1e3 * 1e3); + } + } + // null here means UTIME_OMIT was passed. If both were set to UTIME_OMIT then + // we can skip the call completely. + if ((mtime ?? atime) !== null) { + FS.utime(path, atime, mtime); + } + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + + ___syscall_utimensat.sig = 'iippi'; + + var __abort_js = () => abort(''); + + __abort_js.sig = 'v'; + + var dlSetError = (msg) => { + var sp = stackSave(); + var cmsg = stringToUTF8OnStack(msg); + ___dl_seterr(cmsg, 0); + stackRestore(sp); + }; + + var dlopenInternal = (handle, jsflags) => { + // void *dlopen(const char *file, int mode); + // http://pubs.opengroup.org/onlinepubs/009695399/functions/dlopen.html + var filename = UTF8ToString(handle + 36); + var flags = HEAP32[(handle + 4) >> 2]; + filename = PATH.normalize(filename); + var global = Boolean(flags & 256); + var localScope = global ? null : {}; + // We don't care about RTLD_NOW and RTLD_LAZY. + var combinedFlags = { + global, + nodelete: Boolean(flags & 4096), + loadAsync: jsflags.loadAsync, + }; + if (jsflags.loadAsync) { + return loadDynamicLibrary( + filename, + combinedFlags, + localScope, + handle + ); + } + try { + return loadDynamicLibrary( + filename, + combinedFlags, + localScope, + handle + ); + } catch (e) { + dlSetError(`could not load dynamic lib: ${filename}\n${e}`); + return 0; + } + }; + + function __dlopen_js(handle) { + var jsflags = { + loadAsync: false, + }; + return dlopenInternal(handle, jsflags); + } + + __dlopen_js.sig = 'pp'; + + var __dlsym_js = (handle, symbol, symbolIndex) => { + // void *dlsym(void *restrict handle, const char *restrict name); + // http://pubs.opengroup.org/onlinepubs/009695399/functions/dlsym.html + symbol = UTF8ToString(symbol); + var result; + var newSymIndex; + var lib = LDSO.loadedLibsByHandle[handle]; + newSymIndex = Object.keys(lib.exports).indexOf(symbol); + if (newSymIndex == -1 || lib.exports[symbol].stub) { + dlSetError( + `Tried to lookup unknown symbol "${symbol}" in dynamic lib: ${lib.name}` + ); + return 0; + } + result = lib.exports[symbol]; + if (typeof result == 'function') { + // Asyncify wraps exports, and we need to look through those wrappers. + if (result.orig) { + result = result.orig; + } + var addr = getFunctionAddress(result); + if (addr) { + result = addr; + } else { + // Insert the function into the wasm table. If its a direct wasm + // function the second argument will not be needed. If its a JS + // function we rely on the `sig` attribute being set based on the + // `__sig` specified in library JS file. + result = addFunction(result, result.sig); + HEAPU32[symbolIndex >> 2] = newSymIndex; + } + } + return result; + }; + + __dlsym_js.sig = 'pppp'; + + var __emscripten_lookup_name = (name) => { + // uint32_t _emscripten_lookup_name(const char *name); + var nameString = UTF8ToString(name); + return inetPton4(DNS.lookup_name(nameString)); + }; + + __emscripten_lookup_name.sig = 'ip'; + + var runtimeKeepaliveCounter = 0; + + var __emscripten_runtime_keepalive_clear = () => { + noExitRuntime = false; + runtimeKeepaliveCounter = 0; + }; + + __emscripten_runtime_keepalive_clear.sig = 'v'; + + var __emscripten_throw_longjmp = () => { + throw Infinity; + }; + + __emscripten_throw_longjmp.sig = 'v'; + + function __gmtime_js(time, tmPtr) { + time = bigintToI53Checked(time); + var date = new Date(time * 1e3); + HEAP32[tmPtr >> 2] = date.getUTCSeconds(); + HEAP32[(tmPtr + 4) >> 2] = date.getUTCMinutes(); + HEAP32[(tmPtr + 8) >> 2] = date.getUTCHours(); + HEAP32[(tmPtr + 12) >> 2] = date.getUTCDate(); + HEAP32[(tmPtr + 16) >> 2] = date.getUTCMonth(); + HEAP32[(tmPtr + 20) >> 2] = date.getUTCFullYear() - 1900; + HEAP32[(tmPtr + 24) >> 2] = date.getUTCDay(); + var start = Date.UTC(date.getUTCFullYear(), 0, 1, 0, 0, 0, 0); + var yday = ((date.getTime() - start) / (1e3 * 60 * 60 * 24)) | 0; + HEAP32[(tmPtr + 28) >> 2] = yday; + } + + __gmtime_js.sig = 'vjp'; + + var isLeapYear = (year) => + year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0); + + var MONTH_DAYS_LEAP_CUMULATIVE = [ + 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, + ]; + + var MONTH_DAYS_REGULAR_CUMULATIVE = [ + 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, + ]; + + var ydayFromDate = (date) => { + var leap = isLeapYear(date.getFullYear()); + var monthDaysCumulative = leap + ? MONTH_DAYS_LEAP_CUMULATIVE + : MONTH_DAYS_REGULAR_CUMULATIVE; + var yday = monthDaysCumulative[date.getMonth()] + date.getDate() - 1; + // -1 since it's days since Jan 1 + return yday; + }; + + function __localtime_js(time, tmPtr) { + time = bigintToI53Checked(time); + var date = new Date(time * 1e3); + HEAP32[tmPtr >> 2] = date.getSeconds(); + HEAP32[(tmPtr + 4) >> 2] = date.getMinutes(); + HEAP32[(tmPtr + 8) >> 2] = date.getHours(); + HEAP32[(tmPtr + 12) >> 2] = date.getDate(); + HEAP32[(tmPtr + 16) >> 2] = date.getMonth(); + HEAP32[(tmPtr + 20) >> 2] = date.getFullYear() - 1900; + HEAP32[(tmPtr + 24) >> 2] = date.getDay(); + var yday = ydayFromDate(date) | 0; + HEAP32[(tmPtr + 28) >> 2] = yday; + HEAP32[(tmPtr + 36) >> 2] = -(date.getTimezoneOffset() * 60); + // Attention: DST is in December in South, and some regions don't have DST at all. + var start = new Date(date.getFullYear(), 0, 1); + var summerOffset = new Date( + date.getFullYear(), + 6, + 1 + ).getTimezoneOffset(); + var winterOffset = start.getTimezoneOffset(); + var dst = + (summerOffset != winterOffset && + date.getTimezoneOffset() == + Math.min(winterOffset, summerOffset)) | 0; + HEAP32[(tmPtr + 32) >> 2] = dst; + } + + __localtime_js.sig = 'vjp'; + + var __mktime_js = function (tmPtr) { + var ret = (() => { + var date = new Date( + HEAP32[(tmPtr + 20) >> 2] + 1900, + HEAP32[(tmPtr + 16) >> 2], + HEAP32[(tmPtr + 12) >> 2], + HEAP32[(tmPtr + 8) >> 2], + HEAP32[(tmPtr + 4) >> 2], + HEAP32[tmPtr >> 2], + 0 + ); + // There's an ambiguous hour when the time goes back; the tm_isdst field is + // used to disambiguate it. Date() basically guesses, so we fix it up if it + // guessed wrong, or fill in tm_isdst with the guess if it's -1. + var dst = HEAP32[(tmPtr + 32) >> 2]; + var guessedOffset = date.getTimezoneOffset(); + var start = new Date(date.getFullYear(), 0, 1); + var summerOffset = new Date( + date.getFullYear(), + 6, + 1 + ).getTimezoneOffset(); + var winterOffset = start.getTimezoneOffset(); + var dstOffset = Math.min(winterOffset, summerOffset); + // DST is in December in South + if (dst < 0) { + // Attention: some regions don't have DST at all. + HEAP32[(tmPtr + 32) >> 2] = Number( + summerOffset != winterOffset && dstOffset == guessedOffset + ); + } else if (dst > 0 != (dstOffset == guessedOffset)) { + var nonDstOffset = Math.max(winterOffset, summerOffset); + var trueOffset = dst > 0 ? dstOffset : nonDstOffset; + // Don't try setMinutes(date.getMinutes() + ...) -- it's messed up. + date.setTime( + date.getTime() + (trueOffset - guessedOffset) * 6e4 + ); + } + HEAP32[(tmPtr + 24) >> 2] = date.getDay(); + var yday = ydayFromDate(date) | 0; + HEAP32[(tmPtr + 28) >> 2] = yday; + // To match expected behavior, update fields from date + HEAP32[tmPtr >> 2] = date.getSeconds(); + HEAP32[(tmPtr + 4) >> 2] = date.getMinutes(); + HEAP32[(tmPtr + 8) >> 2] = date.getHours(); + HEAP32[(tmPtr + 12) >> 2] = date.getDate(); + HEAP32[(tmPtr + 16) >> 2] = date.getMonth(); + HEAP32[(tmPtr + 20) >> 2] = date.getYear(); + var timeMs = date.getTime(); + if (isNaN(timeMs)) { + return -1; + } + // Return time in microseconds + return timeMs / 1e3; + })(); + return BigInt(ret); + }; + + __mktime_js.sig = 'jp'; + + function __mmap_js(len, prot, flags, fd, offset, allocated, addr) { + offset = bigintToI53Checked(offset); + try { + var stream = SYSCALLS.getStreamFromFD(fd); + var res = FS.mmap(stream, len, offset, prot, flags); + var ptr = res.ptr; + HEAP32[allocated >> 2] = res.allocated; + HEAPU32[addr >> 2] = ptr; + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + + __mmap_js.sig = 'ipiiijpp'; + + function __munmap_js(addr, len, prot, flags, fd, offset) { + offset = bigintToI53Checked(offset); + try { + var stream = SYSCALLS.getStreamFromFD(fd); + if (prot & 2) { + SYSCALLS.doMsync(addr, stream, len, flags, offset); + } + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + + __munmap_js.sig = 'ippiiij'; + + var timers = {}; + + var handleException = (e) => { + // Certain exception types we do not treat as errors since they are used for + // internal control flow. + // 1. ExitStatus, which is thrown by exit() + // 2. "unwind", which is thrown by emscripten_unwind_to_js_event_loop() and others + // that wish to return to JS event loop. + if (e instanceof ExitStatus || e == 'unwind') { + return EXITSTATUS; + } + quit_(1, e); + }; + + var keepRuntimeAlive = () => noExitRuntime || runtimeKeepaliveCounter > 0; + + var _proc_exit = (code) => { + EXITSTATUS = code; + if (!keepRuntimeAlive()) { + Module['onExit']?.(code); + ABORT = true; + } + quit_(code, new ExitStatus(code)); + }; + + _proc_exit.sig = 'vi'; + + /** @param {boolean|number=} implicit */ var exitJS = ( + status, + implicit + ) => { + EXITSTATUS = status; + if (!keepRuntimeAlive()) { + exitRuntime(); + } + _proc_exit(status); + }; + + var _exit = exitJS; + + _exit.sig = 'vi'; + + var maybeExit = () => { + if (runtimeExited) { + return; + } + if (!keepRuntimeAlive()) { + try { + _exit(EXITSTATUS); + } catch (e) { + handleException(e); + } + } + }; + + var callUserCallback = (func) => { + if (runtimeExited || ABORT) { + return; + } + try { + func(); + maybeExit(); + } catch (e) { + handleException(e); + } + }; + + var _emscripten_get_now = () => performance.now(); + + _emscripten_get_now.sig = 'd'; + + var __setitimer_js = (which, timeout_ms) => { + // First, clear any existing timer. + if (timers[which]) { + clearTimeout(timers[which].id); + delete timers[which]; + } + // A timeout of zero simply cancels the current timeout so we have nothing + // more to do. + if (!timeout_ms) return 0; + var id = setTimeout(() => { + delete timers[which]; + callUserCallback(() => + __emscripten_timeout(which, _emscripten_get_now()) + ); + }, timeout_ms); + timers[which] = { + id, + timeout_ms, + }; + return 0; + }; + + __setitimer_js.sig = 'iid'; + + var __tzset_js = (timezone, daylight, std_name, dst_name) => { + // TODO: Use (malleable) environment variables instead of system settings. + var currentYear = new Date().getFullYear(); + var winter = new Date(currentYear, 0, 1); + var summer = new Date(currentYear, 6, 1); + var winterOffset = winter.getTimezoneOffset(); + var summerOffset = summer.getTimezoneOffset(); + // Local standard timezone offset. Local standard time is not adjusted for + // daylight savings. This code uses the fact that getTimezoneOffset returns + // a greater value during Standard Time versus Daylight Saving Time (DST). + // Thus it determines the expected output during Standard Time, and it + // compares whether the output of the given date the same (Standard) or less + // (DST). + var stdTimezoneOffset = Math.max(winterOffset, summerOffset); + // timezone is specified as seconds west of UTC ("The external variable + // `timezone` shall be set to the difference, in seconds, between + // Coordinated Universal Time (UTC) and local standard time."), the same + // as returned by stdTimezoneOffset. + // See http://pubs.opengroup.org/onlinepubs/009695399/functions/tzset.html + HEAPU32[timezone >> 2] = stdTimezoneOffset * 60; + HEAP32[daylight >> 2] = Number(winterOffset != summerOffset); + var extractZone = (timezoneOffset) => { + // Why inverse sign? + // Read here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getTimezoneOffset + var sign = timezoneOffset >= 0 ? '-' : '+'; + var absOffset = Math.abs(timezoneOffset); + var hours = String(Math.floor(absOffset / 60)).padStart(2, '0'); + var minutes = String(absOffset % 60).padStart(2, '0'); + return `UTC${sign}${hours}${minutes}`; + }; + var winterName = extractZone(winterOffset); + var summerName = extractZone(summerOffset); + if (summerOffset < winterOffset) { + // Northern hemisphere + stringToUTF8(winterName, std_name, 17); + stringToUTF8(summerName, dst_name, 17); + } else { + stringToUTF8(winterName, dst_name, 17); + stringToUTF8(summerName, std_name, 17); + } + }; + + __tzset_js.sig = 'vpppp'; + + var _emscripten_date_now = () => Date.now(); + + _emscripten_date_now.sig = 'd'; + + var nowIsMonotonic = 1; + + var checkWasiClock = (clock_id) => clock_id >= 0 && clock_id <= 3; + + function _clock_time_get(clk_id, ignored_precision, ptime) { + ignored_precision = bigintToI53Checked(ignored_precision); + if (!checkWasiClock(clk_id)) { + return 28; + } + var now; + // all wasi clocks but realtime are monotonic + if (clk_id === 0) { + now = _emscripten_date_now(); + } else if (nowIsMonotonic) { + now = _emscripten_get_now(); + } else { + return 52; + } + // "now" is in ms, and wasi times are in ns. + var nsec = Math.round(now * 1e3 * 1e3); + HEAP64[ptime >> 3] = BigInt(nsec); + return 0; + } + + _clock_time_get.sig = 'iijp'; + + var getHeapMax = () => + // Stay one Wasm page short of 4GB: while e.g. Chrome is able to allocate + // full 4GB Wasm memories, the size will wrap back to 0 bytes in Wasm side + // for any code that deals with heap sizes, which would require special + // casing all heap size related code to treat 0 specially. + 2147483648; + + var _emscripten_get_heap_max = () => getHeapMax(); + + _emscripten_get_heap_max.sig = 'p'; + + var growMemory = (size) => { + var oldHeapSize = wasmMemory.buffer.byteLength; + var pages = ((size - oldHeapSize + 65535) / 65536) | 0; + try { + // round size grow request up to wasm page size (fixed 64KB per spec) + wasmMemory.grow(pages); + // .grow() takes a delta compared to the previous size + updateMemoryViews(); + return 1; + } catch (e) {} + }; + + var _emscripten_resize_heap = (requestedSize) => { + var oldSize = HEAPU8.length; + // With CAN_ADDRESS_2GB or MEMORY64, pointers are already unsigned. + requestedSize >>>= 0; + // With multithreaded builds, races can happen (another thread might increase the size + // in between), so return a failure, and let the caller retry. + // Memory resize rules: + // 1. Always increase heap size to at least the requested size, rounded up + // to next page multiple. + // 2a. If MEMORY_GROWTH_LINEAR_STEP == -1, excessively resize the heap + // geometrically: increase the heap size according to + // MEMORY_GROWTH_GEOMETRIC_STEP factor (default +20%), At most + // overreserve by MEMORY_GROWTH_GEOMETRIC_CAP bytes (default 96MB). + // 2b. If MEMORY_GROWTH_LINEAR_STEP != -1, excessively resize the heap + // linearly: increase the heap size by at least + // MEMORY_GROWTH_LINEAR_STEP bytes. + // 3. Max size for the heap is capped at 2048MB-WASM_PAGE_SIZE, or by + // MAXIMUM_MEMORY, or by ASAN limit, depending on which is smallest + // 4. If we were unable to allocate as much memory, it may be due to + // over-eager decision to excessively reserve due to (3) above. + // Hence if an allocation fails, cut down on the amount of excess + // growth, in an attempt to succeed to perform a smaller allocation. + // A limit is set for how much we can grow. We should not exceed that + // (the wasm binary specifies it, so if we tried, we'd fail anyhow). + var maxHeapSize = getHeapMax(); + if (requestedSize > maxHeapSize) { + return false; + } + // Loop through potential heap size increases. If we attempt a too eager + // reservation that fails, cut down on the attempted size and reserve a + // smaller bump instead. (max 3 times, chosen somewhat arbitrarily) + for (var cutDown = 1; cutDown <= 4; cutDown *= 2) { + var overGrownHeapSize = oldSize * (1 + 0.2 / cutDown); + // ensure geometric growth + // but limit overreserving (default to capping at +96MB overgrowth at most) + overGrownHeapSize = Math.min( + overGrownHeapSize, + requestedSize + 100663296 + ); + var newSize = Math.min( + maxHeapSize, + alignMemory(Math.max(requestedSize, overGrownHeapSize), 65536) + ); + var replacement = growMemory(newSize); + if (replacement) { + return true; + } + } + return false; + }; + + _emscripten_resize_heap.sig = 'ip'; + + var runtimeKeepalivePush = () => { + runtimeKeepaliveCounter += 1; + }; + + runtimeKeepalivePush.sig = 'v'; + + var runtimeKeepalivePop = () => { + runtimeKeepaliveCounter -= 1; + }; + + runtimeKeepalivePop.sig = 'v'; + + /** @param {number=} timeout */ var safeSetTimeout = (func, timeout) => { + runtimeKeepalivePush(); + return setTimeout(() => { + runtimeKeepalivePop(); + callUserCallback(func); + }, timeout); + }; + + var _emscripten_sleep = (ms) => + Asyncify.handleSleep((wakeUp) => safeSetTimeout(wakeUp, ms)); + + _emscripten_sleep.sig = 'vi'; + + _emscripten_sleep.isAsync = true; + + var ENV = PHPLoader.ENV || {}; + + var getExecutableName = () => thisProgram || './this.program'; + + var getEnvStrings = () => { + if (!getEnvStrings.strings) { + // Default values. + // Browser language detection #8751 + var lang = + ( + (typeof navigator == 'object' && navigator.language) || + 'C' + ).replace('-', '_') + '.UTF-8'; + var env = { + USER: 'web_user', + LOGNAME: 'web_user', + PATH: '/', + PWD: '/', + HOME: '/home/web_user', + LANG: lang, + _: getExecutableName(), + }; + // Apply the user-provided values, if any. + for (var x in ENV) { + // x is a key in ENV; if ENV[x] is undefined, that means it was + // explicitly set to be so. We allow user code to do that to + // force variables with default values to remain unset. + if (ENV[x] === undefined) delete env[x]; + else env[x] = ENV[x]; + } + var strings = []; + for (var x in env) { + strings.push(`${x}=${env[x]}`); + } + getEnvStrings.strings = strings; + } + return getEnvStrings.strings; + }; + + var _environ_get = (__environ, environ_buf) => { + var bufSize = 0; + var envp = 0; + for (var string of getEnvStrings()) { + var ptr = environ_buf + bufSize; + HEAPU32[(__environ + envp) >> 2] = ptr; + bufSize += stringToUTF8(string, ptr, Infinity) + 1; + envp += 4; + } + return 0; + }; + + _environ_get.sig = 'ipp'; + + var _environ_sizes_get = (penviron_count, penviron_buf_size) => { + var strings = getEnvStrings(); + HEAPU32[penviron_count >> 2] = strings.length; + var bufSize = 0; + for (var string of strings) { + bufSize += lengthBytesUTF8(string) + 1; + } + HEAPU32[penviron_buf_size >> 2] = bufSize; + return 0; + }; + + _environ_sizes_get.sig = 'ipp'; + + function _fd_fdstat_get(fd, pbuf) { + try { + var rightsBase = 0; + var rightsInheriting = 0; + var flags = 0; + { + var stream = SYSCALLS.getStreamFromFD(fd); + // All character devices are terminals (other things a Linux system would + // assume is a character device, like the mouse, we have special APIs for). + var type = stream.tty + ? 2 + : FS.isDir(stream.mode) + ? 3 + : FS.isLink(stream.mode) + ? 7 + : 4; + } + HEAP8[pbuf] = type; + HEAP16[(pbuf + 2) >> 1] = flags; + HEAP64[(pbuf + 8) >> 3] = BigInt(rightsBase); + HEAP64[(pbuf + 16) >> 3] = BigInt(rightsInheriting); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return e.errno; + } + } + + _fd_fdstat_get.sig = 'iip'; + + /** @param {number=} offset */ var doReadv = ( + stream, + iov, + iovcnt, + offset + ) => { + var ret = 0; + for (var i = 0; i < iovcnt; i++) { + var ptr = HEAPU32[iov >> 2]; + var len = HEAPU32[(iov + 4) >> 2]; + iov += 8; + var curr = FS.read(stream, HEAP8, ptr, len, offset); + if (curr < 0) return -1; + ret += curr; + if (curr < len) break; + // nothing more to read + if (typeof offset != 'undefined') { + offset += curr; + } + } + return ret; + }; + + function _fd_read(fd, iov, iovcnt, pnum) { + try { + var stream = SYSCALLS.getStreamFromFD(fd); + var num = doReadv(stream, iov, iovcnt); + HEAPU32[pnum >> 2] = num; + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return e.errno; + } + } + + _fd_read.sig = 'iippp'; + + function _fd_seek(fd, offset, whence, newOffset) { + offset = bigintToI53Checked(offset); + try { + if (isNaN(offset)) return 61; + var stream = SYSCALLS.getStreamFromFD(fd); + FS.llseek(stream, offset, whence); + HEAP64[newOffset >> 3] = BigInt(stream.position); + if (stream.getdents && offset === 0 && whence === 0) + stream.getdents = null; + // reset readdir state + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return e.errno; + } + } + + _fd_seek.sig = 'iijip'; + + var _fd_sync = function (fd) { + try { + var stream = SYSCALLS.getStreamFromFD(fd); + return Asyncify.handleSleep((wakeUp) => { + var mount = stream.node.mount; + if (!mount.type.syncfs) { + // We write directly to the file system, so there's nothing to do here. + wakeUp(0); + return; + } + mount.type.syncfs(mount, false, (err) => { + wakeUp(err ? 29 : 0); + }); + }); + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return e.errno; + } + }; + + _fd_sync.sig = 'ii'; + + _fd_sync.isAsync = true; + + /** @param {number=} offset */ var doWritev = ( + stream, + iov, + iovcnt, + offset + ) => { + var ret = 0; + for (var i = 0; i < iovcnt; i++) { + var ptr = HEAPU32[iov >> 2]; + var len = HEAPU32[(iov + 4) >> 2]; + iov += 8; + var curr = FS.write(stream, HEAP8, ptr, len, offset); + if (curr < 0) return -1; + ret += curr; + if (curr < len) { + // No more space to write. + break; + } + if (typeof offset != 'undefined') { + offset += curr; + } + } + return ret; + }; + + function _fd_write(fd, iov, iovcnt, pnum) { + try { + var stream = SYSCALLS.getStreamFromFD(fd); + var num = doWritev(stream, iov, iovcnt); + HEAPU32[pnum >> 2] = num; + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return e.errno; + } + } + + _fd_write.sig = 'iippp'; + + var _getaddrinfo = (node, service, hint, out) => { + var addr = 0; + var port = 0; + var flags = 0; + var family = 0; + var type = 0; + var proto = 0; + var ai; + function allocaddrinfo(family, type, proto, canon, addr, port) { + var sa, salen, ai; + var errno; + salen = family === 10 ? 28 : 16; + addr = family === 10 ? inetNtop6(addr) : inetNtop4(addr); + sa = _malloc(salen); + errno = writeSockaddr(sa, family, addr, port); + ai = _malloc(32); + HEAP32[(ai + 4) >> 2] = family; + HEAP32[(ai + 8) >> 2] = type; + HEAP32[(ai + 12) >> 2] = proto; + HEAPU32[(ai + 24) >> 2] = canon; + HEAPU32[(ai + 20) >> 2] = sa; + if (family === 10) { + HEAP32[(ai + 16) >> 2] = 28; + } else { + HEAP32[(ai + 16) >> 2] = 16; + } + HEAP32[(ai + 28) >> 2] = 0; + return ai; + } + if (hint) { + flags = HEAP32[hint >> 2]; + family = HEAP32[(hint + 4) >> 2]; + type = HEAP32[(hint + 8) >> 2]; + proto = HEAP32[(hint + 12) >> 2]; + } + if (type && !proto) { + proto = type === 2 ? 17 : 6; + } + if (!type && proto) { + type = proto === 17 ? 2 : 1; + } + // If type or proto are set to zero in hints we should really be returning multiple addrinfo values, but for + // now default to a TCP STREAM socket so we can at least return a sensible addrinfo given NULL hints. + if (proto === 0) { + proto = 6; + } + if (type === 0) { + type = 1; + } + if (!node && !service) { + return -2; + } + if (flags & ~(1 | 2 | 4 | 1024 | 8 | 16 | 32)) { + return -1; + } + if (hint !== 0 && HEAP32[hint >> 2] & 2 && !node) { + return -1; + } + if (flags & 32) { + // TODO + return -2; + } + if (type !== 0 && type !== 1 && type !== 2) { + return -7; + } + if (family !== 0 && family !== 2 && family !== 10) { + return -6; + } + if (service) { + service = UTF8ToString(service); + port = parseInt(service, 10); + if (isNaN(port)) { + if (flags & 1024) { + return -2; + } + // TODO support resolving well-known service names from: + // http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.txt + return -8; + } + } + if (!node) { + if (family === 0) { + family = 2; + } + if ((flags & 1) === 0) { + if (family === 2) { + addr = _htonl(2130706433); + } else { + addr = [0, 0, 0, _htonl(1)]; + } + } + ai = allocaddrinfo(family, type, proto, null, addr, port); + HEAPU32[out >> 2] = ai; + return 0; + } + // try as a numeric address + node = UTF8ToString(node); + addr = inetPton4(node); + if (addr !== null) { + // incoming node is a valid ipv4 address + if (family === 0 || family === 2) { + family = 2; + } else if (family === 10 && flags & 8) { + addr = [0, 0, _htonl(65535), addr]; + family = 10; + } else { + return -2; + } + } else { + addr = inetPton6(node); + if (addr !== null) { + // incoming node is a valid ipv6 address + if (family === 0 || family === 10) { + family = 10; + } else { + return -2; + } + } + } + if (addr != null) { + ai = allocaddrinfo(family, type, proto, node, addr, port); + HEAPU32[out >> 2] = ai; + return 0; + } + if (flags & 4) { + return -2; + } + // try as a hostname + // resolve the hostname to a temporary fake address + node = DNS.lookup_name(node); + addr = inetPton4(node); + if (family === 0) { + family = 2; + } else if (family === 10) { + addr = [0, 0, _htonl(65535), addr]; + } + ai = allocaddrinfo(family, type, proto, null, addr, port); + HEAPU32[out >> 2] = ai; + return 0; + }; + + _getaddrinfo.sig = 'ipppp'; + + var _getnameinfo = (sa, salen, node, nodelen, serv, servlen, flags) => { + var info = readSockaddr(sa, salen); + if (info.errno) { + return -6; + } + var port = info.port; + var addr = info.addr; + var overflowed = false; + if (node && nodelen) { + var lookup; + if (flags & 1 || !(lookup = DNS.lookup_addr(addr))) { + if (flags & 8) { + return -2; + } + } else { + addr = lookup; + } + var numBytesWrittenExclNull = stringToUTF8(addr, node, nodelen); + if (numBytesWrittenExclNull + 1 >= nodelen) { + overflowed = true; + } + } + if (serv && servlen) { + port = '' + port; + var numBytesWrittenExclNull = stringToUTF8(port, serv, servlen); + if (numBytesWrittenExclNull + 1 >= servlen) { + overflowed = true; + } + } + if (overflowed) { + // Note: even when we overflow, getnameinfo() is specced to write out the truncated results. + return -12; + } + return 0; + }; + + _getnameinfo.sig = 'ipipipii'; + + var Protocols = { + list: [], + map: {}, + }; + + var stringToAscii = (str, buffer) => { + for (var i = 0; i < str.length; ++i) { + HEAP8[buffer++] = str.charCodeAt(i); + } + // Null-terminate the string + HEAP8[buffer] = 0; + }; + + var _setprotoent = (stayopen) => { + // void setprotoent(int stayopen); + // Allocate and populate a protoent structure given a name, protocol number and array of aliases + function allocprotoent(name, proto, aliases) { + // write name into buffer + var nameBuf = _malloc(name.length + 1); + stringToAscii(name, nameBuf); + // write aliases into buffer + var j = 0; + var length = aliases.length; + var aliasListBuf = _malloc((length + 1) * 4); + // Use length + 1 so we have space for the terminating NULL ptr. + for (var i = 0; i < length; i++, j += 4) { + var alias = aliases[i]; + var aliasBuf = _malloc(alias.length + 1); + stringToAscii(alias, aliasBuf); + HEAPU32[(aliasListBuf + j) >> 2] = aliasBuf; + } + HEAPU32[(aliasListBuf + j) >> 2] = 0; + // Terminating NULL pointer. + // generate protoent + var pe = _malloc(12); + HEAPU32[pe >> 2] = nameBuf; + HEAPU32[(pe + 4) >> 2] = aliasListBuf; + HEAP32[(pe + 8) >> 2] = proto; + return pe; + } + // Populate the protocol 'database'. The entries are limited to tcp and udp, though it is fairly trivial + // to add extra entries from /etc/protocols if desired - though not sure if that'd actually be useful. + var list = Protocols.list; + var map = Protocols.map; + if (list.length === 0) { + var entry = allocprotoent('tcp', 6, ['TCP']); + list.push(entry); + map['tcp'] = map['6'] = entry; + entry = allocprotoent('udp', 17, ['UDP']); + list.push(entry); + map['udp'] = map['17'] = entry; + } + _setprotoent.index = 0; + }; + + _setprotoent.sig = 'vi'; + + var _getprotobyname = (name) => { + // struct protoent *getprotobyname(const char *); + name = UTF8ToString(name); + _setprotoent(true); + var result = Protocols.map[name]; + return result; + }; + + _getprotobyname.sig = 'pp'; + + var _getprotobynumber = (number) => { + // struct protoent *getprotobynumber(int proto); + _setprotoent(true); + var result = Protocols.map[number]; + return result; + }; + + _getprotobynumber.sig = 'pi'; + + function _js_flock(fd, op) { + if (typeof Module['userSpace'] === 'undefined') { + // In the absence of a real locking facility, + // return success by default as Emscripten does. + return 0; + } + return Module['userSpace'].flock(fd, op); + } + + function _js_open_process( + command, + argsPtr, + argsLength, + descriptorsPtr, + descriptorsLength, + cwdPtr, + cwdLength, + envPtr, + envLength + ) { + if (!command) { + ___errno_location(ERRNO_CODES.EINVAL); + return -1; + } + const cmdstr = UTF8ToString(command); + if (!cmdstr.length) { + ___errno_location(ERRNO_CODES.EINVAL); + return -1; + } + let argsArray = []; + if (argsLength) { + for (var i = 0; i < argsLength; i++) { + const charPointer = argsPtr + i * 4; + argsArray.push(UTF8ToString(HEAPU32[charPointer >> 2])); + } + } + const cwdstr = cwdPtr ? UTF8ToString(cwdPtr) : FS.cwd(); + let envObject = null; + if (envLength) { + envObject = {}; + for (var i = 0; i < envLength; i++) { + const envPointer = envPtr + i * 4; + const envEntry = UTF8ToString(HEAPU32[envPointer >> 2]); + const splitAt = envEntry.indexOf('='); + if (splitAt === -1) { + continue; + } + const key = envEntry.substring(0, splitAt); + const value = envEntry.substring(splitAt + 1); + envObject[key] = value; + } + } + var std = {}; + // Extracts an array of available descriptors that should be dispatched to streams. + // On the C side, the descriptors are expressed as `**int` so we must go read + // each of the `descriptorsLength` `*int` pointers and convert the associated data into + // a JavaScript object { descriptor : { child : fd, parent : fd } }. + for (var i = 0; i < descriptorsLength; i++) { + const descriptorPtr = HEAPU32[(descriptorsPtr + i * 4) >> 2]; + std[HEAPU32[descriptorPtr >> 2]] = { + child: HEAPU32[(descriptorPtr + 4) >> 2], + parent: HEAPU32[(descriptorPtr + 8) >> 2], + }; + // swap parent and child descs until we rebuild PHP 7.4 + if (i === 0) { + HEAPU32[(descriptorPtr + 8) >> 2] = + std[HEAPU32[descriptorPtr >> 2]].parent; + HEAPU32[(descriptorPtr + 4) >> 2] = + std[HEAPU32[descriptorPtr >> 2]].child; + } + } + return Asyncify.handleAsync(async () => { + let cp; + try { + const options = {}; + if (cwdstr !== null) { + options.cwd = cwdstr; + } + if (envObject !== null) { + options.env = envObject; + } + cp = PHPWASM.spawnProcess(cmdstr, argsArray, options); + if (cp instanceof Promise) { + cp = await cp; + } + } catch (e) { + if (e.code === 'SPAWN_UNSUPPORTED') { + ___errno_location(ERRNO_CODES.ENOSYS); + return -1; + } + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) + throw e; + ___errno_location(e.code); + return -1; + } + const ProcInfo = { + pid: cp.pid, + exited: false, + }; + PHPWASM.processTable[ProcInfo.pid] = ProcInfo; + const stdinParentFd = std[0]?.parent, + stdinChildFd = std[0]?.child, + stdoutChildFd = std[1]?.child, + stdoutParentFd = std[1]?.parent, + stderrChildFd = std[2]?.child, + stderrParentFd = std[2]?.parent; + cp.on('exit', function (code) { + for (const fd of [ + // The child process exited. Let's clean up its output streams: + stdoutChildFd, + stderrChildFd, + stdinChildFd, + ]) { + if (FS.streams[fd] && !FS.isClosed(FS.streams[fd])) { + FS.close(FS.streams[fd]); + } + } + ProcInfo.exitCode = code; + ProcInfo.exited = true; + }); + // Pass data from child process's stdout to PHP's end of the stdout pipe. + if (stdoutChildFd) { + const stdoutStream = SYSCALLS.getStreamFromFD(stdoutChildFd); + let stdoutAt = 0; + cp.stdout.on('data', function (data) { + stdoutStream.stream_ops.write( + stdoutStream, + data, + 0, + data.length, + stdoutAt + ); + stdoutAt += data.length; + }); + } + // Pass data from child process's stderr to PHP's end of the stdout pipe. + if (stderrChildFd) { + const stderrStream = SYSCALLS.getStreamFromFD(stderrChildFd); + let stderrAt = 0; + cp.stderr.on('data', function (data) { + stderrStream.stream_ops.write( + stderrStream, + data, + 0, + data.length, + stderrAt + ); + stderrAt += data.length; + }); + } + /** + * Wait until the child process has been spawned. + * Unfortunately there is no Node.js API to check whether + * the process has already been spawned. We can only listen + * to the 'spawn' event and if it has already been spawned, + * listen to the 'exit' event. + */ try { + await new Promise((resolve, reject) => { + /** + * There was no `await` between the `spawnProcess` call + * and the `await` below so the process haven't had a chance + * to run any of the exit-related callbacks yet. + * + * Good. + * + * Let's listen to all the lifecycle events and resolve + * the promise when the process starts or immediately crashes. + */ let resolved = false; + cp.on('spawn', () => { + if (resolved) return; + resolved = true; + resolve(); + }); + cp.on('error', (e) => { + if (resolved) return; + resolved = true; + reject(e); + }); + cp.on('exit', function (code) { + if (resolved) return; + resolved = true; + if (code === 0) { + resolve(); + } else { + reject( + new Error(`Process exited with code ${code}`) + ); + } + }); + /** + * If the process haven't even started after 5 seconds, something + * is wrong. Perhaps we're missing an event listener, or perhaps + * the `spawnProcess` implementation failed to dispatch the relevant + * event. Either way, let's crash to avoid blocking the proc_open() + * call indefinitely. + */ setTimeout(() => { + if (resolved) return; + resolved = true; + reject(new Error('Process timed out')); + }, 5e3); + }); + } catch (e) { + // Process already started. Even if it exited early, PHP still + // needs to know about the pid and clean up the resources. + console.error(e); + return ProcInfo.pid; + } + // Now we want to pass data from the STDIN source supplied by PHP + // to the child process. + if (stdinChildFd) { + // We're in a kernel function used instead of fork(). + // We are the ones responsible for pumping the data from the stdinChildFd + // into the child process. There is no concurrent task operating on the + // piped data or polling the file descriptors, etc. Nothing will ever + // read from the stdinChildFd if we don't do it here. + // Well, let's do it! We'll periodically read from the child end of the + // data pipe and push what we get into the child process. + let stdinStream; + try { + stdinStream = SYSCALLS.getStreamFromFD(stdinChildFd); + } catch (e) { + ___errno_location(ERRNO_CODES.EBADF); + return ProcInfo.pid; + } + if (!stdinStream?.node) { + return ProcInfo.pid; + } + // Pipe the entire stdinStream to cp.stdin + const CHUNK_SIZE = 1024; + const iov = _malloc(16); + // Space for iovec structure + const pnum = _malloc(4); + // Space for number of bytes read + const buffer = _malloc(CHUNK_SIZE); + // Set up iovec structure pointing to our buffer + HEAPU32[iov >> 2] = buffer; + // iov_base + HEAPU32[(iov + 4) >> 2] = CHUNK_SIZE; + // iov_len + function pump() { + try { + while (true) { + if (cp.killed) { + stopPumpingAndCloseStdin(); + return; + } + const result = js_fd_read( + stdinChildFd, + iov, + 1, + pnum, + false + ); + const bytesRead = HEAPU32[pnum >> 2]; + if (result === 0 && bytesRead > 0) { + const wrote = HEAPU8.subarray( + buffer, + buffer + bytesRead + ); + cp.stdin.write(wrote); + } else if (result === 0 && bytesRead === 0) { + // result === 0 and bytesRead === 0 means the file descriptor + // is at EOF. Let's close the stdin stream and clean up. + stopPumpingAndCloseStdin(); + break; + } else if (result === ERRNO_CODES.EAGAIN) { + // The file descriptor is not ready for reading. + // Let's break out of the loop. setInterval will invoke + // this function again soon. + break; + } else { + throw new FS.ErrnoError(result); + } + } + } catch (e) { + if ( + typeof FS == 'undefined' || + !(e.name === 'ErrnoError') + ) { + throw e; + } + ___errno_location(e.errno); + stopPumpingAndCloseStdin(); + } + } + function stopPumpingAndCloseStdin() { + clearInterval(interval); + if (!cp.stdin.closed) { + cp.stdin.end(); + } + _wasm_free(buffer); + _wasm_free(iov); + _wasm_free(pnum); + } + // pump() can never alter the result of this function. + // Even when it fails, we still return the pid. + // Why? + // Because the process already started. We wouldn't backtrack + // with fork(), we won't backtrack here. Let's give PHP the pid, + // and let it think it's the parent process. It will clean up the + // resources as needed. + // stdin may be non-blocking – let's check for updates periodically. + // If we exhaust it at any point, pump() will self-terminate. + // Note handling any failures, closing the descriptor, etc. will not + // happen synchronously when PHP calls fclose($pipes[0]) or proc_close(). + // It will all happen asynchronously on the next tick. It seems off, + // but there doesn't seem to be a better way: cp.stdin.write() and + // cp.stdin.end() are both async APIs and they both accept onCompleted + // callbacks. + const interval = setInterval(pump, 20); + pump(); + } + return ProcInfo.pid; + }); + } + + function _js_release_file_locks() { + if (typeof Module['userSpace'] === 'undefined') { + return; + } + return Module['userSpace'].js_release_file_locks(); + } + + var arraySum = (array, index) => { + var sum = 0; + for (var i = 0; i <= index; sum += array[i++]) {} + return sum; + }; + + var MONTH_DAYS_LEAP = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + + var MONTH_DAYS_REGULAR = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + + var addDays = (date, days) => { + var newDate = new Date(date.getTime()); + while (days > 0) { + var leap = isLeapYear(newDate.getFullYear()); + var currentMonth = newDate.getMonth(); + var daysInCurrentMonth = ( + leap ? MONTH_DAYS_LEAP : MONTH_DAYS_REGULAR + )[currentMonth]; + if (days > daysInCurrentMonth - newDate.getDate()) { + // we spill over to next month + days -= daysInCurrentMonth - newDate.getDate() + 1; + newDate.setDate(1); + if (currentMonth < 11) { + newDate.setMonth(currentMonth + 1); + } else { + newDate.setMonth(0); + newDate.setFullYear(newDate.getFullYear() + 1); + } + } else { + // we stay in current month + newDate.setDate(newDate.getDate() + days); + return newDate; + } + } + return newDate; + }; + + var _strptime = (buf, format, tm) => { + // char *strptime(const char *restrict buf, const char *restrict format, struct tm *restrict tm); + // http://pubs.opengroup.org/onlinepubs/009695399/functions/strptime.html + var pattern = UTF8ToString(format); + // escape special characters + // TODO: not sure we really need to escape all of these in JS regexps + var SPECIAL_CHARS = '\\!@#$^&*()+=-[]/{}|:<>?,.'; + for (var i = 0, ii = SPECIAL_CHARS.length; i < ii; ++i) { + pattern = pattern.replace( + new RegExp('\\' + SPECIAL_CHARS[i], 'g'), + '\\' + SPECIAL_CHARS[i] + ); + } + // reduce number of matchers + var EQUIVALENT_MATCHERS = { + A: '%a', + B: '%b', + c: '%a %b %d %H:%M:%S %Y', + D: '%m\\/%d\\/%y', + e: '%d', + F: '%Y-%m-%d', + h: '%b', + R: '%H\\:%M', + r: '%I\\:%M\\:%S\\s%p', + T: '%H\\:%M\\:%S', + x: '%m\\/%d\\/(?:%y|%Y)', + X: '%H\\:%M\\:%S', + }; + // TODO: take care of locale + var DATE_PATTERNS = { + /* weekday name */ a: '(?:Sun(?:day)?)|(?:Mon(?:day)?)|(?:Tue(?:sday)?)|(?:Wed(?:nesday)?)|(?:Thu(?:rsday)?)|(?:Fri(?:day)?)|(?:Sat(?:urday)?)', + /* month name */ b: '(?:Jan(?:uary)?)|(?:Feb(?:ruary)?)|(?:Mar(?:ch)?)|(?:Apr(?:il)?)|May|(?:Jun(?:e)?)|(?:Jul(?:y)?)|(?:Aug(?:ust)?)|(?:Sep(?:tember)?)|(?:Oct(?:ober)?)|(?:Nov(?:ember)?)|(?:Dec(?:ember)?)', + /* century */ C: '\\d\\d', + /* day of month */ d: '0[1-9]|[1-9](?!\\d)|1\\d|2\\d|30|31', + /* hour (24hr) */ H: '\\d(?!\\d)|[0,1]\\d|20|21|22|23', + /* hour (12hr) */ I: '\\d(?!\\d)|0\\d|10|11|12', + /* day of year */ j: '00[1-9]|0?[1-9](?!\\d)|0?[1-9]\\d(?!\\d)|[1,2]\\d\\d|3[0-6]\\d', + /* month */ m: '0[1-9]|[1-9](?!\\d)|10|11|12', + /* minutes */ M: '0\\d|\\d(?!\\d)|[1-5]\\d', + /* whitespace */ n: ' ', + /* AM/PM */ p: 'AM|am|PM|pm|A\\.M\\.|a\\.m\\.|P\\.M\\.|p\\.m\\.', + /* seconds */ S: '0\\d|\\d(?!\\d)|[1-5]\\d|60', + /* week number */ U: '0\\d|\\d(?!\\d)|[1-4]\\d|50|51|52|53', + /* week number */ W: '0\\d|\\d(?!\\d)|[1-4]\\d|50|51|52|53', + /* weekday number */ w: '[0-6]', + /* 2-digit year */ y: '\\d\\d', + /* 4-digit year */ Y: '\\d\\d\\d\\d', + /* whitespace */ t: ' ', + /* time zone */ z: 'Z|(?:[\\+\\-]\\d\\d:?(?:\\d\\d)?)', + }; + var MONTH_NUMBERS = { + JAN: 0, + FEB: 1, + MAR: 2, + APR: 3, + MAY: 4, + JUN: 5, + JUL: 6, + AUG: 7, + SEP: 8, + OCT: 9, + NOV: 10, + DEC: 11, + }; + var DAY_NUMBERS_SUN_FIRST = { + SUN: 0, + MON: 1, + TUE: 2, + WED: 3, + THU: 4, + FRI: 5, + SAT: 6, + }; + var DAY_NUMBERS_MON_FIRST = { + MON: 0, + TUE: 1, + WED: 2, + THU: 3, + FRI: 4, + SAT: 5, + SUN: 6, + }; + var capture = []; + var pattern_out = pattern + .replace(/%(.)/g, (m, c) => EQUIVALENT_MATCHERS[c] || m) + .replace(/%(.)/g, (_, c) => { + let pat = DATE_PATTERNS[c]; + if (pat) { + capture.push(c); + return `(${pat})`; + } else { + return c; + } + }) + .replace( + // any number of space or tab characters match zero or more spaces + /\s+/g, + '\\s*' + ); + var matches = new RegExp('^' + pattern_out, 'i').exec( + UTF8ToString(buf) + ); + function initDate() { + function fixup(value, min, max) { + return typeof value != 'number' || isNaN(value) + ? min + : value >= min + ? value <= max + ? value + : max + : min; + } + return { + year: fixup(HEAP32[(tm + 20) >> 2] + 1900, 1970, 9999), + month: fixup(HEAP32[(tm + 16) >> 2], 0, 11), + day: fixup(HEAP32[(tm + 12) >> 2], 1, 31), + hour: fixup(HEAP32[(tm + 8) >> 2], 0, 23), + min: fixup(HEAP32[(tm + 4) >> 2], 0, 59), + sec: fixup(HEAP32[tm >> 2], 0, 59), + gmtoff: 0, + }; + } + if (matches) { + var date = initDate(); + var value; + var getMatch = (symbol) => { + var pos = capture.indexOf(symbol); + // check if symbol appears in regexp + if (pos >= 0) { + // return matched value or null (falsy!) for non-matches + return matches[pos + 1]; + } + return; + }; + // seconds + if ((value = getMatch('S'))) { + date.sec = Number(value); + } + // minutes + if ((value = getMatch('M'))) { + date.min = Number(value); + } + // hours + if ((value = getMatch('H'))) { + // 24h clock + date.hour = Number(value); + } else if ((value = getMatch('I'))) { + // AM/PM clock + var hour = Number(value); + if ((value = getMatch('p'))) { + hour += value.toUpperCase()[0] === 'P' ? 12 : 0; + } + date.hour = hour; + } + // year + if ((value = getMatch('Y'))) { + // parse from four-digit year + date.year = Number(value); + } else if ((value = getMatch('y'))) { + // parse from two-digit year... + var year = Number(value); + if ((value = getMatch('C'))) { + // ...and century + year += Number(value) * 100; + } else { + // ...and rule-of-thumb + year += year < 69 ? 2e3 : 1900; + } + date.year = year; + } + // month + if ((value = getMatch('m'))) { + // parse from month number + date.month = Number(value) - 1; + } else if ((value = getMatch('b'))) { + // parse from month name + date.month = + MONTH_NUMBERS[value.substring(0, 3).toUpperCase()] || 0; + } + // day + if ((value = getMatch('d'))) { + // get day of month directly + date.day = Number(value); + } else if ((value = getMatch('j'))) { + // get day of month from day of year ... + var day = Number(value); + var leapYear = isLeapYear(date.year); + for (var month = 0; month < 12; ++month) { + var daysUntilMonth = arraySum( + leapYear ? MONTH_DAYS_LEAP : MONTH_DAYS_REGULAR, + month - 1 + ); + if ( + day <= + daysUntilMonth + + (leapYear ? MONTH_DAYS_LEAP : MONTH_DAYS_REGULAR)[ + month + ] + ) { + date.day = day - daysUntilMonth; + } + } + } else if ((value = getMatch('a'))) { + // get day of month from weekday ... + var weekDay = value.substring(0, 3).toUpperCase(); + if ((value = getMatch('U'))) { + // ... and week number (Sunday being first day of week) + // Week number of the year (Sunday as the first day of the week) as a decimal number [00,53]. + // All days in a new year preceding the first Sunday are considered to be in week 0. + var weekDayNumber = DAY_NUMBERS_SUN_FIRST[weekDay]; + var weekNumber = Number(value); + // January 1st + var janFirst = new Date(date.year, 0, 1); + var endDate; + if (janFirst.getDay() === 0) { + // Jan 1st is a Sunday, and, hence in the 1st CW + endDate = addDays( + janFirst, + weekDayNumber + 7 * (weekNumber - 1) + ); + } else { + // Jan 1st is not a Sunday, and, hence still in the 0th CW + endDate = addDays( + janFirst, + 7 - + janFirst.getDay() + + weekDayNumber + + 7 * (weekNumber - 1) + ); + } + date.day = endDate.getDate(); + date.month = endDate.getMonth(); + } else if ((value = getMatch('W'))) { + // ... and week number (Monday being first day of week) + // Week number of the year (Monday as the first day of the week) as a decimal number [00,53]. + // All days in a new year preceding the first Monday are considered to be in week 0. + var weekDayNumber = DAY_NUMBERS_MON_FIRST[weekDay]; + var weekNumber = Number(value); + // January 1st + var janFirst = new Date(date.year, 0, 1); + var endDate; + if (janFirst.getDay() === 1) { + // Jan 1st is a Monday, and, hence in the 1st CW + endDate = addDays( + janFirst, + weekDayNumber + 7 * (weekNumber - 1) + ); + } else { + // Jan 1st is not a Monday, and, hence still in the 0th CW + endDate = addDays( + janFirst, + 7 - + janFirst.getDay() + + 1 + + weekDayNumber + + 7 * (weekNumber - 1) + ); + } + date.day = endDate.getDate(); + date.month = endDate.getMonth(); + } + } + // time zone + if ((value = getMatch('z'))) { + // GMT offset as either 'Z' or +-HH:MM or +-HH or +-HHMM + if (value.toLowerCase() === 'z') { + date.gmtoff = 0; + } else { + var match = value.match(/^((?:\-|\+)\d\d):?(\d\d)?/); + date.gmtoff = match[1] * 3600; + if (match[2]) { + date.gmtoff += + date.gmtoff > 0 ? match[2] * 60 : -match[2] * 60; + } + } + } + /* + tm_sec int seconds after the minute 0-61* + tm_min int minutes after the hour 0-59 + tm_hour int hours since midnight 0-23 + tm_mday int day of the month 1-31 + tm_mon int months since January 0-11 + tm_year int years since 1900 + tm_wday int days since Sunday 0-6 + tm_yday int days since January 1 0-365 + tm_isdst int Daylight Saving Time flag + tm_gmtoff long offset from GMT (seconds) + */ var fullDate = new Date( + date.year, + date.month, + date.day, + date.hour, + date.min, + date.sec, + 0 + ); + HEAP32[tm >> 2] = fullDate.getSeconds(); + HEAP32[(tm + 4) >> 2] = fullDate.getMinutes(); + HEAP32[(tm + 8) >> 2] = fullDate.getHours(); + HEAP32[(tm + 12) >> 2] = fullDate.getDate(); + HEAP32[(tm + 16) >> 2] = fullDate.getMonth(); + HEAP32[(tm + 20) >> 2] = fullDate.getFullYear() - 1900; + HEAP32[(tm + 24) >> 2] = fullDate.getDay(); + HEAP32[(tm + 28) >> 2] = + arraySum( + isLeapYear(fullDate.getFullYear()) + ? MONTH_DAYS_LEAP + : MONTH_DAYS_REGULAR, + fullDate.getMonth() - 1 + ) + + fullDate.getDate() - + 1; + HEAP32[(tm + 32) >> 2] = 0; + HEAP32[(tm + 36) >> 2] = date.gmtoff; + // we need to convert the matched sequence into an integer array to take care of UTF-8 characters > 0x7F + // TODO: not sure that intArrayFromString handles all unicode characters correctly + return buf + lengthBytesUTF8(matches[0]); + } + return 0; + }; + + _strptime.sig = 'pppp'; + + function _wasm_close(socketd) { + return PHPWASM.shutdownSocket(socketd, 2); + } + + function _wasm_setsockopt( + socketd, + level, + optionName, + optionValuePtr, + optionLen + ) { + const optionValue = HEAPU8[optionValuePtr]; + const SOL_SOCKET = 1; + const SO_KEEPALIVE = 9; + const SO_RCVTIMEO = 66; + const SO_SNDTIMEO = 67; + const IPPROTO_TCP = 6; + const TCP_NODELAY = 1; + // Options that we can forward to the WebSocket proxy + const isForwardable = + (level === SOL_SOCKET && optionName === SO_KEEPALIVE) || + (level === IPPROTO_TCP && optionName === TCP_NODELAY); + // Options that we acknowledge but don't actually implement + // (WebSocket connections handle timeouts differently) + const isIgnorable = + level === SOL_SOCKET && + (optionName === SO_RCVTIMEO || optionName === SO_SNDTIMEO); + if (!isForwardable && !isIgnorable) { + console.warn( + `Unsupported socket option: ${level}, ${optionName}, ${optionValue}` + ); + return -1; + } + // For ignorable options, just return success + if (isIgnorable) { + return 0; + } + const ws = PHPWASM.getAllWebSockets(socketd)[0]; + if (!ws) { + return -1; + } + ws.setSocketOpt(level, optionName, optionValuePtr); + return 0; + } + + var runAndAbortIfError = (func) => { + try { + return func(); + } catch (e) { + abort(e); + } + }; + + var Asyncify = { + instrumentWasmImports(imports) { + var importPattern = + /^(invoke_i|invoke_ii|invoke_iii|invoke_iiii|invoke_iiiii|invoke_iiiiii|invoke_iiiiiii|invoke_iiiiiiii|invoke_iiiiiiiii|invoke_iiiiiiiiii|invoke_v|invoke_vi|invoke_vii|invoke_viidii|invoke_viii|invoke_viiii|invoke_viiiii|invoke_viiiiii|invoke_viiiiiii|invoke_viiiiiiiii|invoke_i|invoke_ii|invoke_iii|invoke_iiii|invoke_iiiii|invoke_iiiiii|invoke_iiiiiii|invoke_iiiiiiii|invoke_iiiiiiiiii|invoke_iij|invoke_iiji|invoke_iiij|invoke_iijii|invoke_iijiji|invoke_jii|invoke_jiii|invoke_viijii|invoke_vji|js_open_process|_js_open_process|_asyncjs__js_open_process|js_popen_to_file|_js_popen_to_file|_asyncjs__js_popen_to_file|__syscall_fcntl64|___syscall_fcntl64|_asyncjs___syscall_fcntl64|js_release_file_locks|_js_release_file_locks|_async_js_release_file_locks|js_flock|_js_flock|_async_js_flock|js_fd_read|_js_fd_read|fd_close|_fd_close|_asyncjs__fd_close|close|_close|js_module_onMessage|zend_hash_str_find|_js_module_onMessage|_asyncjs__js_module_onMessage|js_waitpid|_js_waitpid|_asyncjs__js_waitpid|wasm_poll_socket|_wasm_poll_socket|_asyncjs__wasm_poll_socket|_wasm_shutdown|_asyncjs__wasm_shutdown|recv|_recv|setsockopt|_setsockopt|wasm_connect|_wasm_connect|__asyncjs__.*)$/; + for (let [x, original] of Object.entries(imports)) { + if (typeof original == 'function') { + let isAsyncifyImport = + original.isAsync || importPattern.test(x); + } + } + }, + instrumentFunction(original) { + var wrapper = (...args) => { + Asyncify.exportCallStack.push(original); + try { + return original(...args); + } finally { + if (!ABORT) { + var top = Asyncify.exportCallStack.pop(); + Asyncify.maybeStopUnwind(); + } + } + }; + Asyncify.funcWrappers.set(original, wrapper); + wrapper.orig = original; + return wrapper; + }, + instrumentWasmExports(exports) { + var ret = {}; + for (let [x, original] of Object.entries(exports)) { + if (typeof original == 'function') { + var wrapper = Asyncify.instrumentFunction(original); + ret[x] = wrapper; + } else { + ret[x] = original; + } + } + return ret; + }, + State: { + Normal: 0, + Unwinding: 1, + Rewinding: 2, + Disabled: 3, + }, + state: 0, + StackSize: 4096, + currData: null, + handleSleepReturnValue: 0, + exportCallStack: [], + callstackFuncToId: new Map(), + callStackIdToFunc: new Map(), + funcWrappers: new Map(), + callStackId: 0, + asyncPromiseHandlers: null, + sleepCallbacks: [], + getCallStackId(func) { + if (!Asyncify.callstackFuncToId.has(func)) { + var id = Asyncify.callStackId++; + Asyncify.callstackFuncToId.set(func, id); + Asyncify.callStackIdToFunc.set(id, func); + } + return Asyncify.callstackFuncToId.get(func); + }, + maybeStopUnwind() { + if ( + Asyncify.currData && + Asyncify.state === Asyncify.State.Unwinding && + Asyncify.exportCallStack.length === 0 + ) { + // We just finished unwinding. + // Be sure to set the state before calling any other functions to avoid + // possible infinite recursion here (For example in debug pthread builds + // the dbg() function itself can call back into WebAssembly to get the + // current pthread_self() pointer). + Asyncify.state = Asyncify.State.Normal; + runtimeKeepalivePush(); + // Keep the runtime alive so that a re-wind can be done later. + runAndAbortIfError(_asyncify_stop_unwind); + if (typeof Fibers != 'undefined') { + Fibers.trampoline(); + } + } + }, + whenDone() { + return new Promise((resolve, reject) => { + Asyncify.asyncPromiseHandlers = { + resolve, + reject, + }; + }); + }, + allocateData() { + // An asyncify data structure has three fields: + // 0 current stack pos + // 4 max stack pos + // 8 id of function at bottom of the call stack (callStackIdToFunc[id] == wasm func) + // The Asyncify ABI only interprets the first two fields, the rest is for the runtime. + // We also embed a stack in the same memory region here, right next to the structure. + // This struct is also defined as asyncify_data_t in emscripten/fiber.h + var ptr = _malloc(12 + Asyncify.StackSize); + Asyncify.setDataHeader(ptr, ptr + 12, Asyncify.StackSize); + Asyncify.setDataRewindFunc(ptr); + return ptr; + }, + setDataHeader(ptr, stack, stackSize) { + HEAPU32[ptr >> 2] = stack; + HEAPU32[(ptr + 4) >> 2] = stack + stackSize; + }, + setDataRewindFunc(ptr) { + var bottomOfCallStack = Asyncify.exportCallStack[0]; + var rewindId = Asyncify.getCallStackId(bottomOfCallStack); + HEAP32[(ptr + 8) >> 2] = rewindId; + }, + getDataRewindFunc(ptr) { + var id = HEAP32[(ptr + 8) >> 2]; + var func = Asyncify.callStackIdToFunc.get(id); + return func; + }, + doRewind(ptr) { + var original = Asyncify.getDataRewindFunc(ptr); + var func = Asyncify.funcWrappers.get(original); + // Once we have rewound and the stack we no longer need to artificially + // keep the runtime alive. + runtimeKeepalivePop(); + return func(); + }, + handleSleep(startAsync) { + if (ABORT) return; + if (Asyncify.state === Asyncify.State.Normal) { + // Prepare to sleep. Call startAsync, and see what happens: + // if the code decided to call our callback synchronously, + // then no async operation was in fact begun, and we don't + // need to do anything. + var reachedCallback = false; + var reachedAfterCallback = false; + startAsync((handleSleepReturnValue = 0) => { + if (ABORT) return; + Asyncify.handleSleepReturnValue = handleSleepReturnValue; + reachedCallback = true; + if (!reachedAfterCallback) { + // We are happening synchronously, so no need for async. + return; + } + Asyncify.state = Asyncify.State.Rewinding; + runAndAbortIfError(() => + _asyncify_start_rewind(Asyncify.currData) + ); + if (typeof MainLoop != 'undefined' && MainLoop.func) { + MainLoop.resume(); + } + var asyncWasmReturnValue, + isError = false; + try { + asyncWasmReturnValue = Asyncify.doRewind( + Asyncify.currData + ); + } catch (err) { + asyncWasmReturnValue = err; + isError = true; + } + // Track whether the return value was handled by any promise handlers. + var handled = false; + if (!Asyncify.currData) { + // All asynchronous execution has finished. + // `asyncWasmReturnValue` now contains the final + // return value of the exported async WASM function. + // Note: `asyncWasmReturnValue` is distinct from + // `Asyncify.handleSleepReturnValue`. + // `Asyncify.handleSleepReturnValue` contains the return + // value of the last C function to have executed + // `Asyncify.handleSleep()`, where as `asyncWasmReturnValue` + // contains the return value of the exported WASM function + // that may have called C functions that + // call `Asyncify.handleSleep()`. + var asyncPromiseHandlers = + Asyncify.asyncPromiseHandlers; + if (asyncPromiseHandlers) { + Asyncify.asyncPromiseHandlers = null; + (isError + ? asyncPromiseHandlers.reject + : asyncPromiseHandlers.resolve)( + asyncWasmReturnValue + ); + handled = true; + } + } + if (isError && !handled) { + // If there was an error and it was not handled by now, we have no choice but to + // rethrow that error into the global scope where it can be caught only by + // `onerror` or `onunhandledpromiserejection`. + throw asyncWasmReturnValue; + } + }); + reachedAfterCallback = true; + if (!reachedCallback) { + // A true async operation was begun; start a sleep. + Asyncify.state = Asyncify.State.Unwinding; + // TODO: reuse, don't alloc/free every sleep + if (!Asyncify._cachedData) { + Asyncify._cachedData = Asyncify.allocateData(); + } else { + Asyncify.setDataHeader( + Asyncify._cachedData, + Asyncify._cachedData + 12, + Asyncify.StackSize + ); + Asyncify.setDataRewindFunc(Asyncify._cachedData); + } + Asyncify.currData = Asyncify._cachedData; + if (typeof MainLoop != 'undefined' && MainLoop.func) { + MainLoop.pause(); + } + runAndAbortIfError(() => + _asyncify_start_unwind(Asyncify.currData) + ); + } + } else if (Asyncify.state === Asyncify.State.Rewinding) { + // Stop a resume. + Asyncify.state = Asyncify.State.Normal; + runAndAbortIfError(_asyncify_stop_rewind); + Asyncify.currData = null; + // Call all sleep callbacks now that the sleep-resume is all done. + Asyncify.sleepCallbacks.forEach(callUserCallback); + } else { + abort(`invalid state: ${Asyncify.state}`); + } + return Asyncify.handleSleepReturnValue; + }, + handleAsync: (startAsync) => + Asyncify.handleSleep((wakeUp) => { + // TODO: add error handling as a second param when handleSleep implements it. + startAsync().then(wakeUp); + }), + }; + + var getCFunc = (ident) => { + var func = Module['_' + ident]; + // closure exported function + return func; + }; + + var writeArrayToMemory = (array, buffer) => { + HEAP8.set(array, buffer); + }; + + /** + * @param {string|null=} returnType + * @param {Array=} argTypes + * @param {Array=} args + * @param {Object=} opts + */ var ccall = (ident, returnType, argTypes, args, opts) => { + // For fast lookup of conversion functions + var toC = { + string: (str) => { + var ret = 0; + if (str !== null && str !== undefined && str !== 0) { + // null string + ret = stringToUTF8OnStack(str); + } + return ret; + }, + array: (arr) => { + var ret = stackAlloc(arr.length); + writeArrayToMemory(arr, ret); + return ret; + }, + }; + function convertReturnValue(ret) { + if (returnType === 'string') { + return UTF8ToString(ret); + } + if (returnType === 'boolean') return Boolean(ret); + return ret; + } + var func = getCFunc(ident); + var cArgs = []; + var stack = 0; + if (args) { + for (var i = 0; i < args.length; i++) { + var converter = toC[argTypes[i]]; + if (converter) { + if (stack === 0) stack = stackSave(); + cArgs[i] = converter(args[i]); + } else { + cArgs[i] = args[i]; + } + } + } + // Data for a previous async operation that was in flight before us. + var previousAsync = Asyncify.currData; + var ret = func(...cArgs); + function onDone(ret) { + runtimeKeepalivePop(); + if (stack !== 0) stackRestore(stack); + return convertReturnValue(ret); + } + var asyncMode = opts?.async; + // Keep the runtime alive through all calls. Note that this call might not be + // async, but for simplicity we push and pop in all calls. + runtimeKeepalivePush(); + if (Asyncify.currData != previousAsync) { + // This is a new async operation. The wasm is paused and has unwound its stack. + // We need to return a Promise that resolves the return value + // once the stack is rewound and execution finishes. + return Asyncify.whenDone().then(onDone); + } + ret = onDone(ret); + // If this is an async ccall, ensure we return a promise + if (asyncMode) return Promise.resolve(ret); + return ret; + }; + + var FS_createPath = (...args) => FS.createPath(...args); + + var FS_unlink = (...args) => FS.unlink(...args); + + var FS_createLazyFile = (...args) => FS.createLazyFile(...args); + + var FS_createDevice = (...args) => FS.createDevice(...args); + + registerWasmPlugin(); + + FS.createPreloadedFile = FS_createPreloadedFile; + + FS.preloadFile = FS_preloadFile; + + FS.staticInit(); + + if (ENVIRONMENT_IS_NODE) { + NODEFS.staticInit(); + } + + PHPWASM.init(); + + // End JS library code + // include: postlibrary.js + // This file is included after the automatically-generated JS library code + // but before the wasm module is created. + { + // Begin ATMODULES hooks + if (Module['preloadPlugins']) preloadPlugins = Module['preloadPlugins']; + if (Module['noExitRuntime']) noExitRuntime = Module['noExitRuntime']; + if (Module['print']) out = Module['print']; + if (Module['printErr']) err = Module['printErr']; + if (Module['dynamicLibraries']) + dynamicLibraries = Module['dynamicLibraries']; + if (Module['wasmBinary']) wasmBinary = Module['wasmBinary']; + // End ATMODULES hooks + if (Module['arguments']) arguments_ = Module['arguments']; + if (Module['thisProgram']) thisProgram = Module['thisProgram']; + if (Module['quit']) quit_ = Module['quit']; + if (Module['preInit']) { + if (typeof Module['preInit'] == 'function') + Module['preInit'] = [Module['preInit']]; + while (Module['preInit'].length > 0) { + Module['preInit'].shift()(); + } + } + } + + // Begin runtime exports + Module['wasmExports'] = wasmExports; + + Module['addRunDependency'] = addRunDependency; + + Module['removeRunDependency'] = removeRunDependency; + + Module['ccall'] = ccall; + + Module['UTF8ToString'] = UTF8ToString; + + Module['stringToUTF8'] = stringToUTF8; + + Module['lengthBytesUTF8'] = lengthBytesUTF8; + + Module['FS_preloadFile'] = FS_preloadFile; + + Module['FS_unlink'] = FS_unlink; + + Module['FS_createPath'] = FS_createPath; + + Module['FS_createDevice'] = FS_createDevice; + + Module['FS_createDataFile'] = FS_createDataFile; + + Module['FS_createLazyFile'] = FS_createLazyFile; + + Module['PROXYFS'] = PROXYFS; + + // End runtime exports + // Begin JS library exports + Module['UTF8ToString'] = UTF8ToString; + + Module['lengthBytesUTF8'] = lengthBytesUTF8; + + Module['stringToUTF8'] = stringToUTF8; + + Module['FS'] = FS; + + Module['_exit'] = _exit; + + Module['_emscripten_sleep'] = _emscripten_sleep; + + // End JS library exports + // end include: postlibrary.js + var ASM_CONSTS = {}; + + function js_popen_to_file(command, mode, exitCodePtr) { + const returnCallback = (resolver) => Asyncify.handleSleep(resolver); + if (!command) return 1; + const cmdstr = UTF8ToString(command); + if (!cmdstr.length) return 0; + const modestr = UTF8ToString(mode); + if (!modestr.length) return 0; + if (modestr === 'w') { + console.error('popen($cmd, "w") is not implemented yet'); + } + return returnCallback(async (wakeUp) => { + let cp; + try { + cp = PHPWASM.spawnProcess(cmdstr, []); + if (cp instanceof Promise) { + cp = await cp; + } + } catch (e) { + console.error(e); + if (e.code === 'SPAWN_UNSUPPORTED') { + return 1; + } + throw e; + } + const outByteArrays = []; + cp.stdout.on('data', function (data) { + outByteArrays.push(data); + }); + const outputPath = '/tmp/popen_output'; + cp.on('exit', function (exitCode) { + const outBytes = new Uint8Array( + outByteArrays.reduce((acc, curr) => acc + curr.length, 0) + ); + let offset = 0; + for (const byteArray of outByteArrays) { + outBytes.set(byteArray, offset); + offset += byteArray.length; + } + FS.writeFile(outputPath, outBytes); + HEAPU8[exitCodePtr] = exitCode; + wakeUp(allocateUTF8OnStack(outputPath)); + }); + }); + } + + js_popen_to_file.sig = 'iiii'; + + function wasm_poll_socket(socketd, events, timeout) { + const returnCallback = (resolver) => Asyncify.handleSleep(resolver); + const POLLIN = 1; + const POLLPRI = 2; + const POLLOUT = 4; + const POLLERR = 8; + const POLLHUP = 16; + const POLLNVAL = 32; + return returnCallback((wakeUp) => { + const polls = []; + const stream = FS.getStream(socketd); + if (FS.isSocket(stream?.node.mode)) { + const sock = getSocketFromFD(socketd); + if (!sock) { + wakeUp(0); + return; + } + const lookingFor = new Set(); + if (events & POLLIN || events & POLLPRI) { + if (sock.server) { + for (const client of sock.pending) { + if ((client.recv_queue || []).length > 0) { + wakeUp(1); + return; + } + } + } else if ((sock.recv_queue || []).length > 0) { + wakeUp(1); + return; + } + } + const webSockets = PHPWASM.getAllWebSockets(sock); + if (!webSockets.length) { + wakeUp(0); + return; + } + for (const ws of webSockets) { + if (events & POLLIN || events & POLLPRI) { + polls.push(PHPWASM.awaitData(ws)); + lookingFor.add('POLLIN'); + } + if (events & POLLOUT) { + polls.push(PHPWASM.awaitConnection(ws)); + lookingFor.add('POLLOUT'); + } + if ( + events & POLLHUP || + events & POLLIN || + events & POLLOUT || + events & POLLERR + ) { + polls.push(PHPWASM.awaitClose(ws)); + lookingFor.add('POLLHUP'); + } + if (events & POLLERR || events & POLLNVAL) { + polls.push(PHPWASM.awaitError(ws)); + lookingFor.add('POLLERR'); + } + } + } else if (stream?.stream_ops?.poll) { + let interrupted = false; + async function poll() { + try { + while (true) { + var mask = POLLNVAL; + mask = SYSCALLS.DEFAULT_POLLMASK; + if (FS.isClosed(stream)) { + return ERRNO_CODES.EBADF; + } + if (stream.stream_ops?.poll) { + mask = stream.stream_ops.poll(stream, -1); + } + mask &= events | POLLERR | POLLHUP; + if (mask) { + return mask; + } + if (interrupted) { + return ERRNO_CODES.ETIMEDOUT; + } + await new Promise((resolve) => + setTimeout(resolve, 10) + ); + } + } catch (e) { + if ( + typeof FS == 'undefined' || + !(e.name === 'ErrnoError') + ) + throw e; + return -e.errno; + } + } + polls.push([ + poll(), + () => { + interrupted = true; + }, + ]); + } else { + setTimeout(function () { + wakeUp(1); + }, timeout); + return; + } + if (polls.length === 0) { + console.warn( + 'Unsupported poll event ' + + events + + ', defaulting to setTimeout().' + ); + setTimeout(function () { + wakeUp(0); + }, timeout); + return; + } + const promises = polls.map(([promise]) => promise); + const clearPolling = () => polls.forEach(([, clear]) => clear()); + let awaken = false; + let timeoutId; + Promise.race(promises).then(function (results) { + if (!awaken) { + awaken = true; + wakeUp(1); + if (timeoutId) { + clearTimeout(timeoutId); + } + clearPolling(); + } + }); + if (timeout !== -1) { + timeoutId = setTimeout(function () { + if (!awaken) { + awaken = true; + wakeUp(0); + clearPolling(); + } + }, timeout); + } + }); + } + + wasm_poll_socket.sig = 'iiii'; + + function js_fd_read(fd, iov, iovcnt, pnum) { + const returnCallback = (resolver) => Asyncify.handleSleep(resolver); + const pollAsync = arguments[4] === undefined ? true : !!arguments[4]; + if ( + Asyncify?.State?.Normal === undefined || + Asyncify?.state === Asyncify?.State?.Normal + ) { + var stream; + try { + stream = SYSCALLS.getStreamFromFD(fd); + HEAPU32[pnum >> 2] = doReadv(stream, iov, iovcnt); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) { + throw e; + } + if ( + e.errno !== ERRNO_CODES.EWOULDBLOCK && + e.errno !== ERRNO_CODES.EAGAIN + ) { + return e.errno; + } + const nonBlocking = stream.flags & PHPWASM.O_NONBLOCK; + if (nonBlocking) { + return e.errno; + } + } + } + if (false === pollAsync) { + return ERRNO_CODES.EWOULDBLOCK; + } + return returnCallback(async (wakeUp) => { + var retries = 0; + var interval = 50; + var timeout = 5e3; + var maxRetries = timeout / interval; + while (true) { + var returnCode; + var stream; + let num; + try { + stream = SYSCALLS.getStreamFromFD(fd); + num = doReadv(stream, iov, iovcnt); + returnCode = 0; + } catch (e) { + if ( + typeof FS == 'undefined' || + !(e.name === 'ErrnoError') + ) { + console.error(e); + throw e; + } + returnCode = e.errno; + } + if (returnCode === 0) { + HEAPU32[pnum >> 2] = num; + return wakeUp(0); + } + if ( + ++retries > maxRetries || + !stream || + FS.isClosed(stream) || + returnCode !== ERRNO_CODES.EWOULDBLOCK || + ('pipe' in stream.node && stream.node.pipe.refcnt < 2) + ) { + HEAPU32[pnum >> 2] = num; + return wakeUp(returnCode); + } + await new Promise((resolve) => setTimeout(resolve, interval)); + } + }); + } + + js_fd_read.sig = 'iiiii'; + + function __asyncjs__js_module_onMessage(data, response_buffer) { + return Asyncify.handleAsync(async () => { + if (Module['onMessage']) { + const dataStr = UTF8ToString(data); + return Module['onMessage'](dataStr) + .then((response) => { + const responseBytes = + typeof response === 'string' + ? new TextEncoder().encode(response) + : response; + const responseSize = responseBytes.byteLength; + const responsePtr = _malloc(responseSize + 1); + HEAPU8.set(responseBytes, responsePtr); + HEAPU8[responsePtr + responseSize] = 0; + HEAPU8[response_buffer] = responsePtr; + HEAPU8[response_buffer + 1] = responsePtr >> 8; + HEAPU8[response_buffer + 2] = responsePtr >> 16; + HEAPU8[response_buffer + 3] = responsePtr >> 24; + return responseSize; + }) + .catch((e) => { + console.error(e); + return -1; + }); + } + }); + } + + __asyncjs__js_module_onMessage.sig = 'iii'; + + // Imports from the Wasm binary. + var _malloc, + _free, + ___errno_location, + _calloc, + _wasm_sleep, + _ntohs, + _htons, + _htonl, + _wasm_read, + _fflush, + _flock, + _wasm_popen, + _wasm_php_exec, + _php_pollfd_for, + ___wrap_usleep, + ___wrap_select, + _wasm_set_sapi_name, + _wasm_set_phpini_path, + _wasm_add_cli_arg, + _run_cli, + _wasm_add_SERVER_entry, + _wasm_add_ENV_entry, + _wasm_set_query_string, + _wasm_set_path_translated, + _wasm_set_skip_shebang, + _wasm_set_request_uri, + _wasm_set_request_method, + _wasm_set_request_host, + _wasm_set_content_type, + _wasm_set_request_body, + _wasm_set_content_length, + _wasm_set_cookies, + _wasm_set_request_port, + _wasm_sapi_request_shutdown, + _wasm_sapi_handle_request, + _php_wasm_init, + _wasm_free, + _wasm_get_end_offset, + ___wrap_getpid, + _wasm_trace, + _initgroups, + ___funcs_on_exit, + ___dl_seterr, + __emscripten_find_dylib, + _emscripten_builtin_memalign, + __emscripten_timeout, + _emscripten_get_sbrk_ptr, + _setThrew, + __emscripten_stack_restore, + __emscripten_stack_alloc, + _emscripten_stack_get_current, + __ZNSt3__211__call_onceERVmPvPFvS2_E, + __ZNSt3__218condition_variable10notify_allEv, + __ZNSt3__25mutex4lockEv, + __ZNSt3__25mutex6unlockEv, + dynCall_viii, + dynCall_vi, + dynCall_ii, + dynCall_iiii, + dynCall_v, + dynCall_iii, + dynCall_iiiii, + dynCall_iiiiii, + dynCall_vii, + dynCall_viiii, + dynCall_iiiiiii, + dynCall_i, + dynCall_viiiii, + dynCall_iiiiiiii, + dynCall_iijii, + dynCall_iiji, + dynCall_iiiiiiiii, + dynCall_viiiiiiii, + dynCall_iiiiiiiiii, + dynCall_iidddd, + dynCall_iij, + dynCall_ji, + dynCall_iiiij, + dynCall_iiiiiiiiiiii, + dynCall_iiiiiiiiiii, + dynCall_jii, + dynCall_iijiji, + dynCall_viiiiiii, + dynCall_viiiiiiiii, + dynCall_viiiiii, + dynCall_jiiiji, + dynCall_jiiji, + dynCall_ij, + dynCall_vijii, + dynCall_iiiiiij, + dynCall_iiid, + dynCall_iiij, + dynCall_dii, + dynCall_vid, + dynCall_vij, + dynCall_di, + dynCall_iiiiijii, + dynCall_j, + dynCall_jj, + dynCall_jiij, + dynCall_iiiiji, + dynCall_iiiijii, + dynCall_viiji, + dynCall_viijii, + dynCall_iiiijji, + dynCall_dd, + dynCall_ddd, + dynCall_jiji, + dynCall_iidiiii, + _asyncify_start_unwind, + _asyncify_stop_unwind, + _asyncify_start_rewind, + _asyncify_stop_rewind, + memory, + __indirect_function_table, + wasmTable, + wasmMemory; + + function assignWasmExports(wasmExports) { + _malloc = + PHPLoader['malloc'] = + Module['_malloc'] = + wasmExports['malloc']; + _free = wasmExports['free']; + ___errno_location = Module['___errno_location'] = + wasmExports['__errno_location']; + _calloc = wasmExports['calloc']; + _wasm_sleep = Module['_wasm_sleep'] = wasmExports['wasm_sleep']; + _ntohs = wasmExports['ntohs']; + _htons = wasmExports['htons']; + _htonl = wasmExports['htonl']; + _wasm_read = Module['_wasm_read'] = wasmExports['wasm_read']; + _fflush = wasmExports['fflush']; + _flock = Module['_flock'] = wasmExports['flock']; + _wasm_popen = Module['_wasm_popen'] = wasmExports['wasm_popen']; + _wasm_php_exec = Module['_wasm_php_exec'] = + wasmExports['wasm_php_exec']; + _php_pollfd_for = Module['_php_pollfd_for'] = + wasmExports['php_pollfd_for']; + ___wrap_usleep = Module['___wrap_usleep'] = + wasmExports['__wrap_usleep']; + ___wrap_select = Module['___wrap_select'] = + wasmExports['__wrap_select']; + _wasm_set_sapi_name = Module['_wasm_set_sapi_name'] = + wasmExports['wasm_set_sapi_name']; + _wasm_set_phpini_path = Module['_wasm_set_phpini_path'] = + wasmExports['wasm_set_phpini_path']; + _wasm_add_cli_arg = Module['_wasm_add_cli_arg'] = + wasmExports['wasm_add_cli_arg']; + _run_cli = Module['_run_cli'] = wasmExports['run_cli']; + _wasm_add_SERVER_entry = Module['_wasm_add_SERVER_entry'] = + wasmExports['wasm_add_SERVER_entry']; + _wasm_add_ENV_entry = Module['_wasm_add_ENV_entry'] = + wasmExports['wasm_add_ENV_entry']; + _wasm_set_query_string = Module['_wasm_set_query_string'] = + wasmExports['wasm_set_query_string']; + _wasm_set_path_translated = Module['_wasm_set_path_translated'] = + wasmExports['wasm_set_path_translated']; + _wasm_set_skip_shebang = Module['_wasm_set_skip_shebang'] = + wasmExports['wasm_set_skip_shebang']; + _wasm_set_request_uri = Module['_wasm_set_request_uri'] = + wasmExports['wasm_set_request_uri']; + _wasm_set_request_method = Module['_wasm_set_request_method'] = + wasmExports['wasm_set_request_method']; + _wasm_set_request_host = Module['_wasm_set_request_host'] = + wasmExports['wasm_set_request_host']; + _wasm_set_content_type = Module['_wasm_set_content_type'] = + wasmExports['wasm_set_content_type']; + _wasm_set_request_body = Module['_wasm_set_request_body'] = + wasmExports['wasm_set_request_body']; + _wasm_set_content_length = Module['_wasm_set_content_length'] = + wasmExports['wasm_set_content_length']; + _wasm_set_cookies = Module['_wasm_set_cookies'] = + wasmExports['wasm_set_cookies']; + _wasm_set_request_port = Module['_wasm_set_request_port'] = + wasmExports['wasm_set_request_port']; + _wasm_sapi_request_shutdown = Module['_wasm_sapi_request_shutdown'] = + wasmExports['wasm_sapi_request_shutdown']; + _wasm_sapi_handle_request = Module['_wasm_sapi_handle_request'] = + wasmExports['wasm_sapi_handle_request']; + _php_wasm_init = Module['_php_wasm_init'] = + wasmExports['php_wasm_init']; + _wasm_free = + PHPLoader['free'] = + Module['_wasm_free'] = + wasmExports['wasm_free']; + _wasm_get_end_offset = Module['_wasm_get_end_offset'] = + wasmExports['wasm_get_end_offset']; + ___wrap_getpid = Module['___wrap_getpid'] = + wasmExports['__wrap_getpid']; + _wasm_trace = Module['_wasm_trace'] = wasmExports['wasm_trace']; + _initgroups = Module['_initgroups'] = wasmExports['initgroups']; + ___funcs_on_exit = wasmExports['__funcs_on_exit']; + ___dl_seterr = wasmExports['__dl_seterr']; + __emscripten_find_dylib = wasmExports['_emscripten_find_dylib']; + _emscripten_builtin_memalign = + wasmExports['emscripten_builtin_memalign']; + __emscripten_timeout = wasmExports['_emscripten_timeout']; + _emscripten_get_sbrk_ptr = wasmExports['emscripten_get_sbrk_ptr']; + _setThrew = wasmExports['setThrew']; + __emscripten_stack_restore = wasmExports['_emscripten_stack_restore']; + __emscripten_stack_alloc = wasmExports['_emscripten_stack_alloc']; + _emscripten_stack_get_current = + wasmExports['emscripten_stack_get_current']; + __ZNSt3__211__call_onceERVmPvPFvS2_E = Module[ + '__ZNSt3__211__call_onceERVmPvPFvS2_E' + ] = wasmExports['_ZNSt3__211__call_onceERVmPvPFvS2_E']; + __ZNSt3__218condition_variable10notify_allEv = Module[ + '__ZNSt3__218condition_variable10notify_allEv' + ] = wasmExports['_ZNSt3__218condition_variable10notify_allEv']; + __ZNSt3__25mutex4lockEv = Module['__ZNSt3__25mutex4lockEv'] = + wasmExports['_ZNSt3__25mutex4lockEv']; + __ZNSt3__25mutex6unlockEv = Module['__ZNSt3__25mutex6unlockEv'] = + wasmExports['_ZNSt3__25mutex6unlockEv']; + dynCall_viii = dynCalls['viii'] = wasmExports['dynCall_viii']; + dynCall_vi = dynCalls['vi'] = wasmExports['dynCall_vi']; + dynCall_ii = dynCalls['ii'] = wasmExports['dynCall_ii']; + dynCall_iiii = dynCalls['iiii'] = wasmExports['dynCall_iiii']; + dynCall_v = dynCalls['v'] = wasmExports['dynCall_v']; + dynCall_iii = dynCalls['iii'] = wasmExports['dynCall_iii']; + dynCall_iiiii = dynCalls['iiiii'] = wasmExports['dynCall_iiiii']; + dynCall_iiiiii = dynCalls['iiiiii'] = wasmExports['dynCall_iiiiii']; + dynCall_vii = dynCalls['vii'] = wasmExports['dynCall_vii']; + dynCall_viiii = dynCalls['viiii'] = wasmExports['dynCall_viiii']; + dynCall_iiiiiii = dynCalls['iiiiiii'] = wasmExports['dynCall_iiiiiii']; + dynCall_i = dynCalls['i'] = wasmExports['dynCall_i']; + dynCall_viiiii = dynCalls['viiiii'] = wasmExports['dynCall_viiiii']; + dynCall_iiiiiiii = dynCalls['iiiiiiii'] = + wasmExports['dynCall_iiiiiiii']; + dynCall_iijii = dynCalls['iijii'] = wasmExports['dynCall_iijii']; + dynCall_iiji = dynCalls['iiji'] = wasmExports['dynCall_iiji']; + dynCall_iiiiiiiii = dynCalls['iiiiiiiii'] = + wasmExports['dynCall_iiiiiiiii']; + dynCall_viiiiiiii = dynCalls['viiiiiiii'] = + wasmExports['dynCall_viiiiiiii']; + dynCall_iiiiiiiiii = dynCalls['iiiiiiiiii'] = + wasmExports['dynCall_iiiiiiiiii']; + dynCall_iidddd = dynCalls['iidddd'] = wasmExports['dynCall_iidddd']; + dynCall_iij = dynCalls['iij'] = wasmExports['dynCall_iij']; + dynCall_ji = dynCalls['ji'] = wasmExports['dynCall_ji']; + dynCall_iiiij = dynCalls['iiiij'] = wasmExports['dynCall_iiiij']; + dynCall_iiiiiiiiiiii = dynCalls['iiiiiiiiiiii'] = + wasmExports['dynCall_iiiiiiiiiiii']; + dynCall_iiiiiiiiiii = dynCalls['iiiiiiiiiii'] = + wasmExports['dynCall_iiiiiiiiiii']; + dynCall_jii = dynCalls['jii'] = wasmExports['dynCall_jii']; + dynCall_iijiji = dynCalls['iijiji'] = wasmExports['dynCall_iijiji']; + dynCall_viiiiiii = dynCalls['viiiiiii'] = + wasmExports['dynCall_viiiiiii']; + dynCall_viiiiiiiii = dynCalls['viiiiiiiii'] = + wasmExports['dynCall_viiiiiiiii']; + dynCall_viiiiii = dynCalls['viiiiii'] = wasmExports['dynCall_viiiiii']; + dynCall_jiiiji = dynCalls['jiiiji'] = wasmExports['dynCall_jiiiji']; + dynCall_jiiji = dynCalls['jiiji'] = wasmExports['dynCall_jiiji']; + dynCall_ij = dynCalls['ij'] = wasmExports['dynCall_ij']; + dynCall_vijii = dynCalls['vijii'] = wasmExports['dynCall_vijii']; + dynCall_iiiiiij = dynCalls['iiiiiij'] = wasmExports['dynCall_iiiiiij']; + dynCall_iiid = dynCalls['iiid'] = wasmExports['dynCall_iiid']; + dynCall_iiij = dynCalls['iiij'] = wasmExports['dynCall_iiij']; + dynCall_dii = dynCalls['dii'] = wasmExports['dynCall_dii']; + dynCall_vid = dynCalls['vid'] = wasmExports['dynCall_vid']; + dynCall_vij = dynCalls['vij'] = wasmExports['dynCall_vij']; + dynCall_di = dynCalls['di'] = wasmExports['dynCall_di']; + dynCall_iiiiijii = dynCalls['iiiiijii'] = + wasmExports['dynCall_iiiiijii']; + dynCall_j = dynCalls['j'] = wasmExports['dynCall_j']; + dynCall_jj = dynCalls['jj'] = wasmExports['dynCall_jj']; + dynCall_jiij = dynCalls['jiij'] = wasmExports['dynCall_jiij']; + dynCall_iiiiji = dynCalls['iiiiji'] = wasmExports['dynCall_iiiiji']; + dynCall_iiiijii = dynCalls['iiiijii'] = wasmExports['dynCall_iiiijii']; + dynCall_viiji = dynCalls['viiji'] = wasmExports['dynCall_viiji']; + dynCall_viijii = dynCalls['viijii'] = wasmExports['dynCall_viijii']; + dynCall_iiiijji = dynCalls['iiiijji'] = wasmExports['dynCall_iiiijji']; + dynCall_dd = dynCalls['dd'] = wasmExports['dynCall_dd']; + dynCall_ddd = dynCalls['ddd'] = wasmExports['dynCall_ddd']; + dynCall_jiji = dynCalls['jiji'] = wasmExports['dynCall_jiji']; + dynCall_iidiiii = dynCalls['iidiiii'] = wasmExports['dynCall_iidiiii']; + _asyncify_start_unwind = wasmExports['asyncify_start_unwind']; + _asyncify_stop_unwind = wasmExports['asyncify_stop_unwind']; + _asyncify_start_rewind = wasmExports['asyncify_start_rewind']; + _asyncify_stop_rewind = wasmExports['asyncify_stop_rewind']; + memory = wasmMemory = wasmExports['memory']; + __indirect_function_table = wasmTable = + wasmExports['__indirect_function_table']; + } + + var ___heap_base = 8339344; + + var wasmImports = { + /** @export */ __assert_fail: ___assert_fail, + /** @export */ __asyncify_data: ___asyncify_data, + /** @export */ __asyncify_state: ___asyncify_state, + /** @export */ __asyncjs__js_module_onMessage, + /** @export */ __call_sighandler: ___call_sighandler, + /** @export */ __syscall_accept4: ___syscall_accept4, + /** @export */ __syscall_bind: ___syscall_bind, + /** @export */ __syscall_chdir: ___syscall_chdir, + /** @export */ __syscall_chmod: ___syscall_chmod, + /** @export */ __syscall_connect: ___syscall_connect, + /** @export */ __syscall_dup: ___syscall_dup, + /** @export */ __syscall_dup3: ___syscall_dup3, + /** @export */ __syscall_faccessat: ___syscall_faccessat, + /** @export */ __syscall_fchmod: ___syscall_fchmod, + /** @export */ __syscall_fchown32: ___syscall_fchown32, + /** @export */ __syscall_fchownat: ___syscall_fchownat, + /** @export */ __syscall_fcntl64: ___syscall_fcntl64, + /** @export */ __syscall_fstat64: ___syscall_fstat64, + /** @export */ __syscall_ftruncate64: ___syscall_ftruncate64, + /** @export */ __syscall_getcwd: ___syscall_getcwd, + /** @export */ __syscall_getdents64: ___syscall_getdents64, + /** @export */ __syscall_getpeername: ___syscall_getpeername, + /** @export */ __syscall_getsockname: ___syscall_getsockname, + /** @export */ __syscall_getsockopt: ___syscall_getsockopt, + /** @export */ __syscall_ioctl: ___syscall_ioctl, + /** @export */ __syscall_listen: ___syscall_listen, + /** @export */ __syscall_lstat64: ___syscall_lstat64, + /** @export */ __syscall_mkdirat: ___syscall_mkdirat, + /** @export */ __syscall_newfstatat: ___syscall_newfstatat, + /** @export */ __syscall_openat: ___syscall_openat, + /** @export */ __syscall_pipe: ___syscall_pipe, + /** @export */ __syscall_poll: ___syscall_poll, + /** @export */ __syscall_readlinkat: ___syscall_readlinkat, + /** @export */ __syscall_recvfrom: ___syscall_recvfrom, + /** @export */ __syscall_renameat: ___syscall_renameat, + /** @export */ __syscall_rmdir: ___syscall_rmdir, + /** @export */ __syscall_sendto: ___syscall_sendto, + /** @export */ __syscall_socket: ___syscall_socket, + /** @export */ __syscall_stat64: ___syscall_stat64, + /** @export */ __syscall_statfs64: ___syscall_statfs64, + /** @export */ __syscall_symlinkat: ___syscall_symlinkat, + /** @export */ __syscall_unlinkat: ___syscall_unlinkat, + /** @export */ __syscall_utimensat: ___syscall_utimensat, + /** @export */ _abort_js: __abort_js, + /** @export */ _dlopen_js: __dlopen_js, + /** @export */ _dlsym_js: __dlsym_js, + /** @export */ _emscripten_lookup_name: __emscripten_lookup_name, + /** @export */ _emscripten_runtime_keepalive_clear: + __emscripten_runtime_keepalive_clear, + /** @export */ _emscripten_throw_longjmp: __emscripten_throw_longjmp, + /** @export */ _gmtime_js: __gmtime_js, + /** @export */ _localtime_js: __localtime_js, + /** @export */ _mktime_js: __mktime_js, + /** @export */ _mmap_js: __mmap_js, + /** @export */ _munmap_js: __munmap_js, + /** @export */ _setitimer_js: __setitimer_js, + /** @export */ _tzset_js: __tzset_js, + /** @export */ clock_time_get: _clock_time_get, + /** @export */ emscripten_date_now: _emscripten_date_now, + /** @export */ emscripten_get_heap_max: _emscripten_get_heap_max, + /** @export */ emscripten_get_now: _emscripten_get_now, + /** @export */ emscripten_resize_heap: _emscripten_resize_heap, + /** @export */ emscripten_sleep: _emscripten_sleep, + /** @export */ environ_get: _environ_get, + /** @export */ environ_sizes_get: _environ_sizes_get, + /** @export */ exit: _exit, + /** @export */ fd_close: _fd_close, + /** @export */ fd_fdstat_get: _fd_fdstat_get, + /** @export */ fd_read: _fd_read, + /** @export */ fd_seek: _fd_seek, + /** @export */ fd_sync: _fd_sync, + /** @export */ fd_write: _fd_write, + /** @export */ getaddrinfo: _getaddrinfo, + /** @export */ getnameinfo: _getnameinfo, + /** @export */ getprotobyname: _getprotobyname, + /** @export */ getprotobynumber: _getprotobynumber, + /** @export */ invoke_i, + /** @export */ invoke_ii, + /** @export */ invoke_iii, + /** @export */ invoke_iiii, + /** @export */ invoke_iiiii, + /** @export */ invoke_iiiiii, + /** @export */ invoke_iiiiiii, + /** @export */ invoke_iiiiiiii, + /** @export */ invoke_iiiiiiiii, + /** @export */ invoke_iiiiiiiiii, + /** @export */ invoke_iiji, + /** @export */ invoke_iijiji, + /** @export */ invoke_jii, + /** @export */ invoke_v, + /** @export */ invoke_vi, + /** @export */ invoke_vii, + /** @export */ invoke_viii, + /** @export */ invoke_viiii, + /** @export */ invoke_viiiii, + /** @export */ invoke_viiiiii, + /** @export */ invoke_viiiiiii, + /** @export */ js_fd_read, + /** @export */ js_flock: _js_flock, + /** @export */ js_getpid: _js_getpid, + /** @export */ js_open_process: _js_open_process, + /** @export */ js_popen_to_file, + /** @export */ js_release_file_locks: _js_release_file_locks, + /** @export */ js_wasm_trace: _js_wasm_trace, + /** @export */ proc_exit: _proc_exit, + /** @export */ strptime: _strptime, + /** @export */ wasm_close: _wasm_close, + /** @export */ wasm_poll_socket, + /** @export */ wasm_setsockopt: _wasm_setsockopt, + }; + + function invoke_iiiiiiii(index, a1, a2, a3, a4, a5, a6, a7) { + var sp = stackSave(); + try { + return dynCalls['iiiiiiii'](index, a1, a2, a3, a4, a5, a6, a7); + } catch (e) { + stackRestore(sp); + if (e !== e + 0) throw e; + _setThrew(1, 0); + } + } + + function invoke_vi(index, a1) { + var sp = stackSave(); + try { + dynCalls['vi'](index, a1); + } catch (e) { + stackRestore(sp); + if (e !== e + 0) throw e; + _setThrew(1, 0); + } + } + + function invoke_vii(index, a1, a2) { + var sp = stackSave(); + try { + dynCalls['vii'](index, a1, a2); + } catch (e) { + stackRestore(sp); + if (e !== e + 0) throw e; + _setThrew(1, 0); + } + } + + function invoke_iii(index, a1, a2) { + var sp = stackSave(); + try { + return dynCalls['iii'](index, a1, a2); + } catch (e) { + stackRestore(sp); + if (e !== e + 0) throw e; + _setThrew(1, 0); + } + } + + function invoke_ii(index, a1) { + var sp = stackSave(); + try { + return dynCalls['ii'](index, a1); + } catch (e) { + stackRestore(sp); + if (e !== e + 0) throw e; + _setThrew(1, 0); + } + } + + function invoke_viii(index, a1, a2, a3) { + var sp = stackSave(); + try { + dynCalls['viii'](index, a1, a2, a3); + } catch (e) { + stackRestore(sp); + if (e !== e + 0) throw e; + _setThrew(1, 0); + } + } + + function invoke_v(index) { + var sp = stackSave(); + try { + dynCalls['v'](index); + } catch (e) { + stackRestore(sp); + if (e !== e + 0) throw e; + _setThrew(1, 0); + } + } + + function invoke_i(index) { + var sp = stackSave(); + try { + return dynCalls['i'](index); + } catch (e) { + stackRestore(sp); + if (e !== e + 0) throw e; + _setThrew(1, 0); + } + } + + function invoke_iiiii(index, a1, a2, a3, a4) { + var sp = stackSave(); + try { + return dynCalls['iiiii'](index, a1, a2, a3, a4); + } catch (e) { + stackRestore(sp); + if (e !== e + 0) throw e; + _setThrew(1, 0); + } + } + + function invoke_iiii(index, a1, a2, a3) { + var sp = stackSave(); + try { + return dynCalls['iiii'](index, a1, a2, a3); + } catch (e) { + stackRestore(sp); + if (e !== e + 0) throw e; + _setThrew(1, 0); + } + } + + function invoke_viiii(index, a1, a2, a3, a4) { + var sp = stackSave(); + try { + dynCalls['viiii'](index, a1, a2, a3, a4); + } catch (e) { + stackRestore(sp); + if (e !== e + 0) throw e; + _setThrew(1, 0); + } + } + + function invoke_iiiiiiiii(index, a1, a2, a3, a4, a5, a6, a7, a8) { + var sp = stackSave(); + try { + return dynCalls['iiiiiiiii'](index, a1, a2, a3, a4, a5, a6, a7, a8); + } catch (e) { + stackRestore(sp); + if (e !== e + 0) throw e; + _setThrew(1, 0); + } + } + + function invoke_viiiiii(index, a1, a2, a3, a4, a5, a6) { + var sp = stackSave(); + try { + dynCalls['viiiiii'](index, a1, a2, a3, a4, a5, a6); + } catch (e) { + stackRestore(sp); + if (e !== e + 0) throw e; + _setThrew(1, 0); + } + } + + function invoke_viiiii(index, a1, a2, a3, a4, a5) { + var sp = stackSave(); + try { + dynCalls['viiiii'](index, a1, a2, a3, a4, a5); + } catch (e) { + stackRestore(sp); + if (e !== e + 0) throw e; + _setThrew(1, 0); + } + } + + function invoke_jii(index, a1, a2) { + var sp = stackSave(); + try { + return dynCalls['jii'](index, a1, a2); + } catch (e) { + stackRestore(sp); + if (e !== e + 0) throw e; + _setThrew(1, 0); + return 0n; + } + } + + function invoke_iiiiii(index, a1, a2, a3, a4, a5) { + var sp = stackSave(); + try { + return dynCalls['iiiiii'](index, a1, a2, a3, a4, a5); + } catch (e) { + stackRestore(sp); + if (e !== e + 0) throw e; + _setThrew(1, 0); + } + } + + function invoke_iijiji(index, a1, a2, a3, a4, a5) { + var sp = stackSave(); + try { + return dynCalls['iijiji'](index, a1, a2, a3, a4, a5); + } catch (e) { + stackRestore(sp); + if (e !== e + 0) throw e; + _setThrew(1, 0); + } + } + + function invoke_iiji(index, a1, a2, a3) { + var sp = stackSave(); + try { + return dynCalls['iiji'](index, a1, a2, a3); + } catch (e) { + stackRestore(sp); + if (e !== e + 0) throw e; + _setThrew(1, 0); + } + } + + function invoke_iiiiiii(index, a1, a2, a3, a4, a5, a6) { + var sp = stackSave(); + try { + return dynCalls['iiiiiii'](index, a1, a2, a3, a4, a5, a6); + } catch (e) { + stackRestore(sp); + if (e !== e + 0) throw e; + _setThrew(1, 0); + } + } + + function invoke_viiiiiii(index, a1, a2, a3, a4, a5, a6, a7) { + var sp = stackSave(); + try { + dynCalls['viiiiiii'](index, a1, a2, a3, a4, a5, a6, a7); + } catch (e) { + stackRestore(sp); + if (e !== e + 0) throw e; + _setThrew(1, 0); + } + } + + function invoke_iiiiiiiiii(index, a1, a2, a3, a4, a5, a6, a7, a8, a9) { + var sp = stackSave(); + try { + return dynCalls['iiiiiiiiii']( + index, + a1, + a2, + a3, + a4, + a5, + a6, + a7, + a8, + a9 + ); + } catch (e) { + stackRestore(sp); + if (e !== e + 0) throw e; + _setThrew(1, 0); + } + } + + // include: postamble.js + // === Auto-generated postamble setup entry stuff === + function callMain(args = []) { + var entryFunction = resolveGlobalSymbol('main').sym; + // Main modules can't tell if they have main() at compile time, since it may + // arrive from a dynamic library. + if (!entryFunction) return; + args.unshift(thisProgram); + var argc = args.length; + var argv = stackAlloc((argc + 1) * 4); + var argv_ptr = argv; + for (var arg of args) { + HEAPU32[argv_ptr >> 2] = stringToUTF8OnStack(arg); + argv_ptr += 4; + } + HEAPU32[argv_ptr >> 2] = 0; + try { + var ret = entryFunction(argc, argv); + // if we're not running an evented main loop, it's time to exit + exitJS(ret, /* implicit = */ true); + return ret; + } catch (e) { + return handleException(e); + } + } + + function run(args = arguments_) { + if (runDependencies > 0) { + dependenciesFulfilled = run; + return; + } + preRun(); + // a preRun added a dependency, run will be called later + if (runDependencies > 0) { + dependenciesFulfilled = run; + return; + } + function doRun() { + // run may have just been called through dependencies being fulfilled just in this very frame, + // or while the async setStatus time below was happening + Module['calledRun'] = true; + if (ABORT) return; + initRuntime(); + preMain(); + Module['onRuntimeInitialized']?.(); + var noInitialRun = Module['noInitialRun'] || true; + if (!noInitialRun) callMain(args); + postRun(); + } + if (Module['setStatus']) { + Module['setStatus']('Running...'); + setTimeout(() => { + setTimeout(() => Module['setStatus'](''), 1); + doRun(); + }, 1); + } else { + doRun(); + } + } + + var wasmExports; + + // With async instantation wasmExports is assigned asynchronously when the + // instance is received. + createWasm(); + + run(); + /** + * Emscripten resolves `localhost` to a random IP address. Let's + * make it always resolve to 127.0.0.1. + */ + DNS.address_map.addrs.localhost = '127.0.0.1'; + + /** + * Debugging Asyncify errors is tricky because the stack trace is lost when the + * error is thrown. This code saves the stack trace in a global variable + * so that it can be inspected later. + */ + PHPLoader.debug = 'debug' in PHPLoader ? PHPLoader.debug : true; + if (PHPLoader.debug && typeof Asyncify !== 'undefined') { + const originalHandleSleep = Asyncify.handleSleep; + Asyncify.handleSleep = function (startAsync) { + if (!ABORT) { + Module['lastAsyncifyStackSource'] = new Error(); + } + return originalHandleSleep(startAsync); + }; + } + + /** + * Data dependencies call removeRunDependency() when they are loaded. + * The synchronous call stack then continues to run. If an error occurs + * in PHP initialization, e.g. Out Of Memory error, it will not be + * caught by any try/catch. This override propagates the failure to + * PHPLoader.onAbort() so that it can be handled. + */ + const originalRemoveRunDependency = PHPLoader['removeRunDependency']; + PHPLoader['removeRunDependency'] = function (...args) { + try { + originalRemoveRunDependency(...args); + } catch (e) { + PHPLoader['onAbort'](e); + } + }; + + if (typeof NODEFS === 'object') { + // We override NODEFS.createNode() to add an `isSharedFS` flag to all NODEFS + // nodes. This way we can tell whether file-locking is needed and possible + // for an FS node, even if wrapped with PROXYFS. + const originalNodeFsCreateNode = NODEFS.createNode; + NODEFS.createNode = function createNodeWithSharedFlag() { + const node = originalNodeFsCreateNode.apply(NODEFS, arguments); + node.isSharedFS = true; + return node; + }; + + var originalHashAddNode = FS.hashAddNode; + FS.hashAddNode = function hashAddNodeIfNotSharedFS(node) { + if (node?.isSharedFS) { + // Avoid caching shared VFS nodes so multiple instances + // can access the same underlying filesystem without + // conflicting caches. + return; + } + return originalHashAddNode.apply(FS, arguments); + }; + } + + /** + * Expose the PHP version so the PHP class can make version-specific + * adjustments to `php.ini`. + */ + PHPLoader['phpVersion'] = (() => { + const [major, minor, patch] = phpVersionString.split('.').map(Number); + return { major, minor, patch }; + })(); + + return PHPLoader; + + // Close the opening bracket from esm-prefix.js: +} diff --git a/packages/php-wasm/node-builds/5-6/build.js b/packages/php-wasm/node-builds/5-6/build.js new file mode 100644 index 00000000000..f801377961a --- /dev/null +++ b/packages/php-wasm/node-builds/5-6/build.js @@ -0,0 +1,90 @@ +import esbuild from 'esbuild'; +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const projectRoot = path.resolve(__dirname, '..', '..', '..', '..'); +const packagePath = path.join(projectRoot, 'packages/php-wasm/node-builds/5-6'); +const distPath = path.join( + projectRoot, + 'dist/packages/php-wasm/node-builds/5-6' +); + +try { + fs.mkdirSync(distPath, { recursive: true }); +} catch (e) { + // Ignore +} + +/** + * Plugin to rewrite imports to work from the dist directory. + * Dynamic imports need to be preserved as external and paths adjusted. + */ +const externalPathPlugin = { + name: 'external-path', + setup(build) { + // Mark PHP loader files as external and rewrite their paths + build.onResolve( + { filter: /\.\.\/(?:jspi|asyncify)\/.*\.js$/ }, + (args) => { + const newPath = args.path.replace('../', './'); + return { path: newPath, external: true }; + } + ); + // Mark extension .so files as external and rewrite paths + build.onResolve( + { filter: /\.\.\/(?:jspi|asyncify)\/extensions\/.*\.so\?url$/ }, + (args) => { + const newPath = args.path.replace('../', './'); + return { path: newPath, external: true }; + } + ); + }, +}; + +async function build() { + // CommonJS build + await esbuild.build({ + entryPoints: [`${packagePath}/src/index.ts`], + supported: { 'dynamic-import': true }, + outExtension: { '.js': '.cjs' }, + outdir: distPath, + platform: 'node', + assetNames: '[name]', + chunkNames: '[name]', + logOverride: { + 'direct-eval': 'silent', + 'commonjs-variable-in-esm': 'silent', + }, + format: 'cjs', + bundle: true, + tsconfig: `${packagePath}/tsconfig.json`, + external: ['@php-wasm/*', 'wasm-feature-detect'], + loader: { '.wasm': 'file', '.so': 'file' }, + plugins: [externalPathPlugin], + }); + + // ESM build + await esbuild.build({ + entryPoints: [`${packagePath}/src/index.ts`], + outdir: distPath, + platform: 'node', + assetNames: '[name]', + chunkNames: '[name]', + logOverride: { + 'direct-eval': 'silent', + 'commonjs-variable-in-esm': 'silent', + }, + packages: 'external', + bundle: true, + tsconfig: `${packagePath}/tsconfig.json`, + external: ['@php-wasm/*', 'wasm-feature-detect'], + supported: { 'dynamic-import': true, 'top-level-await': true }, + format: 'esm', + loader: { '.wasm': 'file', '.so': 'file' }, + plugins: [externalPathPlugin], + }); +} +build(); diff --git a/packages/php-wasm/node-builds/5-6/jspi/5_6_40/.gitkeep b/packages/php-wasm/node-builds/5-6/jspi/5_6_40/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/php-wasm/node-builds/5-6/jspi/php_5_6.js b/packages/php-wasm/node-builds/5-6/jspi/php_5_6.js new file mode 100644 index 00000000000..29d0bb2955c --- /dev/null +++ b/packages/php-wasm/node-builds/5-6/jspi/php_5_6.js @@ -0,0 +1,3 @@ +throw new Error( + 'PHP 5.6 JSPI variant has not been compiled yet. Use the asyncify variant instead.' +); diff --git a/packages/php-wasm/node-builds/5-6/package.json b/packages/php-wasm/node-builds/5-6/package.json new file mode 100644 index 00000000000..188a8f07686 --- /dev/null +++ b/packages/php-wasm/node-builds/5-6/package.json @@ -0,0 +1,38 @@ +{ + "name": "@php-wasm/node-5-6", + "version": "3.1.15", + "description": "PHP 5.6 WebAssembly binaries for node (legacy)", + "repository": { + "type": "git", + "url": "https://github.com/WordPress/wordpress-playground" + }, + "homepage": "https://developer.wordpress.org/playground", + "author": "The WordPress contributors", + "contributors": [ + { + "name": "Adam Zielinski", + "email": "adam@adamziel.com", + "url": "https://github.com/adamziel" + } + ], + "exports": { + ".": { + "import": "./index.js", + "require": "./index.cjs" + }, + "./package.json": "./package.json" + }, + "publishConfig": { + "access": "public", + "directory": "../../../../dist/packages/php-wasm/node-builds/5-6" + }, + "type": "module", + "main": "./index.cjs", + "module": "./index.js", + "types": "index.d.ts", + "license": "GPL-2.0-or-later", + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } +} diff --git a/packages/php-wasm/node-builds/5-6/project.json b/packages/php-wasm/node-builds/5-6/project.json new file mode 100644 index 00000000000..ea421361fbd --- /dev/null +++ b/packages/php-wasm/node-builds/5-6/project.json @@ -0,0 +1,61 @@ +{ + "name": "php-wasm-node-5-6", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/php-wasm/node-builds/5-6/src", + "projectType": "library", + "implicitDependencies": ["php-wasm-compile"], + "targets": { + "build": { + "executor": "nx:noop", + "dependsOn": ["build:copy-assets"] + }, + "build:mkdir": { + "executor": "nx:run-commands", + "options": { + "commands": ["mkdir -p dist/packages/php-wasm/node-builds/5-6"], + "parallel": false + } + }, + "build:package-json": { + "executor": "@wp-playground/nx-extensions:package-json", + "options": { + "tsConfig": "packages/php-wasm/node-builds/5-6/tsconfig.lib.json", + "outputPath": "dist/packages/php-wasm/node-builds/5-6", + "buildTarget": "php-wasm-node-5-6:build:bundle:production" + }, + "dependsOn": ["build:mkdir"] + }, + "build:bundle": { + "executor": "nx:run-commands", + "options": { + "command": "node packages/php-wasm/node-builds/5-6/build.js", + "parallel": false + }, + "dependsOn": ["build:mkdir"] + }, + "build:copy-assets": { + "executor": "nx:run-commands", + "options": { + "commands": [ + "cp -rf packages/php-wasm/node-builds/5-6/jspi dist/packages/php-wasm/node-builds/5-6/", + "cp -rf packages/php-wasm/node-builds/5-6/asyncify dist/packages/php-wasm/node-builds/5-6/" + ], + "parallel": false + }, + "dependsOn": ["build:package-json"] + }, + "publish": { + "executor": "nx:run-commands", + "options": { + "command": "node tools/scripts/publish.mjs php-wasm-node-5-6 {args.ver} {args.tag}", + "parallel": false + }, + "dependsOn": ["build"] + }, + "package-for-self-hosting": { + "executor": "@wp-playground/nx-extensions:package-for-self-hosting", + "dependsOn": ["build"] + } + }, + "tags": ["scope:php-binaries"] +} diff --git a/packages/php-wasm/node-builds/5-6/src/index.ts b/packages/php-wasm/node-builds/5-6/src/index.ts new file mode 100644 index 00000000000..6786b7ff084 --- /dev/null +++ b/packages/php-wasm/node-builds/5-6/src/index.ts @@ -0,0 +1,14 @@ +import type { PHPLoaderModule } from '@php-wasm/universal'; +import { jspi } from 'wasm-feature-detect'; + +export async function getPHPLoaderModule(): Promise { + if (await jspi()) { + // @ts-ignore + return await import('../jspi/php_5_6.js'); + } else { + // @ts-ignore + return await import('../asyncify/php_5_6.js'); + } +} + +export { jspi }; diff --git a/packages/php-wasm/node-builds/5-6/tsconfig.json b/packages/php-wasm/node-builds/5-6/tsconfig.json new file mode 100644 index 00000000000..b943cbfe62a --- /dev/null +++ b/packages/php-wasm/node-builds/5-6/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "ESNext", + "moduleResolution": "bundler", + "allowJs": true, + "checkJs": false + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/php-wasm/node-builds/5-6/tsconfig.lib.json b/packages/php-wasm/node-builds/5-6/tsconfig.lib.json new file mode 100644 index 00000000000..71adff9b5c9 --- /dev/null +++ b/packages/php-wasm/node-builds/5-6/tsconfig.lib.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../../dist/packages/php-wasm/node-builds/5-6", + "declaration": true, + "types": ["node"] + }, + "include": ["src/**/*.ts"], + "exclude": ["**/*.spec.ts", "**/*.test.ts"] +} diff --git a/packages/php-wasm/node/src/lib/extensions/intl/get-intl-extension-module.ts b/packages/php-wasm/node/src/lib/extensions/intl/get-intl-extension-module.ts index d0d53a8b541..c6c2643628a 100644 --- a/packages/php-wasm/node/src/lib/extensions/intl/get-intl-extension-module.ts +++ b/packages/php-wasm/node/src/lib/extensions/intl/get-intl-extension-module.ts @@ -1,5 +1,5 @@ import { LatestSupportedPHPVersion } from '@php-wasm/universal'; -import type { SupportedPHPVersion } from '@php-wasm/universal'; +import type { AllPHPVersion } from '@php-wasm/universal'; /** * Returns the path to the intl extension for the specified PHP version. @@ -11,7 +11,7 @@ import type { SupportedPHPVersion } from '@php-wasm/universal'; * - etc. */ export async function getIntlExtensionModule( - version: SupportedPHPVersion = LatestSupportedPHPVersion + version: AllPHPVersion = LatestSupportedPHPVersion ): Promise { switch (version) { case '8.5': diff --git a/packages/php-wasm/node/src/lib/extensions/intl/with-intl.ts b/packages/php-wasm/node/src/lib/extensions/intl/with-intl.ts index 6fc7fc5cbce..9f3919ab1f6 100644 --- a/packages/php-wasm/node/src/lib/extensions/intl/with-intl.ts +++ b/packages/php-wasm/node/src/lib/extensions/intl/with-intl.ts @@ -1,7 +1,7 @@ import type { EmscriptenOptions, PHPRuntime, - SupportedPHPVersion, + AllPHPVersion, } from '@php-wasm/universal'; import { LatestSupportedPHPVersion, FSHelpers } from '@php-wasm/universal'; import fs from 'fs'; @@ -9,7 +9,7 @@ import path from 'path'; import { getIntlExtensionModule } from './get-intl-extension-module'; export async function withIntl( - version: SupportedPHPVersion = LatestSupportedPHPVersion, + version: AllPHPVersion = LatestSupportedPHPVersion, options: EmscriptenOptions ): Promise { const extensionName = 'intl.so'; diff --git a/packages/php-wasm/node/src/lib/extensions/memcached/get-memcached-extension-module.ts b/packages/php-wasm/node/src/lib/extensions/memcached/get-memcached-extension-module.ts index 8a300e8a2c0..da98059b9ae 100644 --- a/packages/php-wasm/node/src/lib/extensions/memcached/get-memcached-extension-module.ts +++ b/packages/php-wasm/node/src/lib/extensions/memcached/get-memcached-extension-module.ts @@ -1,5 +1,5 @@ import { LatestSupportedPHPVersion } from '@php-wasm/universal'; -import type { SupportedPHPVersion } from '@php-wasm/universal'; +import type { AllPHPVersion } from '@php-wasm/universal'; /** * Returns the path to the memcached extension for the specified PHP version. @@ -11,30 +11,44 @@ import type { SupportedPHPVersion } from '@php-wasm/universal'; * - etc. */ export async function getMemcachedExtensionModule( - version: SupportedPHPVersion = LatestSupportedPHPVersion + version: AllPHPVersion = LatestSupportedPHPVersion ): Promise { switch (version) { case '8.5': // @ts-ignore - return (await import('@php-wasm/node-8-5')).getMemcachedExtensionPath(); + return ( + await import('@php-wasm/node-8-5') + ).getMemcachedExtensionPath(); case '8.4': // @ts-ignore - return (await import('@php-wasm/node-8-4')).getMemcachedExtensionPath(); + return ( + await import('@php-wasm/node-8-4') + ).getMemcachedExtensionPath(); case '8.3': // @ts-ignore - return (await import('@php-wasm/node-8-3')).getMemcachedExtensionPath(); + return ( + await import('@php-wasm/node-8-3') + ).getMemcachedExtensionPath(); case '8.2': // @ts-ignore - return (await import('@php-wasm/node-8-2')).getMemcachedExtensionPath(); + return ( + await import('@php-wasm/node-8-2') + ).getMemcachedExtensionPath(); case '8.1': // @ts-ignore - return (await import('@php-wasm/node-8-1')).getMemcachedExtensionPath(); + return ( + await import('@php-wasm/node-8-1') + ).getMemcachedExtensionPath(); case '8.0': // @ts-ignore - return (await import('@php-wasm/node-8-0')).getMemcachedExtensionPath(); + return ( + await import('@php-wasm/node-8-0') + ).getMemcachedExtensionPath(); case '7.4': // @ts-ignore - return (await import('@php-wasm/node-7-4')).getMemcachedExtensionPath(); + return ( + await import('@php-wasm/node-7-4') + ).getMemcachedExtensionPath(); } throw new Error(`Unsupported PHP version ${version}`); } diff --git a/packages/php-wasm/node/src/lib/extensions/memcached/with-memcached.ts b/packages/php-wasm/node/src/lib/extensions/memcached/with-memcached.ts index a7615dca005..67486082b4a 100644 --- a/packages/php-wasm/node/src/lib/extensions/memcached/with-memcached.ts +++ b/packages/php-wasm/node/src/lib/extensions/memcached/with-memcached.ts @@ -1,14 +1,14 @@ import type { EmscriptenOptions, PHPRuntime, - SupportedPHPVersion, + AllPHPVersion, } from '@php-wasm/universal'; import { LatestSupportedPHPVersion, FSHelpers } from '@php-wasm/universal'; import fs from 'fs'; import { getMemcachedExtensionModule } from './get-memcached-extension-module'; export async function withMemcached( - version: SupportedPHPVersion = LatestSupportedPHPVersion, + version: AllPHPVersion = LatestSupportedPHPVersion, options: EmscriptenOptions ): Promise { const extensionName = 'memcached.so'; diff --git a/packages/php-wasm/node/src/lib/extensions/redis/get-redis-extension-module.ts b/packages/php-wasm/node/src/lib/extensions/redis/get-redis-extension-module.ts index 8346bd31bc1..83f36702441 100644 --- a/packages/php-wasm/node/src/lib/extensions/redis/get-redis-extension-module.ts +++ b/packages/php-wasm/node/src/lib/extensions/redis/get-redis-extension-module.ts @@ -1,5 +1,5 @@ import { LatestSupportedPHPVersion } from '@php-wasm/universal'; -import type { SupportedPHPVersion } from '@php-wasm/universal'; +import type { AllPHPVersion } from '@php-wasm/universal'; /** * Returns the path to the redis extension for the specified PHP version. @@ -11,7 +11,7 @@ import type { SupportedPHPVersion } from '@php-wasm/universal'; * - etc. */ export async function getRedisExtensionModule( - version: SupportedPHPVersion = LatestSupportedPHPVersion + version: AllPHPVersion = LatestSupportedPHPVersion ): Promise { switch (version) { case '8.5': diff --git a/packages/php-wasm/node/src/lib/extensions/redis/with-redis.ts b/packages/php-wasm/node/src/lib/extensions/redis/with-redis.ts index 87aa05fbd9c..ad38d647e59 100644 --- a/packages/php-wasm/node/src/lib/extensions/redis/with-redis.ts +++ b/packages/php-wasm/node/src/lib/extensions/redis/with-redis.ts @@ -1,14 +1,14 @@ import type { EmscriptenOptions, PHPRuntime, - SupportedPHPVersion, + AllPHPVersion, } from '@php-wasm/universal'; import { LatestSupportedPHPVersion, FSHelpers } from '@php-wasm/universal'; import fs from 'fs'; import { getRedisExtensionModule } from './get-redis-extension-module'; export async function withRedis( - version: SupportedPHPVersion = LatestSupportedPHPVersion, + version: AllPHPVersion = LatestSupportedPHPVersion, options: EmscriptenOptions ): Promise { const extensionName = 'redis.so'; diff --git a/packages/php-wasm/node/src/lib/extensions/xdebug/get-xdebug-extension-module.ts b/packages/php-wasm/node/src/lib/extensions/xdebug/get-xdebug-extension-module.ts index afe37ad19ed..dff1d07c6c4 100644 --- a/packages/php-wasm/node/src/lib/extensions/xdebug/get-xdebug-extension-module.ts +++ b/packages/php-wasm/node/src/lib/extensions/xdebug/get-xdebug-extension-module.ts @@ -1,5 +1,5 @@ import { LatestSupportedPHPVersion } from '@php-wasm/universal'; -import type { SupportedPHPVersion } from '@php-wasm/universal'; +import type { AllPHPVersion } from '@php-wasm/universal'; /** * Returns the path to the xdebug extension for the specified PHP version. @@ -11,7 +11,7 @@ import type { SupportedPHPVersion } from '@php-wasm/universal'; * - etc. */ export async function getXdebugExtensionModule( - version: SupportedPHPVersion = LatestSupportedPHPVersion + version: AllPHPVersion = LatestSupportedPHPVersion ): Promise { switch (version) { case '8.5': diff --git a/packages/php-wasm/node/src/lib/extensions/xdebug/with-xdebug.ts b/packages/php-wasm/node/src/lib/extensions/xdebug/with-xdebug.ts index aa79dbd7feb..64d944fa3e1 100644 --- a/packages/php-wasm/node/src/lib/extensions/xdebug/with-xdebug.ts +++ b/packages/php-wasm/node/src/lib/extensions/xdebug/with-xdebug.ts @@ -2,7 +2,7 @@ import { DEFAULT_IDE_KEY } from '@php-wasm/cli-util'; import { type EmscriptenOptions, type PHPRuntime, - type SupportedPHPVersion, + type AllPHPVersion, FSHelpers, LatestSupportedPHPVersion, SupportedPHPVersions, @@ -23,7 +23,7 @@ export interface XdebugOptions { } export async function withXdebug( - version: SupportedPHPVersion = LatestSupportedPHPVersion, + version: AllPHPVersion = LatestSupportedPHPVersion, options: EmscriptenOptions, xdebugOptions: XdebugOptions ): Promise { diff --git a/packages/php-wasm/node/src/lib/get-php-loader-module.ts b/packages/php-wasm/node/src/lib/get-php-loader-module.ts index 7c736466072..9365666e7e6 100644 --- a/packages/php-wasm/node/src/lib/get-php-loader-module.ts +++ b/packages/php-wasm/node/src/lib/get-php-loader-module.ts @@ -1,5 +1,5 @@ import { LatestSupportedPHPVersion } from '@php-wasm/universal'; -import type { PHPLoaderModule, SupportedPHPVersion } from '@php-wasm/universal'; +import type { AllPHPVersion, PHPLoaderModule } from '@php-wasm/universal'; /** * Loads the PHP loader module for the given PHP version. @@ -14,7 +14,7 @@ import type { PHPLoaderModule, SupportedPHPVersion } from '@php-wasm/universal'; * @returns The PHP loader module. */ export async function getPHPLoaderModule( - version: SupportedPHPVersion | string = LatestSupportedPHPVersion + version: AllPHPVersion | string = LatestSupportedPHPVersion ): Promise { try { switch (version) { @@ -53,6 +53,11 @@ export async function getPHPLoaderModule( return ( await import('@php-wasm/node-7-4') ).getPHPLoaderModule(); + case '5.6': + // @ts-ignore + return ( + await import('@php-wasm/node-5-6') + ).getPHPLoaderModule(); } throw new Error(`Unsupported PHP version ${version}`); diff --git a/packages/php-wasm/node/src/lib/load-runtime.ts b/packages/php-wasm/node/src/lib/load-runtime.ts index b23f5f40dec..51291b96ddc 100644 --- a/packages/php-wasm/node/src/lib/load-runtime.ts +++ b/packages/php-wasm/node/src/lib/load-runtime.ts @@ -1,4 +1,5 @@ import { + type AllPHPVersion, type SupportedPHPVersion, type EmscriptenOptions, type PHPRuntime, @@ -6,6 +7,7 @@ import { loadPHPRuntime, FSHelpers, FileLockManagerComposite, + LegacyPHPVersions, ProcessIdAllocator, } from '@php-wasm/universal'; import type { WasmUserSpaceAPI, WasmUserSpaceContext } from './wasm-user-space'; @@ -98,7 +100,7 @@ const dangerousDefaultProcessIdAllocator = (process.env as any).VITEST * @see load */ export async function loadNodeRuntime( - phpVersion: SupportedPHPVersion, + phpVersion: AllPHPVersion, options: PHPLoaderOptionsForNode = {} ) { const processId = @@ -110,6 +112,10 @@ export async function loadNodeRuntime( ? dangerousDefaultProcessIdAllocator!.claim() : undefined); + const isLegacy = (LegacyPHPVersions as readonly string[]).includes( + phpVersion + ); + let emscriptenOptions: EmscriptenOptions = { /** * Emscripten default behavior is to kill the process when @@ -134,6 +140,50 @@ export async function loadNodeRuntime( }, ...(options.emscriptenOptions || {}), processId, + // For legacy PHP: pre-create php.ini with disable_functions BEFORE + // the PHP SAPI starts. initRuntime() calls __wasm_call_ctors which + // triggers wasm_sapi_module_startup -> php_module_startup, and + // disable_functions is processed during php_module_startup. + // This preRun callback runs before initRuntime(). + preRun: isLegacy + ? [ + (module: any) => { + // Pre-create php.ini with ALL default settings + // PLUS disable_functions=ini_get_all. This must + // be done before SAPI startup which processes + // disable_functions at module init time. + // PHP.initializeRuntime skips writing php.ini + // if the file already exists. + module.FS.mkdirTree('/internal/shared'); + module.FS.writeFile( + '/internal/shared/php.ini', + [ + 'auto_prepend_file=/internal/shared/auto_prepend_file.php', + 'memory_limit=256M', + 'ignore_repeated_errors = 1', + 'error_reporting = E_ALL', + 'display_errors = 1', + 'html_errors = 1', + 'display_startup_errors = On', + 'log_errors = 1', + 'always_populate_raw_post_data = -1', + 'upload_max_filesize = 2000M', + 'post_max_size = 2000M', + 'allow_url_fopen = On', + 'allow_url_include = Off', + 'session.save_path = /home/web_user', + 'implicit_flush = 1', + 'output_buffering = 0', + 'max_execution_time = 0', + 'max_input_time = -1', + 'disable_functions = ini_get_all', + 'opcache.enable = 0', + 'opcache.enable_cli = 0', + ].join('\n') + ); + }, + ] + : undefined, onRuntimeInitialized: (phpRuntime: PHPRuntime) => { /** * When users mount a directory using the `mount` function, @@ -287,24 +337,45 @@ export async function loadNodeRuntime( }, }; - if (options?.withXdebug) { - emscriptenOptions = await withXdebug( - phpVersion, - emscriptenOptions, - typeof options.withXdebug === 'object' ? options.withXdebug : {} - ); - } - - if (options?.withIntl === true) { - emscriptenOptions = await withIntl(phpVersion, emscriptenOptions); - } - - if (options?.withRedis === true) { - emscriptenOptions = await withRedis(phpVersion, emscriptenOptions); - } - - if (options?.withMemcached === true) { - emscriptenOptions = await withMemcached(phpVersion, emscriptenOptions); + if (isLegacy) { + if ( + options?.withXdebug || + options?.withIntl || + options?.withRedis || + options?.withMemcached + ) { + throw new Error( + `Extensions (xdebug, intl, redis, memcached) are not ` + + `available for legacy PHP ${phpVersion}.` + ); + } + } else { + const modernVersion = phpVersion as SupportedPHPVersion; + if (options?.withXdebug) { + emscriptenOptions = await withXdebug( + modernVersion, + emscriptenOptions, + typeof options.withXdebug === 'object' ? options.withXdebug : {} + ); + } + if (options?.withIntl === true) { + emscriptenOptions = await withIntl( + modernVersion, + emscriptenOptions + ); + } + if (options?.withRedis === true) { + emscriptenOptions = await withRedis( + modernVersion, + emscriptenOptions + ); + } + if (options?.withMemcached === true) { + emscriptenOptions = await withMemcached( + modernVersion, + emscriptenOptions + ); + } } emscriptenOptions = await withNetworking(emscriptenOptions); diff --git a/packages/php-wasm/supported-php-versions.mjs b/packages/php-wasm/supported-php-versions.mjs index b44f3767e04..98d7a5732aa 100644 --- a/packages/php-wasm/supported-php-versions.mjs +++ b/packages/php-wasm/supported-php-versions.mjs @@ -6,7 +6,7 @@ * @property {string} lastRelease */ -export const lastRefreshed = "2026-03-30T20:17:23.030Z"; +export const lastRefreshed = '2026-04-05T17:34:36.510Z'; /** * @type {PhpVersion[]} @@ -54,5 +54,11 @@ export const phpVersions = [ loaderFilename: 'php_7_4.js', wasmFilename: 'php_7_4.wasm', lastRelease: '7.4.33', - } + }, + { + version: '5.6', + loaderFilename: 'php_5_6.js', + wasmFilename: 'php_5_6.wasm', + lastRelease: '5.6.40', + }, ]; diff --git a/packages/php-wasm/universal/src/lib/index.ts b/packages/php-wasm/universal/src/lib/index.ts index 34a8c0b47e9..aa2f232545e 100644 --- a/packages/php-wasm/universal/src/lib/index.ts +++ b/packages/php-wasm/universal/src/lib/index.ts @@ -46,11 +46,17 @@ export { PHPResponse, StreamedPHPResponse } from './php-response'; export type { PHPResponseData } from './php-response'; export type { ErrnoError } from './rethrow-file-system-error'; export { + AllPHPVersions, LatestSupportedPHPVersion, + LegacyPHPVersions, SupportedPHPVersions, SupportedPHPVersionsList, } from './supported-php-versions'; -export type { SupportedPHPVersion } from './supported-php-versions'; +export type { + AllPHPVersion, + LegacyPHPVersion, + SupportedPHPVersion, +} from './supported-php-versions'; export { PHP, __private__dont__use, PHPExecutionFailureError } from './php'; export type { MountHandler, UnmountFunction } from './php'; export { loadPHPRuntime, popLoadedRuntime } from './load-php-runtime'; diff --git a/packages/php-wasm/universal/src/lib/proxy-file-system.ts b/packages/php-wasm/universal/src/lib/proxy-file-system.ts index 23d9c537790..6c581d0122d 100644 --- a/packages/php-wasm/universal/src/lib/proxy-file-system.ts +++ b/packages/php-wasm/universal/src/lib/proxy-file-system.ts @@ -146,12 +146,21 @@ function ensureProxyFSHasMmapSupport(phpInstance: PHP) { * @param sourceOfTruth - The PHP instance containing the original files * @param replica - The PHP instance that will access files through PROXYFS * @param paths - Absolute paths to mount (e.g., ['/wordpress', '/internal/shared']) + * @param options - Optional configuration + * @param options.withMmap - Whether to add mmap support to PROXYFS. Defaults + * to true. Set to false for PHP runtimes like 5.6 where the C-level mmap + * path in zend_compile_file trusts fstat for buffer sizing — if the proxied + * stat returns a stale size the parser reads garbage and reports spurious + * "syntax error at EOF". Without mmap, PHP falls back to a read-based path + * that handles stat/actual size mismatches gracefully. */ export async function proxyFileSystem( sourceOfTruth: PHP, replica: PHP, - paths: string[] + paths: string[], + options?: { withMmap?: boolean } ) { + const withMmap = options?.withMmap ?? true; // We can't just import the symbol from the library because // Playground CLI is built as ESM and php-wasm-node is built as // CJS and the imported symbols will differ in the production build. @@ -164,7 +173,9 @@ export async function proxyFileSystem( // after runtime rotation in hotSwapPHPRuntime(). replica.mkdir(path); await replica.mount(path, (php: PHP) => { - ensureProxyFSHasMmapSupport(php); + if (withMmap) { + ensureProxyFSHasMmapSupport(php); + } const replicaSymbol = Object.getOwnPropertySymbols(php)[0]; // @ts-ignore php[replicaSymbol].FS.mount( diff --git a/packages/php-wasm/universal/src/lib/single-php-instance-manager.ts b/packages/php-wasm/universal/src/lib/single-php-instance-manager.ts index 1f6fe39915e..08916310822 100644 --- a/packages/php-wasm/universal/src/lib/single-php-instance-manager.ts +++ b/packages/php-wasm/universal/src/lib/single-php-instance-manager.ts @@ -1,3 +1,4 @@ +import { Semaphore } from '@php-wasm/util'; import type { PHP } from './php'; import type { PHPInstanceManager, AcquiredPHP } from './php-instance-manager'; @@ -15,20 +16,23 @@ export interface SinglePHPInstanceManagerOptions { /** * A minimal PHP instance manager that manages a single PHP instance. * - * Unlike PHPProcessManager, this does not maintain a pool of instances - * or implement concurrency control. It simply returns the same PHP - * instance for every request. + * Unlike PHPProcessManager, this does not maintain a pool of instances. + * It returns the same PHP instance for every request, and serializes + * concurrent acquires through a 1-concurrency semaphore so the + * single instance handles requests one at a time. * - * This is suitable for CLI contexts where: - * - Only one PHP instance is needed - * - Runtime rotation is handled separately via php.enableRuntimeRotation() - * - Concurrency is not a concern (each worker has its own instance) + * This is suitable for: + * - CLI contexts where only one PHP instance is needed + * - Legacy PHP runtimes (e.g. 5.6) whose multi-instance support is + * unreliable and which must handle parallel requests on a single + * instance + * - Runtime rotation is handled separately via `php.enableRuntimeRotation()` */ export class SinglePHPInstanceManager implements PHPInstanceManager { private php: PHP | undefined; private phpPromise: Promise | undefined; private phpFactory?: () => Promise; - private isAcquired = false; + private semaphore = new Semaphore({ concurrency: 1 }); constructor(options: SinglePHPInstanceManagerOptions) { if (!options.php && !options.phpFactory) { @@ -55,19 +59,12 @@ export class SinglePHPInstanceManager implements PHPInstanceManager { } async acquirePHPInstance(): Promise { - if (this.isAcquired) { - throw new Error( - 'The PHP instance already acquired. SinglePHPInstanceManager cannot spawn another PHP instance since, by definition, it only manages a single PHP instance.' - ); - } + const release = await this.semaphore.acquire(); const php = await this.getPrimaryPhp(); - this.isAcquired = true; return { php, reap: () => { - // For single-instance manager, reap is a no-op. - // The instance is reused for all requests. - this.isAcquired = false; + release(); }, }; } diff --git a/packages/php-wasm/universal/src/lib/supported-php-versions.ts b/packages/php-wasm/universal/src/lib/supported-php-versions.ts index 64050475b1c..616708c7c66 100644 --- a/packages/php-wasm/universal/src/lib/supported-php-versions.ts +++ b/packages/php-wasm/universal/src/lib/supported-php-versions.ts @@ -10,3 +10,12 @@ export const SupportedPHPVersions = [ export const LatestSupportedPHPVersion = SupportedPHPVersions[0]; export const SupportedPHPVersionsList = SupportedPHPVersions as any as string[]; export type SupportedPHPVersion = (typeof SupportedPHPVersions)[number]; + +export const LegacyPHPVersions = ['5.6'] as const; +export type LegacyPHPVersion = (typeof LegacyPHPVersions)[number]; + +export const AllPHPVersions = [ + ...SupportedPHPVersions, + ...LegacyPHPVersions, +] as const; +export type AllPHPVersion = SupportedPHPVersion | LegacyPHPVersion; diff --git a/packages/php-wasm/web-builds/5-6/asyncify/php_5_6.js b/packages/php-wasm/web-builds/5-6/asyncify/php_5_6.js new file mode 100644 index 00000000000..c03433f31c5 --- /dev/null +++ b/packages/php-wasm/web-builds/5-6/asyncify/php_5_6.js @@ -0,0 +1,3 @@ +throw new Error( + 'PHP 5.6 web asyncify variant has not been compiled yet. Use the node variant instead.' +); diff --git a/packages/php-wasm/web-builds/5-6/build.js b/packages/php-wasm/web-builds/5-6/build.js new file mode 100644 index 00000000000..4d962141533 --- /dev/null +++ b/packages/php-wasm/web-builds/5-6/build.js @@ -0,0 +1,90 @@ +import esbuild from 'esbuild'; +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const projectRoot = path.resolve(__dirname, '..', '..', '..', '..'); +const packagePath = path.join(projectRoot, 'packages/php-wasm/web-builds/5-6'); +const distPath = path.join( + projectRoot, + 'dist/packages/php-wasm/web-builds/5-6' +); + +try { + fs.mkdirSync(distPath, { recursive: true }); +} catch (e) { + // Ignore +} + +/** + * Plugin to rewrite imports to work from the dist directory. + * Dynamic imports need to be preserved as external and paths adjusted. + */ +const externalPathPlugin = { + name: 'external-path', + setup(build) { + // Mark PHP loader files as external and rewrite their paths + build.onResolve( + { filter: /\.\.\/(?:jspi|asyncify)\/.*\.js$/ }, + (args) => { + const newPath = args.path.replace('../', './'); + return { path: newPath, external: true }; + } + ); + // Mark extension .so files as external and rewrite paths + build.onResolve( + { filter: /\.\.\/(?:jspi|asyncify)\/extensions\/.*\.so\?url$/ }, + (args) => { + const newPath = args.path.replace('../', './'); + return { path: newPath, external: true }; + } + ); + }, +}; + +async function build() { + // CommonJS build + await esbuild.build({ + entryPoints: [`${packagePath}/src/index.ts`], + supported: { 'dynamic-import': true }, + outExtension: { '.js': '.cjs' }, + outdir: distPath, + platform: 'browser', + assetNames: '[name]', + chunkNames: '[name]', + logOverride: { + 'direct-eval': 'silent', + 'commonjs-variable-in-esm': 'silent', + }, + format: 'cjs', + bundle: true, + tsconfig: `${packagePath}/tsconfig.json`, + external: ['@php-wasm/*', 'wasm-feature-detect'], + loader: { '.wasm': 'file', '.so': 'file' }, + plugins: [externalPathPlugin], + }); + + // ESM build + await esbuild.build({ + entryPoints: [`${packagePath}/src/index.ts`], + outdir: distPath, + platform: 'browser', + assetNames: '[name]', + chunkNames: '[name]', + logOverride: { + 'direct-eval': 'silent', + 'commonjs-variable-in-esm': 'silent', + }, + packages: 'external', + bundle: true, + tsconfig: `${packagePath}/tsconfig.json`, + external: ['@php-wasm/*', 'wasm-feature-detect'], + supported: { 'dynamic-import': true, 'top-level-await': true }, + format: 'esm', + loader: { '.wasm': 'file', '.so': 'file' }, + plugins: [externalPathPlugin], + }); +} +build(); diff --git a/packages/php-wasm/web-builds/5-6/jspi/5_6_40/php_5_6.wasm b/packages/php-wasm/web-builds/5-6/jspi/5_6_40/php_5_6.wasm new file mode 100755 index 00000000000..2f3e7744cd7 Binary files /dev/null and b/packages/php-wasm/web-builds/5-6/jspi/5_6_40/php_5_6.wasm differ diff --git a/packages/php-wasm/web-builds/5-6/jspi/php_5_6.js b/packages/php-wasm/web-builds/5-6/jspi/php_5_6.js new file mode 100644 index 00000000000..f70a6b611ef --- /dev/null +++ b/packages/php-wasm/web-builds/5-6/jspi/php_5_6.js @@ -0,0 +1,8246 @@ +import dependencyFilename from './5_6_40/php_5_6.wasm'; +export { dependencyFilename }; +export const dependenciesTotalSize = 11258507; +const phpVersionString = '5.6.40'; +export function init(RuntimeName, PHPLoader) { + // The rest of the code comes from the built php.js file and esm-suffix.js + var Module = typeof PHPLoader != 'undefined' ? PHPLoader : {}; + var ENVIRONMENT_IS_WEB = RuntimeName === 'WEB'; + var ENVIRONMENT_IS_WORKER = RuntimeName === 'WORKER'; + var ENVIRONMENT_IS_NODE = RuntimeName === 'NODE'; + var arguments_ = []; + var thisProgram = './this.program'; + var quit_ = (status, toThrow) => { + throw toThrow; + }; + var _scriptName = globalThis.document?.currentScript?.src; + if (ENVIRONMENT_IS_WORKER) { + _scriptName = self.location.href; + } + var scriptDirectory = ''; + function locateFile(path) { + if (Module['locateFile']) { + return Module['locateFile'](path, scriptDirectory); + } + return scriptDirectory + path; + } + var readAsync, readBinary; + if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) { + try { + scriptDirectory = new URL('.', _scriptName).href; + } catch {} + { + if (ENVIRONMENT_IS_WORKER) { + readBinary = (url) => { + var xhr = new XMLHttpRequest(); + xhr.open('GET', url, false); + xhr.responseType = 'arraybuffer'; + xhr.send(null); + return new Uint8Array(xhr.response); + }; + } + readAsync = async (url) => { + var response = await fetch(url, { credentials: 'same-origin' }); + if (response.ok) { + return response.arrayBuffer(); + } + throw new Error(response.status + ' : ' + response.url); + }; + } + } else { + } + var out = console.log.bind(console); + var err = console.error.bind(console); + var dynamicLibraries = []; + var wasmBinary; + var ABORT = false; + var EXITSTATUS; + var HEAP8, HEAPU8, HEAP16, HEAPU16, HEAP32, HEAPU32, HEAPF32, HEAPF64; + var HEAP64, HEAPU64; + var runtimeInitialized = false; + var runtimeExited = false; + function updateMemoryViews() { + var b = wasmMemory.buffer; + HEAP8 = new Int8Array(b); + HEAP16 = new Int16Array(b); + Module['HEAPU8'] = HEAPU8 = new Uint8Array(b); + HEAPU16 = new Uint16Array(b); + HEAP32 = new Int32Array(b); + Module['HEAPU32'] = HEAPU32 = new Uint32Array(b); + HEAPF32 = new Float32Array(b); + HEAPF64 = new Float64Array(b); + HEAP64 = new BigInt64Array(b); + HEAPU64 = new BigUint64Array(b); + } + var __RELOC_FUNCS__ = []; + function preRun() { + if (Module['preRun']) { + if (typeof Module['preRun'] == 'function') + Module['preRun'] = [Module['preRun']]; + while (Module['preRun'].length) { + addOnPreRun(Module['preRun'].shift()); + } + } + callRuntimeCallbacks(onPreRuns); + } + function initRuntime() { + runtimeInitialized = true; + callRuntimeCallbacks(__RELOC_FUNCS__); + callRuntimeCallbacks(onInits); + if (!Module['noFSInit'] && !FS.initialized) FS.init(); + TTY.init(); + SOCKFS.root = FS.mount(SOCKFS, {}, null); + PIPEFS.root = FS.mount(PIPEFS, {}, null); + wasmExports['__wasm_call_ctors'](); + callRuntimeCallbacks(onPostCtors); + FS.ignorePermissions = false; + } + function preMain() {} + function exitRuntime() { + ___funcs_on_exit(); + FS.quit(); + TTY.shutdown(); + runtimeExited = true; + } + function postRun() { + if (Module['postRun']) { + if (typeof Module['postRun'] == 'function') + Module['postRun'] = [Module['postRun']]; + while (Module['postRun'].length) { + addOnPostRun(Module['postRun'].shift()); + } + } + callRuntimeCallbacks(onPostRuns); + } + function abort(what) { + Module['onAbort']?.(what); + what = 'Aborted(' + what + ')'; + err(what); + ABORT = true; + what += '. Build with -sASSERTIONS for more info.'; + if (runtimeInitialized) { + ___trap(); + } + var e = new WebAssembly.RuntimeError(what); + throw e; + } + var wasmBinaryFile; + function findWasmBinary() { + return locateFile(dependencyFilename); + } + function getBinarySync(file) { + if (file == wasmBinaryFile && wasmBinary) { + return new Uint8Array(wasmBinary); + } + if (readBinary) { + return readBinary(file); + } + throw 'both async and sync fetching of the wasm failed'; + } + async function getWasmBinary(binaryFile) { + if (!wasmBinary) { + try { + var response = await readAsync(binaryFile); + return new Uint8Array(response); + } catch {} + } + return getBinarySync(binaryFile); + } + async function instantiateArrayBuffer(binaryFile, imports) { + try { + var binary = await getWasmBinary(binaryFile); + var instance = await WebAssembly.instantiate(binary, imports); + return instance; + } catch (reason) { + err(`failed to asynchronously prepare wasm: ${reason}`); + abort(reason); + } + } + async function instantiateAsync(binary, binaryFile, imports) { + if (!binary) { + try { + var response = fetch(binaryFile, { + credentials: 'same-origin', + }); + var instantiationResult = + await WebAssembly.instantiateStreaming(response, imports); + return instantiationResult; + } catch (reason) { + err(`wasm streaming compile failed: ${reason}`); + err('falling back to ArrayBuffer instantiation'); + } + } + return instantiateArrayBuffer(binaryFile, imports); + } + function getWasmImports() { + Asyncify.instrumentWasmImports(wasmImports); + var imports = { + env: wasmImports, + wasi_snapshot_preview1: wasmImports, + 'GOT.mem': new Proxy(wasmImports, GOTHandler), + 'GOT.func': new Proxy(wasmImports, GOTHandler), + }; + return imports; + } + async function createWasm() { + function receiveInstance(instance, module) { + wasmExports = instance.exports; + var origExports = (wasmExports = relocateExports(wasmExports)); + wasmExports = Asyncify.instrumentWasmExports(wasmExports); + mergeLibSymbols(wasmExports, 'main'); + var metadata = getDylinkMetadata(module); + if (metadata.neededDynlibs) { + dynamicLibraries = + metadata.neededDynlibs.concat(dynamicLibraries); + } + assignWasmExports(wasmExports); + updateGOT(origExports); + Module['wasmExports'] = wasmExports; + LDSO.init(); + loadDylibs(); + updateMemoryViews(); + removeRunDependency('wasm-instantiate'); + return wasmExports; + } + addRunDependency('wasm-instantiate'); + function receiveInstantiationResult(result) { + return receiveInstance(result['instance'], result['module']); + } + var info = getWasmImports(); + if (Module['instantiateWasm']) { + return new Promise((resolve, reject) => { + Module['instantiateWasm'](info, (inst, mod) => { + resolve(receiveInstance(inst, mod)); + }); + }); + } + wasmBinaryFile ??= findWasmBinary(); + var result = await instantiateAsync(wasmBinary, wasmBinaryFile, info); + var exports = receiveInstantiationResult(result); + return exports; + } + var asyncifyStubs = {}; + class ExitStatus { + name = 'ExitStatus'; + constructor(status) { + this.message = `Program terminated with exit(${status})`; + this.status = status; + } + } + ExitStatus = class PHPExitStatus extends Error { + constructor(status) { + super(status); + this.name = 'ExitStatus'; + this.message = 'Program terminated with exit(' + status + ')'; + this.status = status; + } + }; + var GOT = {}; + var currentModuleWeakSymbols = new Set([]); + var GOTHandler = { + get(obj, symName) { + var rtn = GOT[symName]; + if (!rtn) { + rtn = GOT[symName] = new WebAssembly.Global( + { value: 'i32', mutable: true }, + -1 + ); + } + if (!currentModuleWeakSymbols.has(symName)) { + rtn.required = true; + } + return rtn; + }, + }; + var callRuntimeCallbacks = (callbacks) => { + while (callbacks.length > 0) { + callbacks.shift()(Module); + } + }; + var onPostRuns = []; + var addOnPostRun = (cb) => onPostRuns.push(cb); + var onPreRuns = []; + var addOnPreRun = (cb) => onPreRuns.push(cb); + var runDependencies = 0; + var dependenciesFulfilled = null; + var removeRunDependency = (id) => { + runDependencies--; + Module['monitorRunDependencies']?.(runDependencies); + if (runDependencies == 0) { + if (dependenciesFulfilled) { + var callback = dependenciesFulfilled; + dependenciesFulfilled = null; + callback(); + } + } + }; + var addRunDependency = (id) => { + runDependencies++; + Module['monitorRunDependencies']?.(runDependencies); + }; + var UTF8Decoder = globalThis.TextDecoder && new TextDecoder(); + var findStringEnd = (heapOrArray, idx, maxBytesToRead, ignoreNul) => { + var maxIdx = idx + maxBytesToRead; + if (ignoreNul) return maxIdx; + while (heapOrArray[idx] && !(idx >= maxIdx)) ++idx; + return idx; + }; + var UTF8ArrayToString = ( + heapOrArray, + idx = 0, + maxBytesToRead, + ignoreNul + ) => { + var endPtr = findStringEnd(heapOrArray, idx, maxBytesToRead, ignoreNul); + if (endPtr - idx > 16 && heapOrArray.buffer && UTF8Decoder) { + return UTF8Decoder.decode(heapOrArray.subarray(idx, endPtr)); + } + var str = ''; + while (idx < endPtr) { + var u0 = heapOrArray[idx++]; + if (!(u0 & 128)) { + str += String.fromCharCode(u0); + continue; + } + var u1 = heapOrArray[idx++] & 63; + if ((u0 & 224) == 192) { + str += String.fromCharCode(((u0 & 31) << 6) | u1); + continue; + } + var u2 = heapOrArray[idx++] & 63; + if ((u0 & 240) == 224) { + u0 = ((u0 & 15) << 12) | (u1 << 6) | u2; + } else { + u0 = + ((u0 & 7) << 18) | + (u1 << 12) | + (u2 << 6) | + (heapOrArray[idx++] & 63); + } + if (u0 < 65536) { + str += String.fromCharCode(u0); + } else { + var ch = u0 - 65536; + str += String.fromCharCode( + 55296 | (ch >> 10), + 56320 | (ch & 1023) + ); + } + } + return str; + }; + var getDylinkMetadata = (binary) => { + var offset = 0; + var end = 0; + function getU8() { + return binary[offset++]; + } + function getLEB() { + var ret = 0; + var mul = 1; + while (1) { + var byte = binary[offset++]; + ret += (byte & 127) * mul; + mul *= 128; + if (!(byte & 128)) break; + } + return ret; + } + function getString() { + var len = getLEB(); + offset += len; + return UTF8ArrayToString(binary, offset - len, len); + } + function getStringList() { + var count = getLEB(); + var rtn = []; + while (count--) rtn.push(getString()); + return rtn; + } + function failIf(condition, message) { + if (condition) throw new Error(message); + } + if (binary instanceof WebAssembly.Module) { + var dylinkSection = WebAssembly.Module.customSections( + binary, + 'dylink.0' + ); + failIf(dylinkSection.length === 0, 'need dylink section'); + binary = new Uint8Array(dylinkSection[0]); + end = binary.length; + } else { + var int32View = new Uint32Array( + new Uint8Array(binary.subarray(0, 24)).buffer + ); + var magicNumberFound = int32View[0] == 1836278016; + failIf(!magicNumberFound, 'need to see wasm magic number'); + failIf(binary[8] !== 0, 'need the dylink section to be first'); + offset = 9; + var section_size = getLEB(); + end = offset + section_size; + var name = getString(); + failIf(name !== 'dylink.0'); + } + var customSection = { + neededDynlibs: [], + tlsExports: new Set(), + weakImports: new Set(), + runtimePaths: [], + }; + var WASM_DYLINK_MEM_INFO = 1; + var WASM_DYLINK_NEEDED = 2; + var WASM_DYLINK_EXPORT_INFO = 3; + var WASM_DYLINK_IMPORT_INFO = 4; + var WASM_DYLINK_RUNTIME_PATH = 5; + var WASM_SYMBOL_TLS = 256; + var WASM_SYMBOL_BINDING_MASK = 3; + var WASM_SYMBOL_BINDING_WEAK = 1; + while (offset < end) { + var subsectionType = getU8(); + var subsectionSize = getLEB(); + if (subsectionType === WASM_DYLINK_MEM_INFO) { + customSection.memorySize = getLEB(); + customSection.memoryAlign = getLEB(); + customSection.tableSize = getLEB(); + customSection.tableAlign = getLEB(); + } else if (subsectionType === WASM_DYLINK_NEEDED) { + customSection.neededDynlibs = getStringList(); + } else if (subsectionType === WASM_DYLINK_EXPORT_INFO) { + var count = getLEB(); + while (count--) { + var symname = getString(); + var flags = getLEB(); + if (flags & WASM_SYMBOL_TLS) { + customSection.tlsExports.add(symname); + } + } + } else if (subsectionType === WASM_DYLINK_IMPORT_INFO) { + var count = getLEB(); + while (count--) { + var modname = getString(); + var symname = getString(); + var flags = getLEB(); + if ( + (flags & WASM_SYMBOL_BINDING_MASK) == + WASM_SYMBOL_BINDING_WEAK + ) { + customSection.weakImports.add(symname); + } + } + } else if (subsectionType === WASM_DYLINK_RUNTIME_PATH) { + customSection.runtimePaths = getStringList(); + } else { + offset += subsectionSize; + } + } + return customSection; + }; + var newDSO = (name, handle, syms) => { + var dso = { refcount: Infinity, name, exports: syms, global: true }; + LDSO.loadedLibsByName[name] = dso; + if (handle != undefined) { + LDSO.loadedLibsByHandle[handle] = dso; + } + return dso; + }; + var LDSO = { + loadedLibsByName: {}, + loadedLibsByHandle: {}, + init() { + newDSO('__main__', 0, wasmImports); + }, + }; + var alignMemory = (size, alignment) => + Math.ceil(size / alignment) * alignment; + var getMemory = (size) => { + if (runtimeInitialized) { + return _calloc(size, 1); + } + var ret = ___heap_base; + var end = ret + alignMemory(size, 16); + ___heap_base = end; + var sbrk_ptr = _emscripten_get_sbrk_ptr(); + HEAPU32[sbrk_ptr >> 2] = end; + return ret; + }; + var isInternalSym = (symName) => + [ + 'memory', + '__memory_base', + '__table_base', + '__stack_pointer', + '__indirect_function_table', + '__cpp_exception', + '__c_longjmp', + '__wasm_apply_data_relocs', + '__dso_handle', + '__tls_size', + '__tls_align', + '__set_stack_limits', + '_emscripten_tls_init', + '__wasm_init_tls', + '__wasm_call_ctors', + '__start_em_asm', + '__stop_em_asm', + '__start_em_js', + '__stop_em_js', + ].includes(symName) || symName.startsWith('__em_js__'); + var wasmTableMirror = []; + var getWasmTableEntry = (funcPtr) => { + var func = wasmTableMirror[funcPtr]; + if (!func) { + wasmTableMirror[funcPtr] = func = wasmTable.get(funcPtr); + if (Asyncify.isAsyncExport(func)) { + wasmTableMirror[funcPtr] = func = + Asyncify.makeAsyncFunction(func); + } + } + return func; + }; + var updateTableMap = (offset, count) => { + if (functionsInTableMap) { + for (var i = offset; i < offset + count; i++) { + var item = getWasmTableEntry(i); + if (item) { + functionsInTableMap.set(item, i); + } + } + } + }; + var functionsInTableMap; + var getFunctionAddress = (func) => { + if (!functionsInTableMap) { + functionsInTableMap = new WeakMap(); + updateTableMap(0, wasmTable.length); + } + return functionsInTableMap.get(func) || 0; + }; + var freeTableIndexes = []; + var getEmptyTableSlot = () => { + if (freeTableIndexes.length) { + return freeTableIndexes.pop(); + } + return wasmTable['grow'](1); + }; + var setWasmTableEntry = (idx, func) => { + wasmTable.set(idx, func); + wasmTableMirror[idx] = wasmTable.get(idx); + }; + var uleb128EncodeWithLen = (arr) => { + const n = arr.length; + return [(n % 128) | 128, n >> 7, ...arr]; + }; + var wasmTypeCodes = { i: 127, p: 127, j: 126, f: 125, d: 124, e: 111 }; + var generateTypePack = (types) => + uleb128EncodeWithLen( + Array.from(types, (type) => { + var code = wasmTypeCodes[type]; + return code; + }) + ); + var convertJsFunctionToWasm = (func, sig) => { + var bytes = Uint8Array.of( + 0, + 97, + 115, + 109, + 1, + 0, + 0, + 0, + 1, + ...uleb128EncodeWithLen([ + 1, + 96, + ...generateTypePack(sig.slice(1)), + ...generateTypePack(sig[0] === 'v' ? '' : sig[0]), + ]), + 2, + 7, + 1, + 1, + 101, + 1, + 102, + 0, + 0, + 7, + 5, + 1, + 1, + 102, + 0, + 0 + ); + var module = new WebAssembly.Module(bytes); + var instance = new WebAssembly.Instance(module, { e: { f: func } }); + var wrappedFunc = instance.exports['f']; + return wrappedFunc; + }; + var addFunction = (func, sig) => { + var rtn = getFunctionAddress(func); + if (rtn) { + return rtn; + } + var ret = getEmptyTableSlot(); + try { + setWasmTableEntry(ret, func); + } catch (err) { + if (!(err instanceof TypeError)) { + throw err; + } + var wrapped = convertJsFunctionToWasm(func, sig); + setWasmTableEntry(ret, wrapped); + } + functionsInTableMap.set(func, ret); + return ret; + }; + var updateGOT = (exports, replace) => { + for (var symName in exports) { + if (isInternalSym(symName)) { + continue; + } + var value = exports[symName]; + var existingEntry = GOT[symName] && GOT[symName].value != -1; + if (replace || !existingEntry) { + var newValue; + if (typeof value == 'function') { + newValue = addFunction(value); + } else if (typeof value == 'number') { + newValue = value; + } else { + continue; + } + GOT[symName] ??= new WebAssembly.Global({ + value: 'i32', + mutable: true, + }); + GOT[symName].value = newValue; + } + } + }; + var isImmutableGlobal = (val) => { + if (val instanceof WebAssembly.Global) { + try { + val.value = val.value; + } catch { + return true; + } + } + return false; + }; + var relocateExports = (exports, memoryBase = 0) => { + function relocateExport(name, value) { + if (isImmutableGlobal(value)) { + return value.value + memoryBase; + } + return value; + } + var relocated = {}; + for (var e in exports) { + relocated[e] = relocateExport(e, exports[e]); + } + return relocated; + }; + var isSymbolDefined = (symName) => { + var existing = wasmImports[symName]; + if (!existing || existing.stub) { + return false; + } + if (symName in asyncifyStubs && !asyncifyStubs[symName]) { + return false; + } + return true; + }; + var resolveGlobalSymbol = (symName, direct = false) => { + var sym; + if (isSymbolDefined(symName)) { + sym = wasmImports[symName]; + } + return { sym, name: symName }; + }; + var onPostCtors = []; + var addOnPostCtor = (cb) => onPostCtors.push(cb); + var UTF8ToString = (ptr, maxBytesToRead, ignoreNul) => + ptr ? UTF8ArrayToString(HEAPU8, ptr, maxBytesToRead, ignoreNul) : ''; + var loadWebAssemblyModule = ( + binary, + flags, + libName, + localScope, + handle + ) => { + var metadata = getDylinkMetadata(binary); + function loadModule() { + var memAlign = Math.pow(2, metadata.memoryAlign); + var memoryBase = metadata.memorySize + ? alignMemory( + getMemory(metadata.memorySize + memAlign), + memAlign + ) + : 0; + var tableBase = metadata.tableSize ? wasmTable.length : 0; + if (handle) { + HEAP8[handle + 8] = 1; + HEAPU32[(handle + 12) >> 2] = memoryBase; + HEAP32[(handle + 16) >> 2] = metadata.memorySize; + HEAPU32[(handle + 20) >> 2] = tableBase; + HEAP32[(handle + 24) >> 2] = metadata.tableSize; + } + if (metadata.tableSize) { + wasmTable.grow(metadata.tableSize); + } + var moduleExports; + function resolveSymbol(sym) { + var resolved = resolveGlobalSymbol(sym).sym; + if (!resolved && localScope) { + resolved = localScope[sym]; + } + if (!resolved) { + resolved = moduleExports[sym]; + } + return resolved; + } + var proxyHandler = { + get(stubs, prop) { + switch (prop) { + case '__memory_base': + return memoryBase; + case '__table_base': + return tableBase; + } + if (prop in wasmImports && !wasmImports[prop].stub) { + var res = wasmImports[prop]; + if (res.orig) { + res = res.orig; + } + return res; + } + if (!(prop in stubs)) { + var resolved; + stubs[prop] = (...args) => { + resolved ||= resolveSymbol(prop); + return resolved(...args); + }; + } + return stubs[prop]; + }, + }; + var proxy = new Proxy({}, proxyHandler); + currentModuleWeakSymbols = metadata.weakImports; + var info = { + 'GOT.mem': new Proxy({}, GOTHandler), + 'GOT.func': new Proxy({}, GOTHandler), + env: proxy, + wasi_snapshot_preview1: proxy, + }; + function postInstantiation(module, instance) { + updateTableMap(tableBase, metadata.tableSize); + moduleExports = relocateExports(instance.exports, memoryBase); + updateGOT(moduleExports); + moduleExports = Asyncify.instrumentWasmExports(moduleExports); + if (!flags.allowUndefined) { + reportUndefinedSymbols(); + } + function addEmAsm(addr, body) { + var args = []; + for (var arity = 0; ; arity++) { + var argName = '$' + arity; + if (!body.includes(argName)) break; + args.push(argName); + } + args = args.join(','); + var func = `(${args}) => { ${body} };`; + ASM_CONSTS[start] = eval(func); + } + if ('__start_em_asm' in moduleExports) { + var start = moduleExports['__start_em_asm']; + var stop = moduleExports['__stop_em_asm']; + while (start < stop) { + var jsString = UTF8ToString(start); + addEmAsm(start, jsString); + start = HEAPU8.indexOf(0, start) + 1; + } + } + function addEmJs(name, cSig, body) { + var jsArgs = []; + cSig = cSig.slice(1, -1); + if (cSig != 'void') { + cSig = cSig.split(','); + for (var arg of cSig) { + var jsArg = arg.split(' ').pop(); + jsArgs.push(jsArg.replace('*', '')); + } + } + var func = `(${jsArgs}) => ${body};`; + moduleExports[name] = eval(func); + } + for (var name in moduleExports) { + if (name.startsWith('__em_js__')) { + var start = moduleExports[name]; + var jsString = UTF8ToString(start); + var [sig, body] = jsString.split('<::>'); + addEmJs(name.replace('__em_js__', ''), sig, body); + delete moduleExports[name]; + } + } + var applyRelocs = moduleExports['__wasm_apply_data_relocs']; + if (applyRelocs) { + if (runtimeInitialized) { + applyRelocs(); + } else { + __RELOC_FUNCS__.push(applyRelocs); + } + } + var init = moduleExports['__wasm_call_ctors']; + if (init) { + if (runtimeInitialized) { + init(); + } else { + addOnPostCtor(init); + } + } + return moduleExports; + } + if (flags.loadAsync) { + return (async () => { + var instance; + if (binary instanceof WebAssembly.Module) { + instance = new WebAssembly.Instance(binary, info); + } else { + ({ module: binary, instance } = + await WebAssembly.instantiate(binary, info)); + } + return postInstantiation(binary, instance); + })(); + } + var module = + binary instanceof WebAssembly.Module + ? binary + : new WebAssembly.Module(binary); + var instance = new WebAssembly.Instance(module, info); + return postInstantiation(module, instance); + } + flags = { + ...flags, + rpath: { parentLibPath: libName, paths: metadata.runtimePaths }, + }; + if (flags.loadAsync) { + return metadata.neededDynlibs + .reduce( + (chain, dynNeeded) => + chain.then(() => + loadDynamicLibrary(dynNeeded, flags, localScope) + ), + Promise.resolve() + ) + .then(loadModule); + } + for (var needed of metadata.neededDynlibs) { + loadDynamicLibrary(needed, flags, localScope); + } + return loadModule(); + }; + var mergeLibSymbols = (exports, libName) => { + for (var [sym, exp] of Object.entries(exports)) { + const setImport = (target) => { + if (target in asyncifyStubs) { + asyncifyStubs[target] = exp; + } + if (!isSymbolDefined(target)) { + wasmImports[target] = exp; + } + }; + setImport(sym); + const main_alias = '__main_argc_argv'; + if (sym == 'main') { + setImport(main_alias); + } + if (sym == main_alias) { + setImport('main'); + } + } + }; + var asyncLoad = async (url) => { + var arrayBuffer = await readAsync(url); + return new Uint8Array(arrayBuffer); + }; + var preloadPlugins = []; + var registerWasmPlugin = () => { + var wasmPlugin = { + promiseChainEnd: Promise.resolve(), + canHandle: (name) => + !Module['noWasmDecoding'] && name.endsWith('.so'), + handle: async (byteArray, name) => + (wasmPlugin.promiseChainEnd = wasmPlugin.promiseChainEnd.then( + async () => { + try { + var exports = await loadWebAssemblyModule( + byteArray, + { loadAsync: true, nodelete: true }, + name, + {} + ); + } catch (error) { + throw new Error( + `failed to instantiate wasm: ${name}: ${error}` + ); + } + preloadedWasm[name] = exports; + return byteArray; + } + )), + }; + preloadPlugins.push(wasmPlugin); + }; + var preloadedWasm = {}; + var PATH = { + isAbs: (path) => path.charAt(0) === '/', + splitPath: (filename) => { + var splitPathRe = + /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/; + return splitPathRe.exec(filename).slice(1); + }, + normalizeArray: (parts, allowAboveRoot) => { + var up = 0; + for (var i = parts.length - 1; i >= 0; i--) { + var last = parts[i]; + if (last === '.') { + parts.splice(i, 1); + } else if (last === '..') { + parts.splice(i, 1); + up++; + } else if (up) { + parts.splice(i, 1); + up--; + } + } + if (allowAboveRoot) { + for (; up; up--) { + parts.unshift('..'); + } + } + return parts; + }, + normalize: (path) => { + var isAbsolute = PATH.isAbs(path), + trailingSlash = path.slice(-1) === '/'; + path = PATH.normalizeArray( + path.split('/').filter((p) => !!p), + !isAbsolute + ).join('/'); + if (!path && !isAbsolute) { + path = '.'; + } + if (path && trailingSlash) { + path += '/'; + } + return (isAbsolute ? '/' : '') + path; + }, + dirname: (path) => { + var result = PATH.splitPath(path), + root = result[0], + dir = result[1]; + if (!root && !dir) { + return '.'; + } + if (dir) { + dir = dir.slice(0, -1); + } + return root + dir; + }, + basename: (path) => path && path.match(/([^\/]+|\/)\/*$/)[1], + join: (...paths) => PATH.normalize(paths.join('/')), + join2: (l, r) => PATH.normalize(l + '/' + r), + }; + var replaceORIGIN = (parentLibName, rpath) => { + if (rpath.startsWith('$ORIGIN')) { + var origin = PATH.dirname(parentLibName); + return rpath.replace('$ORIGIN', origin); + } + return rpath; + }; + var stackSave = () => _emscripten_stack_get_current(); + var stackRestore = (val) => __emscripten_stack_restore(val); + var withStackSave = (f) => { + var stack = stackSave(); + var ret = f(); + stackRestore(stack); + return ret; + }; + var stackAlloc = (sz) => __emscripten_stack_alloc(sz); + var lengthBytesUTF8 = (str) => { + var len = 0; + for (var i = 0; i < str.length; ++i) { + var c = str.charCodeAt(i); + if (c <= 127) { + len++; + } else if (c <= 2047) { + len += 2; + } else if (c >= 55296 && c <= 57343) { + len += 4; + ++i; + } else { + len += 3; + } + } + return len; + }; + var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { + if (!(maxBytesToWrite > 0)) return 0; + var startIdx = outIdx; + var endIdx = outIdx + maxBytesToWrite - 1; + for (var i = 0; i < str.length; ++i) { + var u = str.codePointAt(i); + if (u <= 127) { + if (outIdx >= endIdx) break; + heap[outIdx++] = u; + } else if (u <= 2047) { + if (outIdx + 1 >= endIdx) break; + heap[outIdx++] = 192 | (u >> 6); + heap[outIdx++] = 128 | (u & 63); + } else if (u <= 65535) { + if (outIdx + 2 >= endIdx) break; + heap[outIdx++] = 224 | (u >> 12); + heap[outIdx++] = 128 | ((u >> 6) & 63); + heap[outIdx++] = 128 | (u & 63); + } else { + if (outIdx + 3 >= endIdx) break; + heap[outIdx++] = 240 | (u >> 18); + heap[outIdx++] = 128 | ((u >> 12) & 63); + heap[outIdx++] = 128 | ((u >> 6) & 63); + heap[outIdx++] = 128 | (u & 63); + i++; + } + } + heap[outIdx] = 0; + return outIdx - startIdx; + }; + var stringToUTF8 = (str, outPtr, maxBytesToWrite) => + stringToUTF8Array(str, HEAPU8, outPtr, maxBytesToWrite); + var stringToUTF8OnStack = (str) => { + var size = lengthBytesUTF8(str) + 1; + var ret = stackAlloc(size); + stringToUTF8(str, ret, size); + return ret; + }; + var initRandomFill = () => (view) => crypto.getRandomValues(view); + var randomFill = (view) => { + (randomFill = initRandomFill())(view); + }; + var PATH_FS = { + resolve: (...args) => { + var resolvedPath = '', + resolvedAbsolute = false; + for (var i = args.length - 1; i >= -1 && !resolvedAbsolute; i--) { + var path = i >= 0 ? args[i] : FS.cwd(); + if (typeof path != 'string') { + throw new TypeError( + 'Arguments to path.resolve must be strings' + ); + } else if (!path) { + return ''; + } + resolvedPath = path + '/' + resolvedPath; + resolvedAbsolute = PATH.isAbs(path); + } + resolvedPath = PATH.normalizeArray( + resolvedPath.split('/').filter((p) => !!p), + !resolvedAbsolute + ).join('/'); + return (resolvedAbsolute ? '/' : '') + resolvedPath || '.'; + }, + relative: (from, to) => { + from = PATH_FS.resolve(from).slice(1); + to = PATH_FS.resolve(to).slice(1); + function trim(arr) { + var start = 0; + for (; start < arr.length; start++) { + if (arr[start] !== '') break; + } + var end = arr.length - 1; + for (; end >= 0; end--) { + if (arr[end] !== '') break; + } + if (start > end) return []; + return arr.slice(start, end - start + 1); + } + var fromParts = trim(from.split('/')); + var toParts = trim(to.split('/')); + var length = Math.min(fromParts.length, toParts.length); + var samePartsLength = length; + for (var i = 0; i < length; i++) { + if (fromParts[i] !== toParts[i]) { + samePartsLength = i; + break; + } + } + var outputParts = []; + for (var i = samePartsLength; i < fromParts.length; i++) { + outputParts.push('..'); + } + outputParts = outputParts.concat(toParts.slice(samePartsLength)); + return outputParts.join('/'); + }, + }; + var FS_stdin_getChar_buffer = []; + var intArrayFromString = (stringy, dontAddNull, length) => { + var len = length > 0 ? length : lengthBytesUTF8(stringy) + 1; + var u8array = new Array(len); + var numBytesWritten = stringToUTF8Array( + stringy, + u8array, + 0, + u8array.length + ); + if (dontAddNull) u8array.length = numBytesWritten; + return u8array; + }; + var FS_stdin_getChar = () => { + if (!FS_stdin_getChar_buffer.length) { + var result = null; + if (globalThis.window?.prompt) { + result = window.prompt('Input: '); + if (result !== null) { + result += '\n'; + } + } else { + } + if (!result) { + return null; + } + FS_stdin_getChar_buffer = intArrayFromString(result, true); + } + return FS_stdin_getChar_buffer.shift(); + }; + var TTY = { + ttys: [], + init() {}, + shutdown() {}, + register(dev, ops) { + TTY.ttys[dev] = { input: [], output: [], ops }; + FS.registerDevice(dev, TTY.stream_ops); + }, + stream_ops: { + open(stream) { + var tty = TTY.ttys[stream.node.rdev]; + if (!tty) { + throw new FS.ErrnoError(43); + } + stream.tty = tty; + stream.seekable = false; + }, + close(stream) { + stream.tty.ops.fsync(stream.tty); + }, + fsync(stream) { + stream.tty.ops.fsync(stream.tty); + }, + read(stream, buffer, offset, length, pos) { + if (!stream.tty || !stream.tty.ops.get_char) { + throw new FS.ErrnoError(60); + } + var bytesRead = 0; + for (var i = 0; i < length; i++) { + var result; + try { + result = stream.tty.ops.get_char(stream.tty); + } catch (e) { + throw new FS.ErrnoError(29); + } + if (result === undefined && bytesRead === 0) { + throw new FS.ErrnoError(6); + } + if (result === null || result === undefined) break; + bytesRead++; + buffer[offset + i] = result; + } + if (bytesRead) { + stream.node.atime = Date.now(); + } + return bytesRead; + }, + write(stream, buffer, offset, length, pos) { + if (!stream.tty || !stream.tty.ops.put_char) { + throw new FS.ErrnoError(60); + } + try { + for (var i = 0; i < length; i++) { + stream.tty.ops.put_char(stream.tty, buffer[offset + i]); + } + } catch (e) { + throw new FS.ErrnoError(29); + } + if (length) { + stream.node.mtime = stream.node.ctime = Date.now(); + } + return i; + }, + }, + default_tty_ops: { + get_char(tty) { + return FS_stdin_getChar(); + }, + put_char(tty, val) { + if (val === null || val === 10) { + out(UTF8ArrayToString(tty.output)); + tty.output = []; + } else { + if (val != 0) tty.output.push(val); + } + }, + fsync(tty) { + if (tty.output?.length > 0) { + out(UTF8ArrayToString(tty.output)); + tty.output = []; + } + }, + ioctl_tcgets(tty) { + return { + c_iflag: 25856, + c_oflag: 5, + c_cflag: 191, + c_lflag: 35387, + c_cc: [ + 3, 28, 127, 21, 4, 0, 1, 0, 17, 19, 26, 0, 18, 15, 23, + 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ], + }; + }, + ioctl_tcsets(tty, optional_actions, data) { + return 0; + }, + ioctl_tiocgwinsz(tty) { + return [24, 80]; + }, + }, + default_tty1_ops: { + put_char(tty, val) { + if (val === null || val === 10) { + err(UTF8ArrayToString(tty.output)); + tty.output = []; + } else { + if (val != 0) tty.output.push(val); + } + }, + fsync(tty) { + if (tty.output?.length > 0) { + err(UTF8ArrayToString(tty.output)); + tty.output = []; + } + }, + }, + }; + var zeroMemory = (ptr, size) => HEAPU8.fill(0, ptr, ptr + size); + var mmapAlloc = (size) => { + size = alignMemory(size, 65536); + var ptr = _emscripten_builtin_memalign(65536, size); + if (ptr) zeroMemory(ptr, size); + return ptr; + }; + var MEMFS = { + ops_table: null, + mount(mount) { + return MEMFS.createNode(null, '/', 16895, 0); + }, + createNode(parent, name, mode, dev) { + if (FS.isBlkdev(mode) || FS.isFIFO(mode)) { + throw new FS.ErrnoError(63); + } + MEMFS.ops_table ||= { + dir: { + node: { + getattr: MEMFS.node_ops.getattr, + setattr: MEMFS.node_ops.setattr, + lookup: MEMFS.node_ops.lookup, + mknod: MEMFS.node_ops.mknod, + rename: MEMFS.node_ops.rename, + unlink: MEMFS.node_ops.unlink, + rmdir: MEMFS.node_ops.rmdir, + readdir: MEMFS.node_ops.readdir, + symlink: MEMFS.node_ops.symlink, + }, + stream: { llseek: MEMFS.stream_ops.llseek }, + }, + file: { + node: { + getattr: MEMFS.node_ops.getattr, + setattr: MEMFS.node_ops.setattr, + }, + stream: { + llseek: MEMFS.stream_ops.llseek, + read: MEMFS.stream_ops.read, + write: MEMFS.stream_ops.write, + mmap: MEMFS.stream_ops.mmap, + msync: MEMFS.stream_ops.msync, + }, + }, + link: { + node: { + getattr: MEMFS.node_ops.getattr, + setattr: MEMFS.node_ops.setattr, + readlink: MEMFS.node_ops.readlink, + }, + stream: {}, + }, + chrdev: { + node: { + getattr: MEMFS.node_ops.getattr, + setattr: MEMFS.node_ops.setattr, + }, + stream: FS.chrdev_stream_ops, + }, + }; + var node = FS.createNode(parent, name, mode, dev); + if (FS.isDir(node.mode)) { + node.node_ops = MEMFS.ops_table.dir.node; + node.stream_ops = MEMFS.ops_table.dir.stream; + node.contents = {}; + } else if (FS.isFile(node.mode)) { + node.node_ops = MEMFS.ops_table.file.node; + node.stream_ops = MEMFS.ops_table.file.stream; + node.usedBytes = 0; + node.contents = null; + } else if (FS.isLink(node.mode)) { + node.node_ops = MEMFS.ops_table.link.node; + node.stream_ops = MEMFS.ops_table.link.stream; + } else if (FS.isChrdev(node.mode)) { + node.node_ops = MEMFS.ops_table.chrdev.node; + node.stream_ops = MEMFS.ops_table.chrdev.stream; + } + node.atime = node.mtime = node.ctime = Date.now(); + if (parent) { + parent.contents[name] = node; + parent.atime = parent.mtime = parent.ctime = node.atime; + } + return node; + }, + getFileDataAsTypedArray(node) { + if (!node.contents) return new Uint8Array(0); + if (node.contents.subarray) + return node.contents.subarray(0, node.usedBytes); + return new Uint8Array(node.contents); + }, + expandFileStorage(node, newCapacity) { + var prevCapacity = node.contents ? node.contents.length : 0; + if (prevCapacity >= newCapacity) return; + var CAPACITY_DOUBLING_MAX = 1024 * 1024; + newCapacity = Math.max( + newCapacity, + (prevCapacity * + (prevCapacity < CAPACITY_DOUBLING_MAX ? 2 : 1.125)) >>> + 0 + ); + if (prevCapacity != 0) newCapacity = Math.max(newCapacity, 256); + var oldContents = node.contents; + node.contents = new Uint8Array(newCapacity); + if (node.usedBytes > 0) + node.contents.set(oldContents.subarray(0, node.usedBytes), 0); + }, + resizeFileStorage(node, newSize) { + if (node.usedBytes == newSize) return; + if (newSize == 0) { + node.contents = null; + node.usedBytes = 0; + } else { + var oldContents = node.contents; + node.contents = new Uint8Array(newSize); + if (oldContents) { + node.contents.set( + oldContents.subarray( + 0, + Math.min(newSize, node.usedBytes) + ) + ); + } + node.usedBytes = newSize; + } + }, + node_ops: { + getattr(node) { + var attr = {}; + attr.dev = FS.isChrdev(node.mode) ? node.id : 1; + attr.ino = node.id; + attr.mode = node.mode; + attr.nlink = 1; + attr.uid = 0; + attr.gid = 0; + attr.rdev = node.rdev; + if (FS.isDir(node.mode)) { + attr.size = 4096; + } else if (FS.isFile(node.mode)) { + attr.size = node.usedBytes; + } else if (FS.isLink(node.mode)) { + attr.size = node.link.length; + } else { + attr.size = 0; + } + attr.atime = new Date(node.atime); + attr.mtime = new Date(node.mtime); + attr.ctime = new Date(node.ctime); + attr.blksize = 4096; + attr.blocks = Math.ceil(attr.size / attr.blksize); + return attr; + }, + setattr(node, attr) { + for (const key of ['mode', 'atime', 'mtime', 'ctime']) { + if (attr[key] != null) { + node[key] = attr[key]; + } + } + if (attr.size !== undefined) { + MEMFS.resizeFileStorage(node, attr.size); + } + }, + lookup(parent, name) { + if (!MEMFS.doesNotExistError) { + MEMFS.doesNotExistError = new FS.ErrnoError(44); + MEMFS.doesNotExistError.stack = ''; + } + throw MEMFS.doesNotExistError; + }, + mknod(parent, name, mode, dev) { + return MEMFS.createNode(parent, name, mode, dev); + }, + rename(old_node, new_dir, new_name) { + var new_node; + try { + new_node = FS.lookupNode(new_dir, new_name); + } catch (e) {} + if (new_node) { + if (FS.isDir(old_node.mode)) { + for (var i in new_node.contents) { + throw new FS.ErrnoError(55); + } + } + FS.hashRemoveNode(new_node); + } + delete old_node.parent.contents[old_node.name]; + new_dir.contents[new_name] = old_node; + old_node.name = new_name; + new_dir.ctime = + new_dir.mtime = + old_node.parent.ctime = + old_node.parent.mtime = + Date.now(); + }, + unlink(parent, name) { + delete parent.contents[name]; + parent.ctime = parent.mtime = Date.now(); + }, + rmdir(parent, name) { + var node = FS.lookupNode(parent, name); + for (var i in node.contents) { + throw new FS.ErrnoError(55); + } + delete parent.contents[name]; + parent.ctime = parent.mtime = Date.now(); + }, + readdir(node) { + return ['.', '..', ...Object.keys(node.contents)]; + }, + symlink(parent, newname, oldpath) { + var node = MEMFS.createNode(parent, newname, 511 | 40960, 0); + node.link = oldpath; + return node; + }, + readlink(node) { + if (!FS.isLink(node.mode)) { + throw new FS.ErrnoError(28); + } + return node.link; + }, + }, + stream_ops: { + read(stream, buffer, offset, length, position) { + var contents = stream.node.contents; + if (position >= stream.node.usedBytes) return 0; + var size = Math.min(stream.node.usedBytes - position, length); + if (size > 8 && contents.subarray) { + buffer.set( + contents.subarray(position, position + size), + offset + ); + } else { + for (var i = 0; i < size; i++) + buffer[offset + i] = contents[position + i]; + } + return size; + }, + write(stream, buffer, offset, length, position, canOwn) { + if (buffer.buffer === HEAP8.buffer) { + canOwn = false; + } + if (!length) return 0; + var node = stream.node; + node.mtime = node.ctime = Date.now(); + if ( + buffer.subarray && + (!node.contents || node.contents.subarray) + ) { + if (canOwn) { + node.contents = buffer.subarray( + offset, + offset + length + ); + node.usedBytes = length; + return length; + } else if (node.usedBytes === 0 && position === 0) { + node.contents = buffer.slice(offset, offset + length); + node.usedBytes = length; + return length; + } else if (position + length <= node.usedBytes) { + node.contents.set( + buffer.subarray(offset, offset + length), + position + ); + return length; + } + } + MEMFS.expandFileStorage(node, position + length); + if (node.contents.subarray && buffer.subarray) { + node.contents.set( + buffer.subarray(offset, offset + length), + position + ); + } else { + for (var i = 0; i < length; i++) { + node.contents[position + i] = buffer[offset + i]; + } + } + node.usedBytes = Math.max(node.usedBytes, position + length); + return length; + }, + llseek(stream, offset, whence) { + var position = offset; + if (whence === 1) { + position += stream.position; + } else if (whence === 2) { + if (FS.isFile(stream.node.mode)) { + position += stream.node.usedBytes; + } + } + if (position < 0) { + throw new FS.ErrnoError(28); + } + return position; + }, + mmap(stream, length, position, prot, flags) { + if (!FS.isFile(stream.node.mode)) { + throw new FS.ErrnoError(43); + } + var ptr; + var allocated; + var contents = stream.node.contents; + if ( + !(flags & 2) && + contents && + contents.buffer === HEAP8.buffer + ) { + allocated = false; + ptr = contents.byteOffset; + } else { + allocated = true; + ptr = mmapAlloc(length); + if (!ptr) { + throw new FS.ErrnoError(48); + } + if (contents) { + if ( + position > 0 || + position + length < contents.length + ) { + if (contents.subarray) { + contents = contents.subarray( + position, + position + length + ); + } else { + contents = Array.prototype.slice.call( + contents, + position, + position + length + ); + } + } + HEAP8.set(contents, ptr); + } + } + return { ptr, allocated }; + }, + msync(stream, buffer, offset, length, mmapFlags) { + MEMFS.stream_ops.write( + stream, + buffer, + 0, + length, + offset, + false + ); + return 0; + }, + }, + }; + var FS_modeStringToFlags = (str) => { + var flagModes = { + r: 0, + 'r+': 2, + w: 512 | 64 | 1, + 'w+': 512 | 64 | 2, + a: 1024 | 64 | 1, + 'a+': 1024 | 64 | 2, + }; + var flags = flagModes[str]; + if (typeof flags == 'undefined') { + throw new Error(`Unknown file open mode: ${str}`); + } + return flags; + }; + var FS_getMode = (canRead, canWrite) => { + var mode = 0; + if (canRead) mode |= 292 | 73; + if (canWrite) mode |= 146; + return mode; + }; + var ERRNO_CODES = { + EPERM: 63, + ENOENT: 44, + ESRCH: 71, + EINTR: 27, + EIO: 29, + ENXIO: 60, + E2BIG: 1, + ENOEXEC: 45, + EBADF: 8, + ECHILD: 12, + EAGAIN: 6, + EWOULDBLOCK: 6, + ENOMEM: 48, + EACCES: 2, + EFAULT: 21, + ENOTBLK: 105, + EBUSY: 10, + EEXIST: 20, + EXDEV: 75, + ENODEV: 43, + ENOTDIR: 54, + EISDIR: 31, + EINVAL: 28, + ENFILE: 41, + EMFILE: 33, + ENOTTY: 59, + ETXTBSY: 74, + EFBIG: 22, + ENOSPC: 51, + ESPIPE: 70, + EROFS: 69, + EMLINK: 34, + EPIPE: 64, + EDOM: 18, + ERANGE: 68, + ENOMSG: 49, + EIDRM: 24, + ECHRNG: 106, + EL2NSYNC: 156, + EL3HLT: 107, + EL3RST: 108, + ELNRNG: 109, + EUNATCH: 110, + ENOCSI: 111, + EL2HLT: 112, + EDEADLK: 16, + ENOLCK: 46, + EBADE: 113, + EBADR: 114, + EXFULL: 115, + ENOANO: 104, + EBADRQC: 103, + EBADSLT: 102, + EDEADLOCK: 16, + EBFONT: 101, + ENOSTR: 100, + ENODATA: 116, + ETIME: 117, + ENOSR: 118, + ENONET: 119, + ENOPKG: 120, + EREMOTE: 121, + ENOLINK: 47, + EADV: 122, + ESRMNT: 123, + ECOMM: 124, + EPROTO: 65, + EMULTIHOP: 36, + EDOTDOT: 125, + EBADMSG: 9, + ENOTUNIQ: 126, + EBADFD: 127, + EREMCHG: 128, + ELIBACC: 129, + ELIBBAD: 130, + ELIBSCN: 131, + ELIBMAX: 132, + ELIBEXEC: 133, + ENOSYS: 52, + ENOTEMPTY: 55, + ENAMETOOLONG: 37, + ELOOP: 32, + EOPNOTSUPP: 138, + EPFNOSUPPORT: 139, + ECONNRESET: 15, + ENOBUFS: 42, + EAFNOSUPPORT: 5, + EPROTOTYPE: 67, + ENOTSOCK: 57, + ENOPROTOOPT: 50, + ESHUTDOWN: 140, + ECONNREFUSED: 14, + EADDRINUSE: 3, + ECONNABORTED: 13, + ENETUNREACH: 40, + ENETDOWN: 38, + ETIMEDOUT: 73, + EHOSTDOWN: 142, + EHOSTUNREACH: 23, + EINPROGRESS: 26, + EALREADY: 7, + EDESTADDRREQ: 17, + EMSGSIZE: 35, + EPROTONOSUPPORT: 66, + ESOCKTNOSUPPORT: 137, + EADDRNOTAVAIL: 4, + ENETRESET: 39, + EISCONN: 30, + ENOTCONN: 53, + ETOOMANYREFS: 141, + EUSERS: 136, + EDQUOT: 19, + ESTALE: 72, + ENOTSUP: 138, + ENOMEDIUM: 148, + EILSEQ: 25, + EOVERFLOW: 61, + ECANCELED: 11, + ENOTRECOVERABLE: 56, + EOWNERDEAD: 62, + ESTRPIPE: 135, + }; + var PROXYFS = { + mount(mount) { + return PROXYFS.createNode( + null, + '/', + mount.opts.fs.lstat(mount.opts.root).mode, + 0 + ); + }, + createNode(parent, name, mode, dev) { + if (!FS.isDir(mode) && !FS.isFile(mode) && !FS.isLink(mode)) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + var node = FS.createNode(parent, name, mode); + node.node_ops = PROXYFS.node_ops; + node.stream_ops = PROXYFS.stream_ops; + return node; + }, + realPath(node) { + var parts = []; + while (node.parent !== node) { + parts.push(node.name); + node = node.parent; + } + parts.push(node.mount.opts.root); + parts.reverse(); + return PATH.join(...parts); + }, + node_ops: { + getattr(node) { + var path = PROXYFS.realPath(node); + var stat; + try { + stat = node.mount.opts.fs.lstat(path); + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + return { + dev: stat.dev, + ino: stat.ino, + mode: stat.mode, + nlink: stat.nlink, + uid: stat.uid, + gid: stat.gid, + rdev: stat.rdev, + size: stat.size, + atime: stat.atime, + mtime: stat.mtime, + ctime: stat.ctime, + blksize: stat.blksize, + blocks: stat.blocks, + }; + }, + setattr(node, attr) { + var path = PROXYFS.realPath(node); + try { + if (attr.mode !== undefined) { + node.mount.opts.fs.chmod(path, attr.mode); + node.mode = attr.mode; + } + if (attr.atime || attr.mtime) { + var atime = new Date(attr.atime || attr.mtime); + var mtime = new Date(attr.mtime || attr.atime); + node.mount.opts.fs.utime(path, atime, mtime); + } + if (attr.size !== undefined) { + node.mount.opts.fs.truncate(path, attr.size); + } + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + lookup(parent, name) { + try { + var path = PATH.join2(PROXYFS.realPath(parent), name); + var mode = parent.mount.opts.fs.lstat(path).mode; + var node = PROXYFS.createNode(parent, name, mode); + return node; + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + mknod(parent, name, mode, dev) { + var node = PROXYFS.createNode(parent, name, mode, dev); + var path = PROXYFS.realPath(node); + try { + if (FS.isDir(node.mode)) { + node.mount.opts.fs.mkdir(path, node.mode); + } else { + node.mount.opts.fs.writeFile(path, '', { + mode: node.mode, + }); + } + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + return node; + }, + rename(oldNode, newDir, newName) { + var oldPath = PROXYFS.realPath(oldNode); + var newPath = PATH.join2(PROXYFS.realPath(newDir), newName); + try { + oldNode.mount.opts.fs.rename(oldPath, newPath); + oldNode.name = newName; + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + unlink(parent, name) { + var path = PATH.join2(PROXYFS.realPath(parent), name); + try { + parent.mount.opts.fs.unlink(path); + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + rmdir(parent, name) { + var path = PATH.join2(PROXYFS.realPath(parent), name); + try { + parent.mount.opts.fs.rmdir(path); + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + readdir(node) { + var path = PROXYFS.realPath(node); + try { + return node.mount.opts.fs.readdir(path); + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + symlink(parent, newName, oldPath) { + var newPath = PATH.join2(PROXYFS.realPath(parent), newName); + try { + parent.mount.opts.fs.symlink(oldPath, newPath); + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + readlink(node) { + var path = PROXYFS.realPath(node); + try { + return node.mount.opts.fs.readlink(path); + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + }, + stream_ops: { + open(stream) { + var path = PROXYFS.realPath(stream.node); + try { + stream.nfd = stream.node.mount.opts.fs.open( + path, + stream.flags + ); + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + close(stream) { + try { + stream.node.mount.opts.fs.close(stream.nfd); + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + read(stream, buffer, offset, length, position) { + try { + return stream.node.mount.opts.fs.read( + stream.nfd, + buffer, + offset, + length, + position + ); + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + write(stream, buffer, offset, length, position) { + try { + return stream.node.mount.opts.fs.write( + stream.nfd, + buffer, + offset, + length, + position + ); + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + llseek(stream, offset, whence) { + var position = offset; + if (whence === 1) { + position += stream.position; + } else if (whence === 2) { + if (FS.isFile(stream.node.mode)) { + try { + var stat = stream.node.node_ops.getattr( + stream.node + ); + position += stat.size; + } catch (e) { + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + } + } + if (position < 0) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + return position; + }, + }, + }; + var FS_createDataFile = (...args) => FS.createDataFile(...args); + var getUniqueRunDependency = (id) => id; + var FS_handledByPreloadPlugin = async (byteArray, fullname) => { + if (typeof Browser != 'undefined') Browser.init(); + for (var plugin of preloadPlugins) { + if (plugin['canHandle'](fullname)) { + return plugin['handle'](byteArray, fullname); + } + } + return byteArray; + }; + var FS_preloadFile = async ( + parent, + name, + url, + canRead, + canWrite, + dontCreateFile, + canOwn, + preFinish + ) => { + var fullname = name + ? PATH_FS.resolve(PATH.join2(parent, name)) + : parent; + var dep = getUniqueRunDependency(`cp ${fullname}`); + addRunDependency(dep); + try { + var byteArray = url; + if (typeof url == 'string') { + byteArray = await asyncLoad(url); + } + byteArray = await FS_handledByPreloadPlugin(byteArray, fullname); + preFinish?.(); + if (!dontCreateFile) { + FS_createDataFile( + parent, + name, + byteArray, + canRead, + canWrite, + canOwn + ); + } + } finally { + removeRunDependency(dep); + } + }; + var FS_createPreloadedFile = ( + parent, + name, + url, + canRead, + canWrite, + onload, + onerror, + dontCreateFile, + canOwn, + preFinish + ) => { + FS_preloadFile( + parent, + name, + url, + canRead, + canWrite, + dontCreateFile, + canOwn, + preFinish + ) + .then(onload) + .catch(onerror); + }; + var FS = { + root: null, + mounts: [], + devices: {}, + streams: [], + nextInode: 1, + nameTable: null, + currentPath: '/', + initialized: false, + ignorePermissions: true, + filesystems: null, + syncFSRequests: 0, + readFiles: {}, + ErrnoError: class { + name = 'ErrnoError'; + constructor(errno) { + this.errno = errno; + } + }, + FSStream: class { + shared = {}; + get object() { + return this.node; + } + set object(val) { + this.node = val; + } + get isRead() { + return (this.flags & 2097155) !== 1; + } + get isWrite() { + return (this.flags & 2097155) !== 0; + } + get isAppend() { + return this.flags & 1024; + } + get flags() { + return this.shared.flags; + } + set flags(val) { + this.shared.flags = val; + } + get position() { + return this.shared.position; + } + set position(val) { + this.shared.position = val; + } + }, + FSNode: class { + node_ops = {}; + stream_ops = {}; + readMode = 292 | 73; + writeMode = 146; + mounted = null; + constructor(parent, name, mode, rdev) { + if (!parent) { + parent = this; + } + this.parent = parent; + this.mount = parent.mount; + this.id = FS.nextInode++; + this.name = name; + this.mode = mode; + this.rdev = rdev; + this.atime = this.mtime = this.ctime = Date.now(); + } + get read() { + return (this.mode & this.readMode) === this.readMode; + } + set read(val) { + val + ? (this.mode |= this.readMode) + : (this.mode &= ~this.readMode); + } + get write() { + return (this.mode & this.writeMode) === this.writeMode; + } + set write(val) { + val + ? (this.mode |= this.writeMode) + : (this.mode &= ~this.writeMode); + } + get isFolder() { + return FS.isDir(this.mode); + } + get isDevice() { + return FS.isChrdev(this.mode); + } + }, + lookupPath(path, opts = {}) { + if (!path) { + throw new FS.ErrnoError(44); + } + opts.follow_mount ??= true; + if (!PATH.isAbs(path)) { + path = FS.cwd() + '/' + path; + } + linkloop: for (var nlinks = 0; nlinks < 40; nlinks++) { + var parts = path.split('/').filter((p) => !!p); + var current = FS.root; + var current_path = '/'; + for (var i = 0; i < parts.length; i++) { + var islast = i === parts.length - 1; + if (islast && opts.parent) { + break; + } + if (parts[i] === '.') { + continue; + } + if (parts[i] === '..') { + current_path = PATH.dirname(current_path); + if (FS.isRoot(current)) { + path = + current_path + + '/' + + parts.slice(i + 1).join('/'); + nlinks--; + continue linkloop; + } else { + current = current.parent; + } + continue; + } + current_path = PATH.join2(current_path, parts[i]); + try { + current = FS.lookupNode(current, parts[i]); + } catch (e) { + if (e?.errno === 44 && islast && opts.noent_okay) { + return { path: current_path }; + } + throw e; + } + if ( + FS.isMountpoint(current) && + (!islast || opts.follow_mount) + ) { + current = current.mounted.root; + } + if (FS.isLink(current.mode) && (!islast || opts.follow)) { + if (!current.node_ops.readlink) { + throw new FS.ErrnoError(52); + } + var link = current.node_ops.readlink(current); + if (!PATH.isAbs(link)) { + link = PATH.dirname(current_path) + '/' + link; + } + path = link + '/' + parts.slice(i + 1).join('/'); + continue linkloop; + } + } + return { path: current_path, node: current }; + } + throw new FS.ErrnoError(32); + }, + getPath(node) { + var path; + while (true) { + if (FS.isRoot(node)) { + var mount = node.mount.mountpoint; + if (!path) return mount; + return mount[mount.length - 1] !== '/' + ? `${mount}/${path}` + : mount + path; + } + path = path ? `${node.name}/${path}` : node.name; + node = node.parent; + } + }, + hashName(parentid, name) { + var hash = 0; + for (var i = 0; i < name.length; i++) { + hash = ((hash << 5) - hash + name.charCodeAt(i)) | 0; + } + return ((parentid + hash) >>> 0) % FS.nameTable.length; + }, + hashAddNode(node) { + var hash = FS.hashName(node.parent.id, node.name); + node.name_next = FS.nameTable[hash]; + FS.nameTable[hash] = node; + }, + hashRemoveNode(node) { + var hash = FS.hashName(node.parent.id, node.name); + if (FS.nameTable[hash] === node) { + FS.nameTable[hash] = node.name_next; + } else { + var current = FS.nameTable[hash]; + while (current) { + if (current.name_next === node) { + current.name_next = node.name_next; + break; + } + current = current.name_next; + } + } + }, + lookupNode(parent, name) { + var errCode = FS.mayLookup(parent); + if (errCode) { + throw new FS.ErrnoError(errCode); + } + var hash = FS.hashName(parent.id, name); + for (var node = FS.nameTable[hash]; node; node = node.name_next) { + var nodeName = node.name; + if (node.parent.id === parent.id && nodeName === name) { + return node; + } + } + return FS.lookup(parent, name); + }, + createNode(parent, name, mode, rdev) { + var node = new FS.FSNode(parent, name, mode, rdev); + FS.hashAddNode(node); + return node; + }, + destroyNode(node) { + FS.hashRemoveNode(node); + }, + isRoot(node) { + return node === node.parent; + }, + isMountpoint(node) { + return !!node.mounted; + }, + isFile(mode) { + return (mode & 61440) === 32768; + }, + isDir(mode) { + return (mode & 61440) === 16384; + }, + isLink(mode) { + return (mode & 61440) === 40960; + }, + isChrdev(mode) { + return (mode & 61440) === 8192; + }, + isBlkdev(mode) { + return (mode & 61440) === 24576; + }, + isFIFO(mode) { + return (mode & 61440) === 4096; + }, + isSocket(mode) { + return (mode & 49152) === 49152; + }, + flagsToPermissionString(flag) { + var perms = ['r', 'w', 'rw'][flag & 3]; + if (flag & 512) { + perms += 'w'; + } + return perms; + }, + nodePermissions(node, perms) { + if (FS.ignorePermissions) { + return 0; + } + if (perms.includes('r') && !(node.mode & 292)) { + return 2; + } else if (perms.includes('w') && !(node.mode & 146)) { + return 2; + } else if (perms.includes('x') && !(node.mode & 73)) { + return 2; + } + return 0; + }, + mayLookup(dir) { + if (!FS.isDir(dir.mode)) return 54; + var errCode = FS.nodePermissions(dir, 'x'); + if (errCode) return errCode; + if (!dir.node_ops.lookup) return 2; + return 0; + }, + mayCreate(dir, name) { + if (!FS.isDir(dir.mode)) { + return 54; + } + try { + var node = FS.lookupNode(dir, name); + return 20; + } catch (e) {} + return FS.nodePermissions(dir, 'wx'); + }, + mayDelete(dir, name, isdir) { + var node; + try { + node = FS.lookupNode(dir, name); + } catch (e) { + return e.errno; + } + var errCode = FS.nodePermissions(dir, 'wx'); + if (errCode) { + return errCode; + } + if (isdir) { + if (!FS.isDir(node.mode)) { + return 54; + } + if (FS.isRoot(node) || FS.getPath(node) === FS.cwd()) { + return 10; + } + } else { + if (FS.isDir(node.mode)) { + return 31; + } + } + return 0; + }, + mayOpen(node, flags) { + if (!node) { + return 44; + } + if (FS.isLink(node.mode)) { + return 32; + } else if (FS.isDir(node.mode)) { + if ( + FS.flagsToPermissionString(flags) !== 'r' || + flags & (512 | 64) + ) { + return 31; + } + } + return FS.nodePermissions(node, FS.flagsToPermissionString(flags)); + }, + checkOpExists(op, err) { + if (!op) { + throw new FS.ErrnoError(err); + } + return op; + }, + MAX_OPEN_FDS: 4096, + nextfd() { + for (var fd = 0; fd <= FS.MAX_OPEN_FDS; fd++) { + if (!FS.streams[fd]) { + return fd; + } + } + throw new FS.ErrnoError(33); + }, + getStreamChecked(fd) { + var stream = FS.getStream(fd); + if (!stream) { + throw new FS.ErrnoError(8); + } + return stream; + }, + getStream: (fd) => FS.streams[fd], + createStream(stream, fd = -1) { + stream = Object.assign(new FS.FSStream(), stream); + if (fd == -1) { + fd = FS.nextfd(); + } + stream.fd = fd; + FS.streams[fd] = stream; + return stream; + }, + closeStream(fd) { + FS.streams[fd] = null; + }, + dupStream(origStream, fd = -1) { + var stream = FS.createStream(origStream, fd); + stream.stream_ops?.dup?.(stream); + return stream; + }, + doSetAttr(stream, node, attr) { + var setattr = stream?.stream_ops.setattr; + var arg = setattr ? stream : node; + setattr ??= node.node_ops.setattr; + FS.checkOpExists(setattr, 63); + setattr(arg, attr); + }, + chrdev_stream_ops: { + open(stream) { + var device = FS.getDevice(stream.node.rdev); + stream.stream_ops = device.stream_ops; + stream.stream_ops.open?.(stream); + }, + llseek() { + throw new FS.ErrnoError(70); + }, + }, + major: (dev) => dev >> 8, + minor: (dev) => dev & 255, + makedev: (ma, mi) => (ma << 8) | mi, + registerDevice(dev, ops) { + FS.devices[dev] = { stream_ops: ops }; + }, + getDevice: (dev) => FS.devices[dev], + getMounts(mount) { + var mounts = []; + var check = [mount]; + while (check.length) { + var m = check.pop(); + mounts.push(m); + check.push(...m.mounts); + } + return mounts; + }, + syncfs(populate, callback) { + if (typeof populate == 'function') { + callback = populate; + populate = false; + } + FS.syncFSRequests++; + if (FS.syncFSRequests > 1) { + err( + `warning: ${FS.syncFSRequests} FS.syncfs operations in flight at once, probably just doing extra work` + ); + } + var mounts = FS.getMounts(FS.root.mount); + var completed = 0; + function doCallback(errCode) { + FS.syncFSRequests--; + return callback(errCode); + } + function done(errCode) { + if (errCode) { + if (!done.errored) { + done.errored = true; + return doCallback(errCode); + } + return; + } + if (++completed >= mounts.length) { + doCallback(null); + } + } + for (var mount of mounts) { + if (mount.type.syncfs) { + mount.type.syncfs(mount, populate, done); + } else { + done(null); + } + } + }, + mount(type, opts, mountpoint) { + var root = mountpoint === '/'; + var pseudo = !mountpoint; + var node; + if (root && FS.root) { + throw new FS.ErrnoError(10); + } else if (!root && !pseudo) { + var lookup = FS.lookupPath(mountpoint, { follow_mount: false }); + mountpoint = lookup.path; + node = lookup.node; + if (FS.isMountpoint(node)) { + throw new FS.ErrnoError(10); + } + } + var mount = { type, opts, mountpoint, mounts: [] }; + var mountRoot = type.mount(mount); + mountRoot.mount = mount; + mount.root = mountRoot; + if (root) { + FS.root = mountRoot; + } else if (node) { + node.mounted = mount; + if (node.mount) { + node.mount.mounts.push(mount); + } + } + return mountRoot; + }, + unmount(mountpoint) { + var lookup = FS.lookupPath(mountpoint, { follow_mount: false }); + if (!FS.isMountpoint(lookup.node)) { + throw new FS.ErrnoError(28); + } + var node = lookup.node; + var mount = node.mounted; + var mounts = FS.getMounts(mount); + for (var [hash, current] of Object.entries(FS.nameTable)) { + while (current) { + var next = current.name_next; + if (mounts.includes(current.mount)) { + FS.destroyNode(current); + } + current = next; + } + } + node.mounted = null; + var idx = node.mount.mounts.indexOf(mount); + node.mount.mounts.splice(idx, 1); + }, + lookup(parent, name) { + return parent.node_ops.lookup(parent, name); + }, + mknod(path, mode, dev) { + var lookup = FS.lookupPath(path, { parent: true }); + var parent = lookup.node; + var name = PATH.basename(path); + if (!name) { + throw new FS.ErrnoError(28); + } + if (name === '.' || name === '..') { + throw new FS.ErrnoError(20); + } + var errCode = FS.mayCreate(parent, name); + if (errCode) { + throw new FS.ErrnoError(errCode); + } + if (!parent.node_ops.mknod) { + throw new FS.ErrnoError(63); + } + return parent.node_ops.mknod(parent, name, mode, dev); + }, + statfs(path) { + return FS.statfsNode(FS.lookupPath(path, { follow: true }).node); + }, + statfsStream(stream) { + return FS.statfsNode(stream.node); + }, + statfsNode(node) { + var rtn = { + bsize: 4096, + frsize: 4096, + blocks: 1e6, + bfree: 5e5, + bavail: 5e5, + files: FS.nextInode, + ffree: FS.nextInode - 1, + fsid: 42, + flags: 2, + namelen: 255, + }; + if (node.node_ops.statfs) { + Object.assign(rtn, node.node_ops.statfs(node.mount.opts.root)); + } + return rtn; + }, + create(path, mode = 438) { + mode &= 4095; + mode |= 32768; + return FS.mknod(path, mode, 0); + }, + mkdir(path, mode = 511) { + mode &= 511 | 512; + mode |= 16384; + return FS.mknod(path, mode, 0); + }, + mkdirTree(path, mode) { + var dirs = path.split('/'); + var d = ''; + for (var dir of dirs) { + if (!dir) continue; + if (d || PATH.isAbs(path)) d += '/'; + d += dir; + try { + FS.mkdir(d, mode); + } catch (e) { + if (e.errno != 20) throw e; + } + } + }, + mkdev(path, mode, dev) { + if (typeof dev == 'undefined') { + dev = mode; + mode = 438; + } + mode |= 8192; + return FS.mknod(path, mode, dev); + }, + symlink(oldpath, newpath) { + if (!PATH_FS.resolve(oldpath)) { + throw new FS.ErrnoError(44); + } + var lookup = FS.lookupPath(newpath, { parent: true }); + var parent = lookup.node; + if (!parent) { + throw new FS.ErrnoError(44); + } + var newname = PATH.basename(newpath); + var errCode = FS.mayCreate(parent, newname); + if (errCode) { + throw new FS.ErrnoError(errCode); + } + if (!parent.node_ops.symlink) { + throw new FS.ErrnoError(63); + } + return parent.node_ops.symlink(parent, newname, oldpath); + }, + rename(old_path, new_path) { + var old_dirname = PATH.dirname(old_path); + var new_dirname = PATH.dirname(new_path); + var old_name = PATH.basename(old_path); + var new_name = PATH.basename(new_path); + var lookup, old_dir, new_dir; + lookup = FS.lookupPath(old_path, { parent: true }); + old_dir = lookup.node; + lookup = FS.lookupPath(new_path, { parent: true }); + new_dir = lookup.node; + if (!old_dir || !new_dir) throw new FS.ErrnoError(44); + if (old_dir.mount !== new_dir.mount) { + throw new FS.ErrnoError(75); + } + var old_node = FS.lookupNode(old_dir, old_name); + var relative = PATH_FS.relative(old_path, new_dirname); + if (relative.charAt(0) !== '.') { + throw new FS.ErrnoError(28); + } + relative = PATH_FS.relative(new_path, old_dirname); + if (relative.charAt(0) !== '.') { + throw new FS.ErrnoError(55); + } + var new_node; + try { + new_node = FS.lookupNode(new_dir, new_name); + } catch (e) {} + if (old_node === new_node) { + return; + } + var isdir = FS.isDir(old_node.mode); + var errCode = FS.mayDelete(old_dir, old_name, isdir); + if (errCode) { + throw new FS.ErrnoError(errCode); + } + errCode = new_node + ? FS.mayDelete(new_dir, new_name, isdir) + : FS.mayCreate(new_dir, new_name); + if (errCode) { + throw new FS.ErrnoError(errCode); + } + if (!old_dir.node_ops.rename) { + throw new FS.ErrnoError(63); + } + if ( + FS.isMountpoint(old_node) || + (new_node && FS.isMountpoint(new_node)) + ) { + throw new FS.ErrnoError(10); + } + if (new_dir !== old_dir) { + errCode = FS.nodePermissions(old_dir, 'w'); + if (errCode) { + throw new FS.ErrnoError(errCode); + } + } + FS.hashRemoveNode(old_node); + try { + old_dir.node_ops.rename(old_node, new_dir, new_name); + old_node.parent = new_dir; + } catch (e) { + throw e; + } finally { + FS.hashAddNode(old_node); + } + }, + rmdir(path) { + var lookup = FS.lookupPath(path, { parent: true }); + var parent = lookup.node; + var name = PATH.basename(path); + var node = FS.lookupNode(parent, name); + var errCode = FS.mayDelete(parent, name, true); + if (errCode) { + throw new FS.ErrnoError(errCode); + } + if (!parent.node_ops.rmdir) { + throw new FS.ErrnoError(63); + } + if (FS.isMountpoint(node)) { + throw new FS.ErrnoError(10); + } + parent.node_ops.rmdir(parent, name); + FS.destroyNode(node); + }, + readdir(path) { + var lookup = FS.lookupPath(path, { follow: true }); + var node = lookup.node; + var readdir = FS.checkOpExists(node.node_ops.readdir, 54); + return readdir(node); + }, + unlink(path) { + var lookup = FS.lookupPath(path, { parent: true }); + var parent = lookup.node; + if (!parent) { + throw new FS.ErrnoError(44); + } + var name = PATH.basename(path); + var node = FS.lookupNode(parent, name); + var errCode = FS.mayDelete(parent, name, false); + if (errCode) { + throw new FS.ErrnoError(errCode); + } + if (!parent.node_ops.unlink) { + throw new FS.ErrnoError(63); + } + if (FS.isMountpoint(node)) { + throw new FS.ErrnoError(10); + } + parent.node_ops.unlink(parent, name); + FS.destroyNode(node); + }, + readlink(path) { + var lookup = FS.lookupPath(path); + var link = lookup.node; + if (!link) { + throw new FS.ErrnoError(44); + } + if (!link.node_ops.readlink) { + throw new FS.ErrnoError(28); + } + return link.node_ops.readlink(link); + }, + stat(path, dontFollow) { + var lookup = FS.lookupPath(path, { follow: !dontFollow }); + var node = lookup.node; + var getattr = FS.checkOpExists(node.node_ops.getattr, 63); + return getattr(node); + }, + fstat(fd) { + var stream = FS.getStreamChecked(fd); + var node = stream.node; + var getattr = stream.stream_ops.getattr; + var arg = getattr ? stream : node; + getattr ??= node.node_ops.getattr; + FS.checkOpExists(getattr, 63); + return getattr(arg); + }, + lstat(path) { + return FS.stat(path, true); + }, + doChmod(stream, node, mode, dontFollow) { + FS.doSetAttr(stream, node, { + mode: (mode & 4095) | (node.mode & ~4095), + ctime: Date.now(), + dontFollow, + }); + }, + chmod(path, mode, dontFollow) { + var node; + if (typeof path == 'string') { + var lookup = FS.lookupPath(path, { follow: !dontFollow }); + node = lookup.node; + } else { + node = path; + } + FS.doChmod(null, node, mode, dontFollow); + }, + lchmod(path, mode) { + FS.chmod(path, mode, true); + }, + fchmod(fd, mode) { + var stream = FS.getStreamChecked(fd); + FS.doChmod(stream, stream.node, mode, false); + }, + doChown(stream, node, dontFollow) { + FS.doSetAttr(stream, node, { timestamp: Date.now(), dontFollow }); + }, + chown(path, uid, gid, dontFollow) { + var node; + if (typeof path == 'string') { + var lookup = FS.lookupPath(path, { follow: !dontFollow }); + node = lookup.node; + } else { + node = path; + } + FS.doChown(null, node, dontFollow); + }, + lchown(path, uid, gid) { + FS.chown(path, uid, gid, true); + }, + fchown(fd, uid, gid) { + var stream = FS.getStreamChecked(fd); + FS.doChown(stream, stream.node, false); + }, + doTruncate(stream, node, len) { + if (FS.isDir(node.mode)) { + throw new FS.ErrnoError(31); + } + if (!FS.isFile(node.mode)) { + throw new FS.ErrnoError(28); + } + var errCode = FS.nodePermissions(node, 'w'); + if (errCode) { + throw new FS.ErrnoError(errCode); + } + FS.doSetAttr(stream, node, { size: len, timestamp: Date.now() }); + }, + truncate(path, len) { + if (len < 0) { + throw new FS.ErrnoError(28); + } + var node; + if (typeof path == 'string') { + var lookup = FS.lookupPath(path, { follow: true }); + node = lookup.node; + } else { + node = path; + } + FS.doTruncate(null, node, len); + }, + ftruncate(fd, len) { + var stream = FS.getStreamChecked(fd); + if (len < 0 || (stream.flags & 2097155) === 0) { + throw new FS.ErrnoError(28); + } + FS.doTruncate(stream, stream.node, len); + }, + utime(path, atime, mtime) { + var lookup = FS.lookupPath(path, { follow: true }); + var node = lookup.node; + var setattr = FS.checkOpExists(node.node_ops.setattr, 63); + setattr(node, { atime, mtime }); + }, + open(path, flags, mode = 438) { + if (path === '') { + throw new FS.ErrnoError(44); + } + flags = + typeof flags == 'string' ? FS_modeStringToFlags(flags) : flags; + if (flags & 64) { + mode = (mode & 4095) | 32768; + } else { + mode = 0; + } + var node; + var isDirPath; + if (typeof path == 'object') { + node = path; + } else { + isDirPath = path.endsWith('/'); + var lookup = FS.lookupPath(path, { + follow: !(flags & 131072), + noent_okay: true, + }); + node = lookup.node; + path = lookup.path; + } + var created = false; + if (flags & 64) { + if (node) { + if (flags & 128) { + throw new FS.ErrnoError(20); + } + } else if (isDirPath) { + throw new FS.ErrnoError(31); + } else { + node = FS.mknod(path, mode | 511, 0); + created = true; + } + } + if (!node) { + throw new FS.ErrnoError(44); + } + if (FS.isChrdev(node.mode)) { + flags &= ~512; + } + if (flags & 65536 && !FS.isDir(node.mode)) { + throw new FS.ErrnoError(54); + } + if (!created) { + var errCode = FS.mayOpen(node, flags); + if (errCode) { + throw new FS.ErrnoError(errCode); + } + } + if (flags & 512 && !created) { + FS.truncate(node, 0); + } + flags &= ~(128 | 512 | 131072); + var stream = FS.createStream({ + node, + path: FS.getPath(node), + flags, + seekable: true, + position: 0, + stream_ops: node.stream_ops, + ungotten: [], + error: false, + }); + if (stream.stream_ops.open) { + stream.stream_ops.open(stream); + } + if (created) { + FS.chmod(node, mode & 511); + } + if (Module['logReadFiles'] && !(flags & 1)) { + if (!(path in FS.readFiles)) { + FS.readFiles[path] = 1; + } + } + return stream; + }, + close(stream) { + if (FS.isClosed(stream)) { + throw new FS.ErrnoError(8); + } + if (stream.getdents) stream.getdents = null; + try { + if (stream.stream_ops.close) { + stream.stream_ops.close(stream); + } + } catch (e) { + throw e; + } finally { + FS.closeStream(stream.fd); + } + stream.fd = null; + }, + isClosed(stream) { + return stream.fd === null; + }, + llseek(stream, offset, whence) { + if (FS.isClosed(stream)) { + throw new FS.ErrnoError(8); + } + if (!stream.seekable || !stream.stream_ops.llseek) { + throw new FS.ErrnoError(70); + } + if (whence != 0 && whence != 1 && whence != 2) { + throw new FS.ErrnoError(28); + } + stream.position = stream.stream_ops.llseek(stream, offset, whence); + stream.ungotten = []; + return stream.position; + }, + read(stream, buffer, offset, length, position) { + if (length < 0 || position < 0) { + throw new FS.ErrnoError(28); + } + if (FS.isClosed(stream)) { + throw new FS.ErrnoError(8); + } + if ((stream.flags & 2097155) === 1) { + throw new FS.ErrnoError(8); + } + if (FS.isDir(stream.node.mode)) { + throw new FS.ErrnoError(31); + } + if (!stream.stream_ops.read) { + throw new FS.ErrnoError(28); + } + var seeking = typeof position != 'undefined'; + if (!seeking) { + position = stream.position; + } else if (!stream.seekable) { + throw new FS.ErrnoError(70); + } + var bytesRead = stream.stream_ops.read( + stream, + buffer, + offset, + length, + position + ); + if (!seeking) stream.position += bytesRead; + return bytesRead; + }, + write(stream, buffer, offset, length, position, canOwn) { + if (length < 0 || position < 0) { + throw new FS.ErrnoError(28); + } + if (FS.isClosed(stream)) { + throw new FS.ErrnoError(8); + } + if ((stream.flags & 2097155) === 0) { + throw new FS.ErrnoError(8); + } + if (FS.isDir(stream.node.mode)) { + throw new FS.ErrnoError(31); + } + if (!stream.stream_ops.write) { + throw new FS.ErrnoError(28); + } + if (stream.seekable && stream.flags & 1024) { + FS.llseek(stream, 0, 2); + } + var seeking = typeof position != 'undefined'; + if (!seeking) { + position = stream.position; + } else if (!stream.seekable) { + throw new FS.ErrnoError(70); + } + var bytesWritten = stream.stream_ops.write( + stream, + buffer, + offset, + length, + position, + canOwn + ); + if (!seeking) stream.position += bytesWritten; + return bytesWritten; + }, + mmap(stream, length, position, prot, flags) { + if ( + (prot & 2) !== 0 && + (flags & 2) === 0 && + (stream.flags & 2097155) !== 2 + ) { + throw new FS.ErrnoError(2); + } + if ((stream.flags & 2097155) === 1) { + throw new FS.ErrnoError(2); + } + if (!stream.stream_ops.mmap) { + throw new FS.ErrnoError(43); + } + if (!length) { + throw new FS.ErrnoError(28); + } + return stream.stream_ops.mmap( + stream, + length, + position, + prot, + flags + ); + }, + msync(stream, buffer, offset, length, mmapFlags) { + if (!stream.stream_ops.msync) { + return 0; + } + return stream.stream_ops.msync( + stream, + buffer, + offset, + length, + mmapFlags + ); + }, + ioctl(stream, cmd, arg) { + if (!stream.stream_ops.ioctl) { + throw new FS.ErrnoError(59); + } + return stream.stream_ops.ioctl(stream, cmd, arg); + }, + readFile(path, opts = {}) { + opts.flags = opts.flags || 0; + opts.encoding = opts.encoding || 'binary'; + if (opts.encoding !== 'utf8' && opts.encoding !== 'binary') { + abort(`Invalid encoding type "${opts.encoding}"`); + } + var stream = FS.open(path, opts.flags); + var stat = FS.stat(path); + var length = stat.size; + var buf = new Uint8Array(length); + FS.read(stream, buf, 0, length, 0); + if (opts.encoding === 'utf8') { + buf = UTF8ArrayToString(buf); + } + FS.close(stream); + return buf; + }, + writeFile(path, data, opts = {}) { + opts.flags = opts.flags || 577; + var stream = FS.open(path, opts.flags, opts.mode); + if (typeof data == 'string') { + data = new Uint8Array(intArrayFromString(data, true)); + } + if (ArrayBuffer.isView(data)) { + FS.write( + stream, + data, + 0, + data.byteLength, + undefined, + opts.canOwn + ); + } else { + abort('Unsupported data type'); + } + FS.close(stream); + }, + cwd: () => FS.currentPath, + chdir(path) { + var lookup = FS.lookupPath(path, { follow: true }); + if (lookup.node === null) { + throw new FS.ErrnoError(44); + } + if (!FS.isDir(lookup.node.mode)) { + throw new FS.ErrnoError(54); + } + var errCode = FS.nodePermissions(lookup.node, 'x'); + if (errCode) { + throw new FS.ErrnoError(errCode); + } + FS.currentPath = lookup.path; + }, + createDefaultDirectories() { + FS.mkdir('/tmp'); + FS.mkdir('/home'); + FS.mkdir('/home/web_user'); + }, + createDefaultDevices() { + FS.mkdir('/dev'); + FS.registerDevice(FS.makedev(1, 3), { + read: () => 0, + write: (stream, buffer, offset, length, pos) => length, + llseek: () => 0, + }); + FS.mkdev('/dev/null', FS.makedev(1, 3)); + TTY.register(FS.makedev(5, 0), TTY.default_tty_ops); + TTY.register(FS.makedev(6, 0), TTY.default_tty1_ops); + FS.mkdev('/dev/tty', FS.makedev(5, 0)); + FS.mkdev('/dev/tty1', FS.makedev(6, 0)); + var randomBuffer = new Uint8Array(1024), + randomLeft = 0; + var randomByte = () => { + if (randomLeft === 0) { + randomFill(randomBuffer); + randomLeft = randomBuffer.byteLength; + } + return randomBuffer[--randomLeft]; + }; + FS.createDevice('/dev', 'random', randomByte); + FS.createDevice('/dev', 'urandom', randomByte); + FS.mkdir('/dev/shm'); + FS.mkdir('/dev/shm/tmp'); + }, + createSpecialDirectories() { + FS.mkdir('/proc'); + var proc_self = FS.mkdir('/proc/self'); + FS.mkdir('/proc/self/fd'); + FS.mount( + { + mount() { + var node = FS.createNode(proc_self, 'fd', 16895, 73); + node.stream_ops = { llseek: MEMFS.stream_ops.llseek }; + node.node_ops = { + lookup(parent, name) { + var fd = +name; + var stream = FS.getStreamChecked(fd); + var ret = { + parent: null, + mount: { mountpoint: 'fake' }, + node_ops: { readlink: () => stream.path }, + id: fd + 1, + }; + ret.parent = ret; + return ret; + }, + readdir() { + return Array.from(FS.streams.entries()) + .filter(([k, v]) => v) + .map(([k, v]) => k.toString()); + }, + }; + return node; + }, + }, + {}, + '/proc/self/fd' + ); + }, + createStandardStreams(input, output, error) { + if (input) { + FS.createDevice('/dev', 'stdin', input); + } else { + FS.symlink('/dev/tty', '/dev/stdin'); + } + if (output) { + FS.createDevice('/dev', 'stdout', null, output); + } else { + FS.symlink('/dev/tty', '/dev/stdout'); + } + if (error) { + FS.createDevice('/dev', 'stderr', null, error); + } else { + FS.symlink('/dev/tty1', '/dev/stderr'); + } + var stdin = FS.open('/dev/stdin', 0); + var stdout = FS.open('/dev/stdout', 1); + var stderr = FS.open('/dev/stderr', 1); + }, + staticInit() { + FS.nameTable = new Array(4096); + FS.mount(MEMFS, {}, '/'); + FS.createDefaultDirectories(); + FS.createDefaultDevices(); + FS.createSpecialDirectories(); + FS.filesystems = { MEMFS, PROXYFS }; + }, + init(input, output, error) { + FS.initialized = true; + input ??= Module['stdin']; + output ??= Module['stdout']; + error ??= Module['stderr']; + FS.createStandardStreams(input, output, error); + }, + quit() { + FS.initialized = false; + _fflush(0); + for (var stream of FS.streams) { + if (stream) { + FS.close(stream); + } + } + }, + findObject(path, dontResolveLastLink) { + var ret = FS.analyzePath(path, dontResolveLastLink); + if (!ret.exists) { + return null; + } + return ret.object; + }, + analyzePath(path, dontResolveLastLink) { + try { + var lookup = FS.lookupPath(path, { + follow: !dontResolveLastLink, + }); + path = lookup.path; + } catch (e) {} + var ret = { + isRoot: false, + exists: false, + error: 0, + name: null, + path: null, + object: null, + parentExists: false, + parentPath: null, + parentObject: null, + }; + try { + var lookup = FS.lookupPath(path, { parent: true }); + ret.parentExists = true; + ret.parentPath = lookup.path; + ret.parentObject = lookup.node; + ret.name = PATH.basename(path); + lookup = FS.lookupPath(path, { follow: !dontResolveLastLink }); + ret.exists = true; + ret.path = lookup.path; + ret.object = lookup.node; + ret.name = lookup.node.name; + ret.isRoot = lookup.path === '/'; + } catch (e) { + ret.error = e.errno; + } + return ret; + }, + createPath(parent, path, canRead, canWrite) { + parent = typeof parent == 'string' ? parent : FS.getPath(parent); + var parts = path.split('/').reverse(); + while (parts.length) { + var part = parts.pop(); + if (!part) continue; + var current = PATH.join2(parent, part); + try { + FS.mkdir(current); + } catch (e) { + if (e.errno != 20) throw e; + } + parent = current; + } + return current; + }, + createFile(parent, name, properties, canRead, canWrite) { + var path = PATH.join2( + typeof parent == 'string' ? parent : FS.getPath(parent), + name + ); + var mode = FS_getMode(canRead, canWrite); + return FS.create(path, mode); + }, + createDataFile(parent, name, data, canRead, canWrite, canOwn) { + var path = name; + if (parent) { + parent = + typeof parent == 'string' ? parent : FS.getPath(parent); + path = name ? PATH.join2(parent, name) : parent; + } + var mode = FS_getMode(canRead, canWrite); + var node = FS.create(path, mode); + if (data) { + if (typeof data == 'string') { + var arr = new Array(data.length); + for (var i = 0, len = data.length; i < len; ++i) + arr[i] = data.charCodeAt(i); + data = arr; + } + FS.chmod(node, mode | 146); + var stream = FS.open(node, 577); + FS.write(stream, data, 0, data.length, 0, canOwn); + FS.close(stream); + FS.chmod(node, mode); + } + }, + createDevice(parent, name, input, output) { + var path = PATH.join2( + typeof parent == 'string' ? parent : FS.getPath(parent), + name + ); + var mode = FS_getMode(!!input, !!output); + FS.createDevice.major ??= 64; + var dev = FS.makedev(FS.createDevice.major++, 0); + FS.registerDevice(dev, { + open(stream) { + stream.seekable = false; + }, + close(stream) { + if (output?.buffer?.length) { + output(10); + } + }, + read(stream, buffer, offset, length, pos) { + var bytesRead = 0; + for (var i = 0; i < length; i++) { + var result; + try { + result = input(); + } catch (e) { + throw new FS.ErrnoError(29); + } + if (result === undefined && bytesRead === 0) { + throw new FS.ErrnoError(6); + } + if (result === null || result === undefined) break; + bytesRead++; + buffer[offset + i] = result; + } + if (bytesRead) { + stream.node.atime = Date.now(); + } + return bytesRead; + }, + write(stream, buffer, offset, length, pos) { + for (var i = 0; i < length; i++) { + try { + output(buffer[offset + i]); + } catch (e) { + throw new FS.ErrnoError(29); + } + } + if (length) { + stream.node.mtime = stream.node.ctime = Date.now(); + } + return i; + }, + }); + return FS.mkdev(path, mode, dev); + }, + forceLoadFile(obj) { + if (obj.isDevice || obj.isFolder || obj.link || obj.contents) + return true; + if (globalThis.XMLHttpRequest) { + abort( + 'Lazy loading should have been performed (contents set) in createLazyFile, but it was not. Lazy loading only works in web workers. Use --embed-file or --preload-file in emcc on the main thread.' + ); + } else { + try { + obj.contents = readBinary(obj.url); + } catch (e) { + throw new FS.ErrnoError(29); + } + } + }, + createLazyFile(parent, name, url, canRead, canWrite) { + class LazyUint8Array { + lengthKnown = false; + chunks = []; + get(idx) { + if (idx > this.length - 1 || idx < 0) { + return undefined; + } + var chunkOffset = idx % this.chunkSize; + var chunkNum = (idx / this.chunkSize) | 0; + return this.getter(chunkNum)[chunkOffset]; + } + setDataGetter(getter) { + this.getter = getter; + } + cacheLength() { + var xhr = new XMLHttpRequest(); + xhr.open('HEAD', url, false); + xhr.send(null); + if ( + !( + (xhr.status >= 200 && xhr.status < 300) || + xhr.status === 304 + ) + ) + abort( + "Couldn't load " + url + '. Status: ' + xhr.status + ); + var datalength = Number( + xhr.getResponseHeader('Content-length') + ); + var header; + var hasByteServing = + (header = xhr.getResponseHeader('Accept-Ranges')) && + header === 'bytes'; + var usesGzip = + (header = xhr.getResponseHeader('Content-Encoding')) && + header === 'gzip'; + var chunkSize = 1024 * 1024; + if (!hasByteServing) chunkSize = datalength; + var doXHR = (from, to) => { + if (from > to) + abort( + 'invalid range (' + + from + + ', ' + + to + + ') or no bytes requested!' + ); + if (to > datalength - 1) + abort( + 'only ' + + datalength + + ' bytes available! programmer error!' + ); + var xhr = new XMLHttpRequest(); + xhr.open('GET', url, false); + if (datalength !== chunkSize) + xhr.setRequestHeader( + 'Range', + 'bytes=' + from + '-' + to + ); + xhr.responseType = 'arraybuffer'; + if (xhr.overrideMimeType) { + xhr.overrideMimeType( + 'text/plain; charset=x-user-defined' + ); + } + xhr.send(null); + if ( + !( + (xhr.status >= 200 && xhr.status < 300) || + xhr.status === 304 + ) + ) + abort( + "Couldn't load " + + url + + '. Status: ' + + xhr.status + ); + if (xhr.response !== undefined) { + return new Uint8Array(xhr.response || []); + } + return intArrayFromString(xhr.responseText || '', true); + }; + var lazyArray = this; + lazyArray.setDataGetter((chunkNum) => { + var start = chunkNum * chunkSize; + var end = (chunkNum + 1) * chunkSize - 1; + end = Math.min(end, datalength - 1); + if (typeof lazyArray.chunks[chunkNum] == 'undefined') { + lazyArray.chunks[chunkNum] = doXHR(start, end); + } + if (typeof lazyArray.chunks[chunkNum] == 'undefined') + abort('doXHR failed!'); + return lazyArray.chunks[chunkNum]; + }); + if (usesGzip || !datalength) { + chunkSize = datalength = 1; + datalength = this.getter(0).length; + chunkSize = datalength; + out( + 'LazyFiles on gzip forces download of the whole file when length is accessed' + ); + } + this._length = datalength; + this._chunkSize = chunkSize; + this.lengthKnown = true; + } + get length() { + if (!this.lengthKnown) { + this.cacheLength(); + } + return this._length; + } + get chunkSize() { + if (!this.lengthKnown) { + this.cacheLength(); + } + return this._chunkSize; + } + } + if (globalThis.XMLHttpRequest) { + if (!ENVIRONMENT_IS_WORKER) + abort( + 'Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc' + ); + var lazyArray = new LazyUint8Array(); + var properties = { isDevice: false, contents: lazyArray }; + } else { + var properties = { isDevice: false, url }; + } + var node = FS.createFile( + parent, + name, + properties, + canRead, + canWrite + ); + if (properties.contents) { + node.contents = properties.contents; + } else if (properties.url) { + node.contents = null; + node.url = properties.url; + } + Object.defineProperties(node, { + usedBytes: { + get: function () { + return this.contents.length; + }, + }, + }); + var stream_ops = {}; + for (const [key, fn] of Object.entries(node.stream_ops)) { + stream_ops[key] = (...args) => { + FS.forceLoadFile(node); + return fn(...args); + }; + } + function writeChunks(stream, buffer, offset, length, position) { + var contents = stream.node.contents; + if (position >= contents.length) return 0; + var size = Math.min(contents.length - position, length); + if (contents.slice) { + for (var i = 0; i < size; i++) { + buffer[offset + i] = contents[position + i]; + } + } else { + for (var i = 0; i < size; i++) { + buffer[offset + i] = contents.get(position + i); + } + } + return size; + } + stream_ops.read = (stream, buffer, offset, length, position) => { + FS.forceLoadFile(node); + return writeChunks(stream, buffer, offset, length, position); + }; + stream_ops.mmap = (stream, length, position, prot, flags) => { + FS.forceLoadFile(node); + var ptr = mmapAlloc(length); + if (!ptr) { + throw new FS.ErrnoError(48); + } + writeChunks(stream, HEAP8, ptr, length, position); + return { ptr, allocated: true }; + }; + node.stream_ops = stream_ops; + return node; + }, + }; + var findLibraryFS = (libName, rpath) => { + if (!runtimeInitialized) { + return undefined; + } + if (PATH.isAbs(libName)) { + try { + FS.lookupPath(libName); + return libName; + } catch (e) { + return undefined; + } + } + var rpathResolved = (rpath?.paths || []).map((p) => + replaceORIGIN(rpath?.parentLibPath, p) + ); + return withStackSave(() => { + var bufSize = 2 * 255 + 2; + var buf = stackAlloc(bufSize); + var rpathC = stringToUTF8OnStack(rpathResolved.join(':')); + var libNameC = stringToUTF8OnStack(libName); + var resLibNameC = __emscripten_find_dylib( + buf, + rpathC, + libNameC, + bufSize + ); + return resLibNameC ? UTF8ToString(resLibNameC) : undefined; + }); + }; + function loadDynamicLibrary( + libName, + flags = { global: true, nodelete: true }, + localScope, + handle + ) { + var dso = LDSO.loadedLibsByName[libName]; + if (dso) { + if (!flags.global) { + if (localScope) { + Object.assign(localScope, dso.exports); + } + } else if (!dso.global) { + dso.global = true; + mergeLibSymbols(dso.exports, libName); + } + if (flags.nodelete && dso.refcount !== Infinity) { + dso.refcount = Infinity; + } + dso.refcount++; + if (handle) { + LDSO.loadedLibsByHandle[handle] = dso; + } + return flags.loadAsync ? Promise.resolve(true) : true; + } + dso = newDSO(libName, handle, 'loading'); + dso.refcount = flags.nodelete ? Infinity : 1; + dso.global = flags.global; + function loadLibData() { + if (handle) { + var data = HEAPU32[(handle + 28) >> 2]; + var dataSize = HEAPU32[(handle + 32) >> 2]; + if (data && dataSize) { + var libData = HEAP8.slice(data, data + dataSize); + return flags.loadAsync ? Promise.resolve(libData) : libData; + } + } + var f = findLibraryFS(libName, flags.rpath); + if (f) { + var libData = FS.readFile(f, { encoding: 'binary' }); + return flags.loadAsync ? Promise.resolve(libData) : libData; + } + var libFile = locateFile(libName); + if (flags.loadAsync) { + return asyncLoad(libFile); + } + if (!readBinary) { + throw new Error( + `${libFile}: file not found, and synchronous loading of external files is not available` + ); + } + return readBinary(libFile); + } + function getExports() { + var preloaded = preloadedWasm[libName]; + if (preloaded) { + return flags.loadAsync ? Promise.resolve(preloaded) : preloaded; + } + if (flags.loadAsync) { + return loadLibData().then((libData) => + loadWebAssemblyModule( + libData, + flags, + libName, + localScope, + handle + ) + ); + } + return loadWebAssemblyModule( + loadLibData(), + flags, + libName, + localScope, + handle + ); + } + function moduleLoaded(exports) { + if (dso.global) { + mergeLibSymbols(exports, libName); + } else if (localScope) { + Object.assign(localScope, exports); + } + dso.exports = exports; + } + if (flags.loadAsync) { + return getExports().then((exports) => { + moduleLoaded(exports); + return true; + }); + } + moduleLoaded(getExports()); + return true; + } + var reportUndefinedSymbols = () => { + for (var [symName, entry] of Object.entries(GOT)) { + if (entry.value == -1) { + var value = resolveGlobalSymbol(symName, true).sym; + if (!value && !entry.required) { + entry.value = 0; + continue; + } + if (typeof value == 'function') { + entry.value = addFunction(value, value.sig); + } else if (typeof value == 'number') { + entry.value = value; + } else { + throw new Error( + `bad export type for '${symName}': ${typeof value} (${value})` + ); + } + } + } + }; + var loadDylibs = async () => { + if (!dynamicLibraries.length) { + reportUndefinedSymbols(); + return; + } + addRunDependency('loadDylibs'); + for (var lib of dynamicLibraries) { + await loadDynamicLibrary(lib, { + loadAsync: true, + global: true, + nodelete: true, + allowUndefined: true, + }); + } + reportUndefinedSymbols(); + removeRunDependency('loadDylibs'); + }; + var noExitRuntime = false; + var ___assert_fail = (condition, filename, line, func) => + abort( + `Assertion failed: ${UTF8ToString(condition)}, at: ` + + [ + filename ? UTF8ToString(filename) : 'unknown filename', + line, + func ? UTF8ToString(func) : 'unknown function', + ] + ); + ___assert_fail.sig = 'vppip'; + var ___call_sighandler = (fp, sig) => getWasmTableEntry(fp)(sig); + ___call_sighandler.sig = 'vpi'; + var SOCKFS = { + websocketArgs: {}, + callbacks: {}, + on(event, callback) { + SOCKFS.callbacks[event] = callback; + }, + emit(event, param) { + SOCKFS.callbacks[event]?.(param); + }, + mount(mount) { + SOCKFS.websocketArgs = Module['websocket'] || {}; + (Module['websocket'] ??= {})['on'] = SOCKFS.on; + return FS.createNode(null, '/', 16895, 0); + }, + createSocket(family, type, protocol) { + if (family != 2) { + throw new FS.ErrnoError(5); + } + type &= ~526336; + if (type != 1 && type != 2) { + throw new FS.ErrnoError(28); + } + var streaming = type == 1; + if (streaming && protocol && protocol != 6) { + throw new FS.ErrnoError(66); + } + var sock = { + family, + type, + protocol, + server: null, + error: null, + peers: {}, + pending: [], + recv_queue: [], + sock_ops: SOCKFS.websocket_sock_ops, + }; + var name = SOCKFS.nextname(); + var node = FS.createNode(SOCKFS.root, name, 49152, 0); + node.sock = sock; + var stream = FS.createStream({ + path: name, + node, + flags: 2, + seekable: false, + stream_ops: SOCKFS.stream_ops, + }); + sock.stream = stream; + return sock; + }, + getSocket(fd) { + var stream = FS.getStream(fd); + if (!stream || !FS.isSocket(stream.node.mode)) { + return null; + } + return stream.node.sock; + }, + stream_ops: { + poll(stream) { + var sock = stream.node.sock; + return sock.sock_ops.poll(sock); + }, + ioctl(stream, request, varargs) { + var sock = stream.node.sock; + return sock.sock_ops.ioctl(sock, request, varargs); + }, + read(stream, buffer, offset, length, position) { + var sock = stream.node.sock; + var msg = sock.sock_ops.recvmsg(sock, length); + if (!msg) { + return 0; + } + buffer.set(msg.buffer, offset); + return msg.buffer.length; + }, + write(stream, buffer, offset, length, position) { + var sock = stream.node.sock; + return sock.sock_ops.sendmsg(sock, buffer, offset, length); + }, + close(stream) { + var sock = stream.node.sock; + sock.sock_ops.close(sock); + }, + }, + nextname() { + if (!SOCKFS.nextname.current) { + SOCKFS.nextname.current = 0; + } + return `socket[${SOCKFS.nextname.current++}]`; + }, + websocket_sock_ops: { + createPeer(sock, addr, port) { + var ws; + if (typeof addr == 'object') { + ws = addr; + addr = null; + port = null; + } + if (ws) { + if (ws._socket) { + addr = ws._socket.remoteAddress; + port = ws._socket.remotePort; + } else { + var result = /ws[s]?:\/\/([^:]+):(\d+)/.exec(ws.url); + if (!result) { + throw new Error( + 'WebSocket URL must be in the format ws(s)://address:port' + ); + } + addr = result[1]; + port = parseInt(result[2], 10); + } + } else { + try { + var url = 'ws://'.replace('#', '//'); + var subProtocols = 'binary'; + var opts = undefined; + if ('function' === typeof SOCKFS.websocketArgs['url']) { + url = SOCKFS.websocketArgs['url'](...arguments); + } else if ( + 'string' === typeof SOCKFS.websocketArgs['url'] + ) { + url = SOCKFS.websocketArgs['url']; + } + if (SOCKFS.websocketArgs['subprotocol']) { + subProtocols = SOCKFS.websocketArgs['subprotocol']; + } else if ( + SOCKFS.websocketArgs['subprotocol'] === null + ) { + subProtocols = 'null'; + } + if (url === 'ws://' || url === 'wss://') { + var parts = addr.split('/'); + url = + url + + parts[0] + + ':' + + port + + '/' + + parts.slice(1).join('/'); + } + if (subProtocols !== 'null') { + subProtocols = subProtocols + .replace(/^ +| +$/g, '') + .split(/ *, */); + opts = subProtocols; + } + var WebSocketConstructor; + { + WebSocketConstructor = WebSocket; + } + if (Module['websocket']['decorator']) { + WebSocketConstructor = + Module['websocket']['decorator']( + WebSocketConstructor + ); + } + ws = new WebSocketConstructor(url, opts); + ws.binaryType = 'arraybuffer'; + } catch (e) { + throw new FS.ErrnoError(23); + } + } + var peer = { addr, port, socket: ws, msg_send_queue: [] }; + SOCKFS.websocket_sock_ops.addPeer(sock, peer); + SOCKFS.websocket_sock_ops.handlePeerEvents(sock, peer); + if (sock.type === 2 && typeof sock.sport != 'undefined') { + peer.msg_send_queue.push( + new Uint8Array([ + 255, + 255, + 255, + 255, + 'p'.charCodeAt(0), + 'o'.charCodeAt(0), + 'r'.charCodeAt(0), + 't'.charCodeAt(0), + (sock.sport & 65280) >> 8, + sock.sport & 255, + ]) + ); + } + return peer; + }, + getPeer(sock, addr, port) { + return sock.peers[addr + ':' + port]; + }, + addPeer(sock, peer) { + sock.peers[peer.addr + ':' + peer.port] = peer; + }, + removePeer(sock, peer) { + delete sock.peers[peer.addr + ':' + peer.port]; + }, + handlePeerEvents(sock, peer) { + var first = true; + var handleOpen = function () { + sock.connecting = false; + SOCKFS.emit('open', sock.stream.fd); + try { + var queued = peer.msg_send_queue.shift(); + while (queued) { + peer.socket.send(queued); + queued = peer.msg_send_queue.shift(); + } + } catch (e) { + peer.socket.close(); + } + }; + function handleMessage(data) { + if (typeof data == 'string') { + var encoder = new TextEncoder(); + data = encoder.encode(data); + } else { + if (data.byteLength == 0) { + return; + } + data = new Uint8Array(data); + } + var wasfirst = first; + first = false; + if ( + wasfirst && + data.length === 10 && + data[0] === 255 && + data[1] === 255 && + data[2] === 255 && + data[3] === 255 && + data[4] === 'p'.charCodeAt(0) && + data[5] === 'o'.charCodeAt(0) && + data[6] === 'r'.charCodeAt(0) && + data[7] === 't'.charCodeAt(0) + ) { + var newport = (data[8] << 8) | data[9]; + SOCKFS.websocket_sock_ops.removePeer(sock, peer); + peer.port = newport; + SOCKFS.websocket_sock_ops.addPeer(sock, peer); + return; + } + sock.recv_queue.push({ + addr: peer.addr, + port: peer.port, + data, + }); + SOCKFS.emit('message', sock.stream.fd); + } + if (ENVIRONMENT_IS_NODE) { + peer.socket.on('open', handleOpen); + peer.socket.on('message', function (data, isBinary) { + if (!isBinary) { + return; + } + handleMessage(new Uint8Array(data).buffer); + }); + peer.socket.on('close', function () { + SOCKFS.emit('close', sock.stream.fd); + }); + peer.socket.on('error', function (error) { + sock.error = 14; + SOCKFS.emit('error', [ + sock.stream.fd, + sock.error, + 'ECONNREFUSED: Connection refused', + ]); + }); + } else { + peer.socket.onopen = handleOpen; + peer.socket.onclose = function () { + SOCKFS.emit('close', sock.stream.fd); + }; + peer.socket.onmessage = function peer_socket_onmessage( + event + ) { + handleMessage(event.data); + }; + peer.socket.onerror = function (error) { + sock.error = 14; + SOCKFS.emit('error', [ + sock.stream.fd, + sock.error, + 'ECONNREFUSED: Connection refused', + ]); + }; + } + }, + poll(sock) { + if (sock.type === 1 && sock.server) { + return sock.pending.length ? 64 | 1 : 0; + } + var mask = 0; + var dest = + sock.type === 1 + ? SOCKFS.websocket_sock_ops.getPeer( + sock, + sock.daddr, + sock.dport + ) + : null; + if ( + sock.recv_queue.length || + !dest || + (dest && dest.socket.readyState === dest.socket.CLOSING) || + (dest && dest.socket.readyState === dest.socket.CLOSED) + ) { + mask |= 64 | 1; + } + if ( + !dest || + (dest && dest.socket.readyState === dest.socket.OPEN) + ) { + mask |= 4; + } + if ( + (dest && dest.socket.readyState === dest.socket.CLOSING) || + (dest && dest.socket.readyState === dest.socket.CLOSED) + ) { + if (sock.connecting) { + mask |= 4; + } else { + mask |= 16; + } + } + return mask; + }, + ioctl(sock, request, arg) { + switch (request) { + case 21531: + var bytes = 0; + if (sock.recv_queue.length) { + bytes = sock.recv_queue[0].data.length; + } + HEAP32[arg >> 2] = bytes; + return 0; + case 21537: + var on = HEAP32[arg >> 2]; + if (on) { + sock.stream.flags |= 2048; + } else { + sock.stream.flags &= ~2048; + } + return 0; + default: + return 28; + } + }, + close(sock) { + if (sock.server) { + try { + sock.server.close(); + } catch (e) {} + sock.server = null; + } + for (var peer of Object.values(sock.peers)) { + try { + peer.socket.close(); + } catch (e) {} + SOCKFS.websocket_sock_ops.removePeer(sock, peer); + } + return 0; + }, + bind(sock, addr, port) { + if ( + typeof sock.saddr != 'undefined' || + typeof sock.sport != 'undefined' + ) { + throw new FS.ErrnoError(28); + } + sock.saddr = addr; + sock.sport = port; + if (sock.type === 2) { + if (sock.server) { + sock.server.close(); + sock.server = null; + } + try { + sock.sock_ops.listen(sock, 0); + } catch (e) { + if (!(e.name === 'ErrnoError')) throw e; + if (e.errno !== 138) throw e; + } + } + }, + connect(sock, addr, port) { + if (sock.server) { + throw new FS.ErrnoError(138); + } + if ( + typeof sock.daddr != 'undefined' && + typeof sock.dport != 'undefined' + ) { + var dest = SOCKFS.websocket_sock_ops.getPeer( + sock, + sock.daddr, + sock.dport + ); + if (dest) { + if (dest.socket.readyState === dest.socket.CONNECTING) { + throw new FS.ErrnoError(7); + } else { + throw new FS.ErrnoError(30); + } + } + } + var peer = SOCKFS.websocket_sock_ops.createPeer( + sock, + addr, + port + ); + sock.daddr = peer.addr; + sock.dport = peer.port; + sock.connecting = true; + }, + listen(sock, backlog) { + if (!ENVIRONMENT_IS_NODE) { + throw new FS.ErrnoError(138); + } + }, + accept(listensock) { + if (!listensock.server || !listensock.pending.length) { + throw new FS.ErrnoError(28); + } + var newsock = listensock.pending.shift(); + newsock.stream.flags = listensock.stream.flags; + return newsock; + }, + getname(sock, peer) { + var addr, port; + if (peer) { + if (sock.daddr === undefined || sock.dport === undefined) { + throw new FS.ErrnoError(53); + } + addr = sock.daddr; + port = sock.dport; + } else { + addr = sock.saddr || 0; + port = sock.sport || 0; + } + return { addr, port }; + }, + sendmsg(sock, buffer, offset, length, addr, port) { + if (sock.type === 2) { + if (addr === undefined || port === undefined) { + addr = sock.daddr; + port = sock.dport; + } + if (addr === undefined || port === undefined) { + throw new FS.ErrnoError(17); + } + } else { + addr = sock.daddr; + port = sock.dport; + } + var dest = SOCKFS.websocket_sock_ops.getPeer(sock, addr, port); + if (sock.type === 1) { + if ( + !dest || + dest.socket.readyState === dest.socket.CLOSING || + dest.socket.readyState === dest.socket.CLOSED + ) { + throw new FS.ErrnoError(53); + } + } + if (ArrayBuffer.isView(buffer)) { + offset += buffer.byteOffset; + buffer = buffer.buffer; + } + var data = buffer.slice(offset, offset + length); + if (!dest || dest.socket.readyState !== dest.socket.OPEN) { + if (sock.type === 2) { + if ( + !dest || + dest.socket.readyState === dest.socket.CLOSING || + dest.socket.readyState === dest.socket.CLOSED + ) { + dest = SOCKFS.websocket_sock_ops.createPeer( + sock, + addr, + port + ); + } + } + dest.msg_send_queue.push(data); + return length; + } + try { + dest.socket.send(data); + return length; + } catch (e) { + throw new FS.ErrnoError(28); + } + }, + recvmsg(sock, length, flags) { + if (sock.type === 1 && sock.server) { + throw new FS.ErrnoError(53); + } + var queued = sock.recv_queue.shift(); + if (!queued) { + if (sock.type === 1) { + var dest = SOCKFS.websocket_sock_ops.getPeer( + sock, + sock.daddr, + sock.dport + ); + if (!dest) { + throw new FS.ErrnoError(53); + } + if ( + dest.socket.readyState === dest.socket.CLOSING || + dest.socket.readyState === dest.socket.CLOSED + ) { + return null; + } + throw new FS.ErrnoError(6); + } + throw new FS.ErrnoError(6); + } + var queuedLength = queued.data.byteLength || queued.data.length; + var queuedOffset = queued.data.byteOffset || 0; + var queuedBuffer = queued.data.buffer || queued.data; + var bytesRead = Math.min(length, queuedLength); + var res = { + buffer: new Uint8Array( + queuedBuffer, + queuedOffset, + bytesRead + ), + addr: queued.addr, + port: queued.port, + }; + if (flags & 2) { + bytesRead = 0; + } + if (sock.type === 1 && bytesRead < queuedLength) { + var bytesRemaining = queuedLength - bytesRead; + queued.data = new Uint8Array( + queuedBuffer, + queuedOffset + bytesRead, + bytesRemaining + ); + sock.recv_queue.unshift(queued); + } + return res; + }, + }, + }; + var getSocketFromFD = (fd) => { + var socket = SOCKFS.getSocket(fd); + if (!socket) throw new FS.ErrnoError(8); + return socket; + }; + var inetPton4 = (str) => { + var b = str.split('.'); + for (var i = 0; i < 4; i++) { + var tmp = Number(b[i]); + if (isNaN(tmp)) return null; + b[i] = tmp; + } + return (b[0] | (b[1] << 8) | (b[2] << 16) | (b[3] << 24)) >>> 0; + }; + var inetPton6 = (str) => { + var words; + var w, offset, z; + var valid6regx = + /^((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\3)::|:\b|$))|(?!\2\3)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})$/i; + var parts = []; + if (!valid6regx.test(str)) { + return null; + } + if (str === '::') { + return [0, 0, 0, 0, 0, 0, 0, 0]; + } + if (str.startsWith('::')) { + str = str.replace('::', 'Z:'); + } else { + str = str.replace('::', ':Z:'); + } + if (str.indexOf('.') > 0) { + str = str.replace(new RegExp('[.]', 'g'), ':'); + words = str.split(':'); + words[words.length - 4] = + Number(words[words.length - 4]) + + Number(words[words.length - 3]) * 256; + words[words.length - 3] = + Number(words[words.length - 2]) + + Number(words[words.length - 1]) * 256; + words = words.slice(0, words.length - 2); + } else { + words = str.split(':'); + } + offset = 0; + z = 0; + for (w = 0; w < words.length; w++) { + if (typeof words[w] == 'string') { + if (words[w] === 'Z') { + for (z = 0; z < 8 - words.length + 1; z++) { + parts[w + z] = 0; + } + offset = z - 1; + } else { + parts[w + offset] = _htons(parseInt(words[w], 16)); + } + } else { + parts[w + offset] = words[w]; + } + } + return [ + (parts[1] << 16) | parts[0], + (parts[3] << 16) | parts[2], + (parts[5] << 16) | parts[4], + (parts[7] << 16) | parts[6], + ]; + }; + var writeSockaddr = (sa, family, addr, port, addrlen) => { + switch (family) { + case 2: + addr = inetPton4(addr); + zeroMemory(sa, 16); + if (addrlen) { + HEAP32[addrlen >> 2] = 16; + } + HEAP16[sa >> 1] = family; + HEAP32[(sa + 4) >> 2] = addr; + HEAP16[(sa + 2) >> 1] = _htons(port); + break; + case 10: + addr = inetPton6(addr); + zeroMemory(sa, 28); + if (addrlen) { + HEAP32[addrlen >> 2] = 28; + } + HEAP32[sa >> 2] = family; + HEAP32[(sa + 8) >> 2] = addr[0]; + HEAP32[(sa + 12) >> 2] = addr[1]; + HEAP32[(sa + 16) >> 2] = addr[2]; + HEAP32[(sa + 20) >> 2] = addr[3]; + HEAP16[(sa + 2) >> 1] = _htons(port); + break; + default: + return 5; + } + return 0; + }; + var DNS = { + address_map: { id: 1, addrs: {}, names: {} }, + lookup_name(name) { + var res = inetPton4(name); + if (res !== null) { + return name; + } + res = inetPton6(name); + if (res !== null) { + return name; + } + var addr; + if (DNS.address_map.addrs[name]) { + addr = DNS.address_map.addrs[name]; + } else { + var id = DNS.address_map.id++; + addr = '172.29.' + (id & 255) + '.' + (id & 65280); + DNS.address_map.names[addr] = name; + DNS.address_map.addrs[name] = addr; + } + return addr; + }, + lookup_addr(addr) { + if (DNS.address_map.names[addr]) { + return DNS.address_map.names[addr]; + } + return null; + }, + }; + function ___syscall_accept4(fd, addr, addrlen, flags, d1, d2) { + try { + var sock = getSocketFromFD(fd); + var newsock = sock.sock_ops.accept(sock); + if (addr) { + var errno = writeSockaddr( + addr, + newsock.family, + DNS.lookup_name(newsock.daddr), + newsock.dport, + addrlen + ); + } + return newsock.stream.fd; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + ___syscall_accept4.sig = 'iippiii'; + var inetNtop4 = (addr) => + (addr & 255) + + '.' + + ((addr >> 8) & 255) + + '.' + + ((addr >> 16) & 255) + + '.' + + ((addr >> 24) & 255); + var inetNtop6 = (ints) => { + var str = ''; + var word = 0; + var longest = 0; + var lastzero = 0; + var zstart = 0; + var len = 0; + var i = 0; + var parts = [ + ints[0] & 65535, + ints[0] >> 16, + ints[1] & 65535, + ints[1] >> 16, + ints[2] & 65535, + ints[2] >> 16, + ints[3] & 65535, + ints[3] >> 16, + ]; + var hasipv4 = true; + var v4part = ''; + for (i = 0; i < 5; i++) { + if (parts[i] !== 0) { + hasipv4 = false; + break; + } + } + if (hasipv4) { + v4part = inetNtop4(parts[6] | (parts[7] << 16)); + if (parts[5] === -1) { + str = '::ffff:'; + str += v4part; + return str; + } + if (parts[5] === 0) { + str = '::'; + if (v4part === '0.0.0.0') v4part = ''; + if (v4part === '0.0.0.1') v4part = '1'; + str += v4part; + return str; + } + } + for (word = 0; word < 8; word++) { + if (parts[word] === 0) { + if (word - lastzero > 1) { + len = 0; + } + lastzero = word; + len++; + } + if (len > longest) { + longest = len; + zstart = word - longest + 1; + } + } + for (word = 0; word < 8; word++) { + if (longest > 1) { + if ( + parts[word] === 0 && + word >= zstart && + word < zstart + longest + ) { + if (word === zstart) { + str += ':'; + if (zstart === 0) str += ':'; + } + continue; + } + } + str += Number(_ntohs(parts[word] & 65535)).toString(16); + str += word < 7 ? ':' : ''; + } + return str; + }; + var readSockaddr = (sa, salen) => { + var family = HEAP16[sa >> 1]; + var port = _ntohs(HEAPU16[(sa + 2) >> 1]); + var addr; + switch (family) { + case 2: + if (salen !== 16) { + return { errno: 28 }; + } + addr = HEAP32[(sa + 4) >> 2]; + addr = inetNtop4(addr); + break; + case 10: + if (salen !== 28) { + return { errno: 28 }; + } + addr = [ + HEAP32[(sa + 8) >> 2], + HEAP32[(sa + 12) >> 2], + HEAP32[(sa + 16) >> 2], + HEAP32[(sa + 20) >> 2], + ]; + addr = inetNtop6(addr); + break; + default: + return { errno: 5 }; + } + return { family, addr, port }; + }; + var getSocketAddress = (addrp, addrlen) => { + var info = readSockaddr(addrp, addrlen); + if (info.errno) throw new FS.ErrnoError(info.errno); + info.addr = DNS.lookup_addr(info.addr) || info.addr; + return info; + }; + function ___syscall_bind(fd, addr, addrlen, d1, d2, d3) { + try { + var sock = getSocketFromFD(fd); + var info = getSocketAddress(addr, addrlen); + sock.sock_ops.bind(sock, info.addr, info.port); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + ___syscall_bind.sig = 'iippiii'; + var SYSCALLS = { + DEFAULT_POLLMASK: 5, + calculateAt(dirfd, path, allowEmpty) { + if (PATH.isAbs(path)) { + return path; + } + var dir; + if (dirfd === -100) { + dir = FS.cwd(); + } else { + var dirstream = SYSCALLS.getStreamFromFD(dirfd); + dir = dirstream.path; + } + if (path.length == 0) { + if (!allowEmpty) { + throw new FS.ErrnoError(44); + } + return dir; + } + return dir + '/' + path; + }, + writeStat(buf, stat) { + HEAPU32[buf >> 2] = stat.dev; + HEAPU32[(buf + 4) >> 2] = stat.mode; + HEAPU32[(buf + 8) >> 2] = stat.nlink; + HEAPU32[(buf + 12) >> 2] = stat.uid; + HEAPU32[(buf + 16) >> 2] = stat.gid; + HEAPU32[(buf + 20) >> 2] = stat.rdev; + HEAP64[(buf + 24) >> 3] = BigInt(stat.size); + HEAP32[(buf + 32) >> 2] = 4096; + HEAP32[(buf + 36) >> 2] = stat.blocks; + var atime = stat.atime.getTime(); + var mtime = stat.mtime.getTime(); + var ctime = stat.ctime.getTime(); + HEAP64[(buf + 40) >> 3] = BigInt(Math.floor(atime / 1e3)); + HEAPU32[(buf + 48) >> 2] = (atime % 1e3) * 1e3 * 1e3; + HEAP64[(buf + 56) >> 3] = BigInt(Math.floor(mtime / 1e3)); + HEAPU32[(buf + 64) >> 2] = (mtime % 1e3) * 1e3 * 1e3; + HEAP64[(buf + 72) >> 3] = BigInt(Math.floor(ctime / 1e3)); + HEAPU32[(buf + 80) >> 2] = (ctime % 1e3) * 1e3 * 1e3; + HEAP64[(buf + 88) >> 3] = BigInt(stat.ino); + return 0; + }, + writeStatFs(buf, stats) { + HEAPU32[(buf + 4) >> 2] = stats.bsize; + HEAPU32[(buf + 60) >> 2] = stats.bsize; + HEAP64[(buf + 8) >> 3] = BigInt(stats.blocks); + HEAP64[(buf + 16) >> 3] = BigInt(stats.bfree); + HEAP64[(buf + 24) >> 3] = BigInt(stats.bavail); + HEAP64[(buf + 32) >> 3] = BigInt(stats.files); + HEAP64[(buf + 40) >> 3] = BigInt(stats.ffree); + HEAPU32[(buf + 48) >> 2] = stats.fsid; + HEAPU32[(buf + 64) >> 2] = stats.flags; + HEAPU32[(buf + 56) >> 2] = stats.namelen; + }, + doMsync(addr, stream, len, flags, offset) { + if (!FS.isFile(stream.node.mode)) { + throw new FS.ErrnoError(43); + } + if (flags & 2) { + return 0; + } + var buffer = HEAPU8.slice(addr, addr + len); + FS.msync(stream, buffer, offset, len, flags); + }, + getStreamFromFD(fd) { + var stream = FS.getStreamChecked(fd); + return stream; + }, + varargs: undefined, + getStr(ptr) { + var ret = UTF8ToString(ptr); + return ret; + }, + }; + function ___syscall_chdir(path) { + try { + path = SYSCALLS.getStr(path); + FS.chdir(path); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + ___syscall_chdir.sig = 'ip'; + function ___syscall_chmod(path, mode) { + try { + path = SYSCALLS.getStr(path); + FS.chmod(path, mode); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + ___syscall_chmod.sig = 'ipi'; + var allocateUTF8OnStack = (...args) => stringToUTF8OnStack(...args); + var onInits = []; + var addOnInit = (cb) => onInits.push(cb); + function _js_getpid() { + return PHPLoader.processId ?? 42; + } + function _js_wasm_trace(format, ...args) { + if (PHPLoader.trace instanceof Function) { + PHPLoader.trace(_js_getpid(), format, ...args); + } + } + var PHPWASM = { + O_APPEND: 1024, + O_NONBLOCK: 2048, + POLLHUP: 16, + SETFL_MASK: 3072, + init: function () { + if (PHPLoader.bindUserSpace) { + addOnInit(() => { + if (typeof PHPLoader.processId !== 'number') { + throw new Error( + 'PHPLoader.processId must be set before init' + ); + } + Module['userSpace'] = PHPLoader.bindUserSpace({ + pid: PHPLoader.processId, + constants: { + F_GETFL: Number('3'), + O_ACCMODE: Number('2097155'), + O_RDONLY: Number('0'), + O_WRONLY: Number('1'), + O_APPEND: Number('1024'), + O_NONBLOCK: Number('2048'), + F_SETFL: Number('4'), + F_GETLK: Number('12'), + F_SETLK: Number('13'), + F_SETLKW: Number('14'), + SEEK_SET: Number('0'), + SEEK_CUR: Number('1'), + SEEK_END: Number('2'), + F_GETFL: Number('3'), + O_ACCMODE: Number('2097155'), + O_RDONLY: Number('0'), + O_WRONLY: Number('1'), + O_APPEND: Number('1024'), + O_NONBLOCK: Number('2048'), + F_SETFL: Number('4'), + F_GETLK: Number('12'), + F_SETLK: Number('13'), + F_SETLKW: Number('14'), + SEEK_SET: Number('0'), + SEEK_CUR: Number('1'), + SEEK_END: Number('2'), + F_RDLCK: 0, + F_WRLCK: 1, + F_UNLCK: 2, + LOCK_SH: 1, + LOCK_EX: 2, + LOCK_NB: 4, + LOCK_UN: 8, + }, + errnoCodes: ERRNO_CODES, + memory: { + HEAP8: { + get(offset) { + return HEAP8[offset]; + }, + set(offset, value) { + HEAP8[offset] = value; + }, + }, + HEAPU8: { + get(offset) { + return HEAPU8[offset]; + }, + set(offset, value) { + HEAPU8[offset] = value; + }, + }, + HEAP16: { + get(offset) { + return HEAP16[offset]; + }, + set(offset, value) { + HEAP16[offset] = value; + }, + }, + HEAPU16: { + get(offset) { + return HEAPU16[offset]; + }, + set(offset, value) { + HEAPU16[offset] = value; + }, + }, + HEAP32: { + get(offset) { + return HEAP32[offset]; + }, + set(offset, value) { + HEAP32[offset] = value; + }, + }, + HEAPU32: { + get(offset) { + return HEAPU32[offset]; + }, + set(offset, value) { + HEAPU32[offset] = value; + }, + }, + HEAPF32: { + get(offset) { + return HEAPF32[offset]; + }, + set(offset, value) { + HEAPF32[offset] = value; + }, + }, + HEAP64: { + get(offset) { + return HEAP64[offset]; + }, + set(offset, value) { + HEAP64[offset] = value; + }, + }, + HEAPU64: { + get(offset) { + return HEAPU64[offset]; + }, + set(offset, value) { + HEAPU64[offset] = value; + }, + }, + HEAPF64: { + get(offset) { + return HEAPF64[offset]; + }, + set(offset, value) { + HEAPF64[offset] = value; + }, + }, + }, + wasmImports: Object.assign( + {}, + wasmImports, + typeof _builtin_fd_close === 'function' + ? { builtin_fd_close: _builtin_fd_close } + : {}, + typeof _builtin_fcntl64 === 'function' + ? { builtin_fcntl64: _builtin_fcntl64 } + : {} + ), + wasmExports, + syscalls: SYSCALLS, + FS, + PROXYFS, + NODEFS, + }); + }); + } + Module['ENV'] = Module['ENV'] || {}; + Module['ENV']['PATH'] = [ + Module['ENV']['PATH'], + '/internal/shared/bin', + ] + .filter(Boolean) + .join(':'); + FS.mkdir('/request'); + FS.mkdir('/internal'); + if (PHPLoader.nativeInternalDirPath) { + FS.mount( + FS.filesystems.NODEFS, + { root: PHPLoader.nativeInternalDirPath }, + '/internal' + ); + } + FS.mkdirTree('/internal/shared'); + FS.mkdirTree('/internal/shared/preload'); + FS.mkdirTree('/internal/shared/bin'); + const originalOnRuntimeInitialized = Module['onRuntimeInitialized']; + Module['onRuntimeInitialized'] = () => { + const { node: phpBinaryNode } = FS.lookupPath( + '/internal/shared/bin/php', + { noent_okay: true } + ); + if (!phpBinaryNode) { + FS.writeFile( + '/internal/shared/bin/php', + new TextEncoder().encode('#!/bin/sh\nphp "$@"') + ); + FS.chmod('/internal/shared/bin/php', 493); + } + originalOnRuntimeInitialized(); + }; + FS.registerDevice(FS.makedev(64, 0), { + open: () => {}, + close: () => {}, + read: () => 0, + write: (stream, buffer, offset, length, pos) => { + const chunk = buffer.subarray(offset, offset + length); + PHPWASM.onStdout(chunk); + return length; + }, + }); + FS.mkdev('/request/stdout', FS.makedev(64, 0)); + FS.registerDevice(FS.makedev(63, 0), { + open: () => {}, + close: () => {}, + read: () => 0, + write: (stream, buffer, offset, length, pos) => { + const chunk = buffer.subarray(offset, offset + length); + PHPWASM.onStderr(chunk); + return length; + }, + }); + FS.mkdev('/request/stderr', FS.makedev(63, 0)); + FS.registerDevice(FS.makedev(62, 0), { + open: () => {}, + close: () => {}, + read: () => 0, + write: (stream, buffer, offset, length, pos) => { + const chunk = buffer.subarray(offset, offset + length); + PHPWASM.onHeaders(chunk); + return length; + }, + }); + FS.mkdev('/request/headers', FS.makedev(62, 0)); + PHPWASM.EventEmitter = ENVIRONMENT_IS_NODE + ? require('events').EventEmitter + : class EventEmitter { + constructor() { + this.listeners = {}; + } + emit(eventName, data) { + if (this.listeners[eventName]) { + this.listeners[eventName].forEach( + (callback) => { + callback(data); + } + ); + } + } + once(eventName, callback) { + const self = this; + function removedCallback() { + callback(...arguments); + self.removeListener(eventName, removedCallback); + } + this.on(eventName, removedCallback); + } + removeAllListeners(eventName) { + if (eventName) { + delete this.listeners[eventName]; + } else { + this.listeners = {}; + } + } + removeListener(eventName, callback) { + if (this.listeners[eventName]) { + const idx = + this.listeners[eventName].indexOf(callback); + if (idx !== -1) { + this.listeners[eventName].splice(idx, 1); + } + } + } + }; + PHPWASM.processTable = {}; + PHPWASM.input_devices = {}; + const originalWrite = TTY.stream_ops.write; + TTY.stream_ops.write = function (stream, ...rest) { + const retval = originalWrite(stream, ...rest); + stream.tty.ops.fsync(stream.tty); + return retval; + }; + const originalPutChar = TTY.stream_ops.put_char; + TTY.stream_ops.put_char = function (tty, val) { + if (val === 10) tty.output.push(val); + return originalPutChar(tty, val); + }; + }, + onHeaders: function (chunk) { + if (Module['onHeaders']) { + Module['onHeaders'](chunk); + return; + } + console.log('headers', { chunk }); + }, + onStdout: function (chunk) { + if (Module['onStdout']) { + Module['onStdout'](chunk); + return; + } + if (ENVIRONMENT_IS_NODE) { + process.stdout.write(chunk); + } else { + console.log('stdout', { chunk }); + } + }, + onStderr: function (chunk) { + if (Module['onStderr']) { + Module['onStderr'](chunk); + return; + } + if (ENVIRONMENT_IS_NODE) { + process.stderr.write(chunk); + } else { + console.warn('stderr', { chunk }); + } + }, + getAllWebSockets: function (sock) { + const webSockets = new Set(); + if (sock.server) { + sock.server.clients.forEach((ws) => { + webSockets.add(ws); + }); + } + for (const peer of PHPWASM.getAllPeers(sock)) { + webSockets.add(peer.socket); + } + return Array.from(webSockets); + }, + getAllPeers: function (sock) { + const peers = new Set(); + if (sock.server) { + sock.pending + .filter((pending) => pending.peers) + .forEach((pending) => { + for (const peer of Object.values(pending.peers)) { + peers.add(peer); + } + }); + } + if (sock.peers) { + for (const peer of Object.values(sock.peers)) { + peers.add(peer); + } + } + return Array.from(peers); + }, + awaitData: function (ws) { + return PHPWASM.awaitEvent(ws, 'message'); + }, + awaitConnection: function (ws) { + if (ws.OPEN === ws.readyState) { + return [Promise.resolve(), PHPWASM.noop]; + } + return PHPWASM.awaitEvent(ws, 'open'); + }, + awaitClose: function (ws) { + if ([ws.CLOSING, ws.CLOSED].includes(ws.readyState)) { + return [Promise.resolve(), PHPWASM.noop]; + } + return PHPWASM.awaitEvent(ws, 'close'); + }, + awaitError: function (ws) { + if ([ws.CLOSING, ws.CLOSED].includes(ws.readyState)) { + return [Promise.resolve(), PHPWASM.noop]; + } + return PHPWASM.awaitEvent(ws, 'error'); + }, + awaitEvent: function (ws, event) { + let resolve; + const listener = () => { + resolve(); + }; + const promise = new Promise(function (_resolve) { + resolve = _resolve; + ws.once(event, listener); + }); + const cancel = () => { + ws.removeListener(event, listener); + setTimeout(resolve); + }; + return [promise, cancel]; + }, + noop: function () {}, + spawnProcess: function (command, args, options) { + if (Module['spawnProcess']) { + const spawned = Module['spawnProcess'](command, args, { + ...options, + shell: true, + stdio: ['pipe', 'pipe', 'pipe'], + }); + if (spawned && !('then' in spawned) && 'on' in spawned) { + return spawned; + } + return Promise.resolve(spawned).then(function (spawned) { + if (!spawned || !spawned.on) { + throw new Error( + 'spawnProcess() must return an EventEmitter but returned a different type.' + ); + } + return spawned; + }); + } + const e = new Error( + 'popen(), proc_open() etc. are unsupported on this PHP instance. Call php.setSpawnHandler() ' + + 'and provide a callback to handle spawning processes, or disable a popen(), proc_open() ' + + 'and similar functions via php.ini.' + ); + e.code = 'SPAWN_UNSUPPORTED'; + throw e; + }, + shutdownSocket: function (socketd, how) { + const sock = getSocketFromFD(socketd); + const peer = Object.values(sock.peers)[0]; + if (!peer) { + return -1; + } + try { + peer.socket.close(); + SOCKFS.websocket_sock_ops.removePeer(sock, peer); + return 0; + } catch (e) { + console.log('Socket shutdown error', e); + return -1; + } + }, + }; + function _wasm_connect(sockfd, addr, addrlen) { + if (!('Suspending' in WebAssembly)) { + var sock = getSocketFromFD(sockfd); + var info = getSocketAddress(addr, addrlen); + sock.sock_ops.connect(sock, info.addr, info.port); + return 0; + } + return Asyncify.handleSleep((wakeUp) => { + let sock; + try { + sock = getSocketFromFD(sockfd); + } catch (e) { + wakeUp(-ERRNO_CODES.EBADF); + return; + } + if (!sock) { + wakeUp(-ERRNO_CODES.EBADF); + return; + } + let info; + try { + info = getSocketAddress(addr, addrlen); + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) { + wakeUp(-ERRNO_CODES.EFAULT); + return; + } + wakeUp(-e.errno); + return; + } + try { + sock.sock_ops.connect(sock, info.addr, info.port); + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) { + wakeUp(-ERRNO_CODES.ECONNREFUSED); + return; + } + wakeUp(-e.errno); + return; + } + const webSockets = PHPWASM.getAllWebSockets(sock); + if (!webSockets.length) { + wakeUp(-ERRNO_CODES.ECONNREFUSED); + return; + } + const ws = webSockets[0]; + if (ws.readyState === ws.OPEN) { + wakeUp(0); + return; + } + if (ws.readyState === ws.CLOSING || ws.readyState === ws.CLOSED) { + wakeUp(-ERRNO_CODES.ECONNREFUSED); + return; + } + const timeout = 3e4; + let resolved = false; + const timeoutId = setTimeout(() => { + if (!resolved) { + resolved = true; + wakeUp(-ERRNO_CODES.ETIMEDOUT); + } + }, timeout); + const handleOpen = () => { + if (!resolved) { + resolved = true; + clearTimeout(timeoutId); + ws.removeEventListener('error', handleError); + ws.removeEventListener('close', handleClose); + wakeUp(0); + } + }; + const handleError = () => { + if (!resolved) { + resolved = true; + clearTimeout(timeoutId); + ws.removeEventListener('open', handleOpen); + ws.removeEventListener('close', handleClose); + wakeUp(-ERRNO_CODES.ECONNREFUSED); + } + }; + const handleClose = () => { + if (!resolved) { + resolved = true; + clearTimeout(timeoutId); + ws.removeEventListener('open', handleOpen); + ws.removeEventListener('error', handleError); + wakeUp(-ERRNO_CODES.ECONNREFUSED); + } + }; + ws.addEventListener('open', handleOpen); + ws.addEventListener('error', handleError); + ws.addEventListener('close', handleClose); + }); + } + function ___syscall_connect(sockfd, addr, addrlen, d1, d2, d3) { + return _wasm_connect(sockfd, addr, addrlen); + } + ___syscall_connect.sig = 'iippiii'; + function ___syscall_dup(fd) { + try { + var old = SYSCALLS.getStreamFromFD(fd); + return FS.dupStream(old).fd; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + ___syscall_dup.sig = 'ii'; + function ___syscall_dup3(fd, newfd, flags) { + try { + var old = SYSCALLS.getStreamFromFD(fd); + if (old.fd === newfd) return -28; + if (newfd < 0 || newfd >= FS.MAX_OPEN_FDS) return -8; + var existing = FS.getStream(newfd); + if (existing) FS.close(existing); + return FS.dupStream(old, newfd).fd; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + ___syscall_dup3.sig = 'iiii'; + function ___syscall_faccessat(dirfd, path, amode, flags) { + try { + path = SYSCALLS.getStr(path); + path = SYSCALLS.calculateAt(dirfd, path); + if (amode & ~7) { + return -28; + } + var lookup = FS.lookupPath(path, { follow: true }); + var node = lookup.node; + if (!node) { + return -44; + } + var perms = ''; + if (amode & 4) perms += 'r'; + if (amode & 2) perms += 'w'; + if (amode & 1) perms += 'x'; + if (perms && FS.nodePermissions(node, perms)) { + return -2; + } + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + ___syscall_faccessat.sig = 'iipii'; + function ___syscall_fchmod(fd, mode) { + try { + FS.fchmod(fd, mode); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + ___syscall_fchmod.sig = 'iii'; + function ___syscall_fchown32(fd, owner, group) { + try { + FS.fchown(fd, owner, group); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + ___syscall_fchown32.sig = 'iiii'; + function ___syscall_fchownat(dirfd, path, owner, group, flags) { + try { + path = SYSCALLS.getStr(path); + var nofollow = flags & 256; + flags = flags & ~256; + path = SYSCALLS.calculateAt(dirfd, path); + (nofollow ? FS.lchown : FS.chown)(path, owner, group); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + ___syscall_fchownat.sig = 'iipiii'; + var syscallGetVarargI = () => { + var ret = HEAP32[+SYSCALLS.varargs >> 2]; + SYSCALLS.varargs += 4; + return ret; + }; + var syscallGetVarargP = syscallGetVarargI; + function ___syscall_fcntl64(fd, cmd, varargs) { + SYSCALLS.varargs = varargs; + try { + var stream = SYSCALLS.getStreamFromFD(fd); + switch (cmd) { + case 0: { + var arg = syscallGetVarargI(); + if (arg < 0) { + return -28; + } + while (FS.streams[arg]) { + arg++; + } + var newStream; + newStream = FS.dupStream(stream, arg); + return newStream.fd; + } + case 1: + case 2: + return 0; + case 3: + return stream.flags; + case 4: { + var arg = syscallGetVarargI(); + stream.flags |= arg; + return 0; + } + case 12: { + var arg = syscallGetVarargP(); + var offset = 0; + HEAP16[(arg + offset) >> 1] = 2; + return 0; + } + case 13: + case 14: + return 0; + } + return -28; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + ___syscall_fcntl64.sig = 'iiip'; + function ___syscall_fstat64(fd, buf) { + try { + return SYSCALLS.writeStat(buf, FS.fstat(fd)); + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + ___syscall_fstat64.sig = 'iip'; + var INT53_MAX = 9007199254740992; + var INT53_MIN = -9007199254740992; + var bigintToI53Checked = (num) => + num < INT53_MIN || num > INT53_MAX ? NaN : Number(num); + function ___syscall_ftruncate64(fd, length) { + length = bigintToI53Checked(length); + try { + if (isNaN(length)) return -61; + FS.ftruncate(fd, length); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + ___syscall_ftruncate64.sig = 'iij'; + function ___syscall_getcwd(buf, size) { + try { + if (size === 0) return -28; + var cwd = FS.cwd(); + var cwdLengthInBytes = lengthBytesUTF8(cwd) + 1; + if (size < cwdLengthInBytes) return -68; + stringToUTF8(cwd, buf, size); + return cwdLengthInBytes; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + ___syscall_getcwd.sig = 'ipp'; + function ___syscall_getdents64(fd, dirp, count) { + try { + var stream = SYSCALLS.getStreamFromFD(fd); + stream.getdents ||= FS.readdir(stream.path); + var struct_size = 280; + var pos = 0; + var off = FS.llseek(stream, 0, 1); + var startIdx = Math.floor(off / struct_size); + var endIdx = Math.min( + stream.getdents.length, + startIdx + Math.floor(count / struct_size) + ); + for (var idx = startIdx; idx < endIdx; idx++) { + var id; + var type; + var name = stream.getdents[idx]; + if (name === '.') { + id = stream.node.id; + type = 4; + } else if (name === '..') { + var lookup = FS.lookupPath(stream.path, { parent: true }); + id = lookup.node.id; + type = 4; + } else { + var child; + try { + child = FS.lookupNode(stream.node, name); + } catch (e) { + if (e?.errno === 28) { + continue; + } + throw e; + } + id = child.id; + type = FS.isChrdev(child.mode) + ? 2 + : FS.isDir(child.mode) + ? 4 + : FS.isLink(child.mode) + ? 10 + : 8; + } + HEAP64[(dirp + pos) >> 3] = BigInt(id); + HEAP64[(dirp + pos + 8) >> 3] = BigInt((idx + 1) * struct_size); + HEAP16[(dirp + pos + 16) >> 1] = 280; + HEAP8[dirp + pos + 18] = type; + stringToUTF8(name, dirp + pos + 19, 256); + pos += struct_size; + } + FS.llseek(stream, idx * struct_size, 0); + return pos; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + ___syscall_getdents64.sig = 'iipp'; + function ___syscall_getpeername(fd, addr, addrlen, d1, d2, d3) { + try { + var sock = getSocketFromFD(fd); + if (!sock.daddr) { + return -53; + } + var errno = writeSockaddr( + addr, + sock.family, + DNS.lookup_name(sock.daddr), + sock.dport, + addrlen + ); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + ___syscall_getpeername.sig = 'iippiii'; + function ___syscall_getsockname(fd, addr, addrlen, d1, d2, d3) { + try { + var sock = getSocketFromFD(fd); + var errno = writeSockaddr( + addr, + sock.family, + DNS.lookup_name(sock.saddr || '0.0.0.0'), + sock.sport, + addrlen + ); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + ___syscall_getsockname.sig = 'iippiii'; + function ___syscall_getsockopt(fd, level, optname, optval, optlen, d1) { + try { + var sock = getSocketFromFD(fd); + if (level === 1) { + if (optname === 4) { + HEAP32[optval >> 2] = sock.error; + HEAP32[optlen >> 2] = 4; + sock.error = null; + return 0; + } + } + return -50; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + ___syscall_getsockopt.sig = 'iiiippi'; + function ___syscall_ioctl(fd, op, varargs) { + SYSCALLS.varargs = varargs; + try { + var stream = SYSCALLS.getStreamFromFD(fd); + switch (op) { + case 21509: { + if (!stream.tty) return -59; + return 0; + } + case 21505: { + if (!stream.tty) return -59; + if (stream.tty.ops.ioctl_tcgets) { + var termios = stream.tty.ops.ioctl_tcgets(stream); + var argp = syscallGetVarargP(); + HEAP32[argp >> 2] = termios.c_iflag || 0; + HEAP32[(argp + 4) >> 2] = termios.c_oflag || 0; + HEAP32[(argp + 8) >> 2] = termios.c_cflag || 0; + HEAP32[(argp + 12) >> 2] = termios.c_lflag || 0; + for (var i = 0; i < 32; i++) { + HEAP8[argp + i + 17] = termios.c_cc[i] || 0; + } + return 0; + } + return 0; + } + case 21510: + case 21511: + case 21512: { + if (!stream.tty) return -59; + return 0; + } + case 21506: + case 21507: + case 21508: { + if (!stream.tty) return -59; + if (stream.tty.ops.ioctl_tcsets) { + var argp = syscallGetVarargP(); + var c_iflag = HEAP32[argp >> 2]; + var c_oflag = HEAP32[(argp + 4) >> 2]; + var c_cflag = HEAP32[(argp + 8) >> 2]; + var c_lflag = HEAP32[(argp + 12) >> 2]; + var c_cc = []; + for (var i = 0; i < 32; i++) { + c_cc.push(HEAP8[argp + i + 17]); + } + return stream.tty.ops.ioctl_tcsets(stream.tty, op, { + c_iflag, + c_oflag, + c_cflag, + c_lflag, + c_cc, + }); + } + return 0; + } + case 21519: { + if (!stream.tty) return -59; + var argp = syscallGetVarargP(); + HEAP32[argp >> 2] = 0; + return 0; + } + case 21520: { + if (!stream.tty) return -59; + return -28; + } + case 21537: + case 21531: { + var argp = syscallGetVarargP(); + return FS.ioctl(stream, op, argp); + } + case 21523: { + if (!stream.tty) return -59; + if (stream.tty.ops.ioctl_tiocgwinsz) { + var winsize = stream.tty.ops.ioctl_tiocgwinsz( + stream.tty + ); + var argp = syscallGetVarargP(); + HEAP16[argp >> 1] = winsize[0]; + HEAP16[(argp + 2) >> 1] = winsize[1]; + } + return 0; + } + case 21524: { + if (!stream.tty) return -59; + return 0; + } + case 21515: { + if (!stream.tty) return -59; + return 0; + } + default: + return -28; + } + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + ___syscall_ioctl.sig = 'iiip'; + function ___syscall_listen(fd, backlog) { + try { + var sock = getSocketFromFD(fd); + sock.sock_ops.listen(sock, backlog); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + ___syscall_listen.sig = 'iiiiiii'; + function ___syscall_lstat64(path, buf) { + try { + path = SYSCALLS.getStr(path); + return SYSCALLS.writeStat(buf, FS.lstat(path)); + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + ___syscall_lstat64.sig = 'ipp'; + function ___syscall_mkdirat(dirfd, path, mode) { + try { + path = SYSCALLS.getStr(path); + path = SYSCALLS.calculateAt(dirfd, path); + FS.mkdir(path, mode, 0); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + ___syscall_mkdirat.sig = 'iipi'; + function ___syscall_newfstatat(dirfd, path, buf, flags) { + try { + path = SYSCALLS.getStr(path); + var nofollow = flags & 256; + var allowEmpty = flags & 4096; + flags = flags & ~6400; + path = SYSCALLS.calculateAt(dirfd, path, allowEmpty); + return SYSCALLS.writeStat( + buf, + nofollow ? FS.lstat(path) : FS.stat(path) + ); + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + ___syscall_newfstatat.sig = 'iippi'; + function ___syscall_openat(dirfd, path, flags, varargs) { + SYSCALLS.varargs = varargs; + try { + path = SYSCALLS.getStr(path); + path = SYSCALLS.calculateAt(dirfd, path); + var mode = varargs ? syscallGetVarargI() : 0; + return FS.open(path, flags, mode).fd; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + ___syscall_openat.sig = 'iipip'; + var PIPEFS = { + BUCKET_BUFFER_SIZE: 8192, + mount(mount) { + return FS.createNode(null, '/', 16384 | 511, 0); + }, + createPipe() { + var pipe = { buckets: [], refcnt: 2, timestamp: new Date() }; + pipe.buckets.push({ + buffer: new Uint8Array(PIPEFS.BUCKET_BUFFER_SIZE), + offset: 0, + roffset: 0, + }); + var rName = PIPEFS.nextname(); + var wName = PIPEFS.nextname(); + var rNode = FS.createNode(PIPEFS.root, rName, 4096, 0); + var wNode = FS.createNode(PIPEFS.root, wName, 4096, 0); + rNode.pipe = pipe; + wNode.pipe = pipe; + var readableStream = FS.createStream({ + path: rName, + node: rNode, + flags: 0, + seekable: false, + stream_ops: PIPEFS.stream_ops, + }); + rNode.stream = readableStream; + var writableStream = FS.createStream({ + path: wName, + node: wNode, + flags: 1, + seekable: false, + stream_ops: PIPEFS.stream_ops, + }); + wNode.stream = writableStream; + return { + readable_fd: readableStream.fd, + writable_fd: writableStream.fd, + }; + }, + stream_ops: { + getattr(stream) { + var node = stream.node; + var timestamp = node.pipe.timestamp; + return { + dev: 14, + ino: node.id, + mode: 4480, + nlink: 1, + uid: 0, + gid: 0, + rdev: 0, + size: 0, + atime: timestamp, + mtime: timestamp, + ctime: timestamp, + blksize: 4096, + blocks: 0, + }; + }, + poll(stream) { + var pipe = stream.node.pipe; + if ((stream.flags & 2097155) === 1) { + return 256 | 4; + } + for (var bucket of pipe.buckets) { + if (bucket.offset - bucket.roffset > 0) { + return 64 | 1; + } + } + return 0; + }, + dup(stream) { + stream.node.pipe.refcnt++; + }, + ioctl(stream, request, varargs) { + return 28; + }, + fsync(stream) { + return 28; + }, + read(stream, buffer, offset, length, position) { + var pipe = stream.node.pipe; + var currentLength = 0; + for (var bucket of pipe.buckets) { + currentLength += bucket.offset - bucket.roffset; + } + var data = buffer.subarray(offset, offset + length); + if (length <= 0) { + return 0; + } + if (currentLength == 0) { + if (pipe.refcnt < 2) { + return 0; + } + throw new FS.ErrnoError(6); + } + var toRead = Math.min(currentLength, length); + var totalRead = toRead; + var toRemove = 0; + for (var bucket of pipe.buckets) { + var bucketSize = bucket.offset - bucket.roffset; + if (toRead <= bucketSize) { + var tmpSlice = bucket.buffer.subarray( + bucket.roffset, + bucket.offset + ); + if (toRead < bucketSize) { + tmpSlice = tmpSlice.subarray(0, toRead); + bucket.roffset += toRead; + } else { + toRemove++; + } + data.set(tmpSlice); + break; + } else { + var tmpSlice = bucket.buffer.subarray( + bucket.roffset, + bucket.offset + ); + data.set(tmpSlice); + data = data.subarray(tmpSlice.byteLength); + toRead -= tmpSlice.byteLength; + toRemove++; + } + } + if (toRemove && toRemove == pipe.buckets.length) { + toRemove--; + pipe.buckets[toRemove].offset = 0; + pipe.buckets[toRemove].roffset = 0; + } + pipe.buckets.splice(0, toRemove); + return totalRead; + }, + write(stream, buffer, offset, length, position) { + var pipe = stream.node.pipe; + var data = buffer.subarray(offset, offset + length); + var dataLen = data.byteLength; + if (dataLen <= 0) { + return 0; + } + var currBucket = null; + if (pipe.buckets.length == 0) { + currBucket = { + buffer: new Uint8Array(PIPEFS.BUCKET_BUFFER_SIZE), + offset: 0, + roffset: 0, + }; + pipe.buckets.push(currBucket); + } else { + currBucket = pipe.buckets[pipe.buckets.length - 1]; + } + var freeBytesInCurrBuffer = + PIPEFS.BUCKET_BUFFER_SIZE - currBucket.offset; + if (freeBytesInCurrBuffer >= dataLen) { + currBucket.buffer.set(data, currBucket.offset); + currBucket.offset += dataLen; + return dataLen; + } else if (freeBytesInCurrBuffer > 0) { + currBucket.buffer.set( + data.subarray(0, freeBytesInCurrBuffer), + currBucket.offset + ); + currBucket.offset += freeBytesInCurrBuffer; + data = data.subarray( + freeBytesInCurrBuffer, + data.byteLength + ); + } + var numBuckets = + (data.byteLength / PIPEFS.BUCKET_BUFFER_SIZE) | 0; + var remElements = data.byteLength % PIPEFS.BUCKET_BUFFER_SIZE; + for (var i = 0; i < numBuckets; i++) { + var newBucket = { + buffer: new Uint8Array(PIPEFS.BUCKET_BUFFER_SIZE), + offset: PIPEFS.BUCKET_BUFFER_SIZE, + roffset: 0, + }; + pipe.buckets.push(newBucket); + newBucket.buffer.set( + data.subarray(0, PIPEFS.BUCKET_BUFFER_SIZE) + ); + data = data.subarray( + PIPEFS.BUCKET_BUFFER_SIZE, + data.byteLength + ); + } + if (remElements > 0) { + var newBucket = { + buffer: new Uint8Array(PIPEFS.BUCKET_BUFFER_SIZE), + offset: data.byteLength, + roffset: 0, + }; + pipe.buckets.push(newBucket); + newBucket.buffer.set(data); + } + return dataLen; + }, + close(stream) { + var pipe = stream.node.pipe; + pipe.refcnt--; + if (pipe.refcnt === 0) { + pipe.buckets = null; + } + }, + }, + nextname() { + if (!PIPEFS.nextname.current) { + PIPEFS.nextname.current = 0; + } + return 'pipe[' + PIPEFS.nextname.current++ + ']'; + }, + }; + function ___syscall_pipe(fdPtr) { + try { + if (fdPtr == 0) { + throw new FS.ErrnoError(21); + } + var res = PIPEFS.createPipe(); + HEAP32[fdPtr >> 2] = res.readable_fd; + HEAP32[(fdPtr + 4) >> 2] = res.writable_fd; + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + ___syscall_pipe.sig = 'ip'; + function ___syscall_poll(fds, nfds, timeout) { + try { + var nonzero = 0; + for (var i = 0; i < nfds; i++) { + var pollfd = fds + 8 * i; + var fd = HEAP32[pollfd >> 2]; + var events = HEAP16[(pollfd + 4) >> 1]; + var mask = 32; + var stream = FS.getStream(fd); + if (stream) { + mask = SYSCALLS.DEFAULT_POLLMASK; + if (stream.stream_ops?.poll) { + mask = stream.stream_ops.poll(stream, -1); + } + } + mask &= events | 8 | 16; + if (mask) nonzero++; + HEAP16[(pollfd + 6) >> 1] = mask; + } + return nonzero; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + ___syscall_poll.sig = 'ipii'; + function ___syscall_readlinkat(dirfd, path, buf, bufsize) { + try { + path = SYSCALLS.getStr(path); + path = SYSCALLS.calculateAt(dirfd, path); + if (bufsize <= 0) return -28; + var ret = FS.readlink(path); + var len = Math.min(bufsize, lengthBytesUTF8(ret)); + var endChar = HEAP8[buf + len]; + stringToUTF8(ret, buf, bufsize + 1); + HEAP8[buf + len] = endChar; + return len; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + ___syscall_readlinkat.sig = 'iippp'; + function ___syscall_recvfrom(fd, buf, len, flags, addr, addrlen) { + try { + var sock = getSocketFromFD(fd); + var msg = sock.sock_ops.recvmsg( + sock, + len, + typeof flags !== 'undefined' ? flags : 0 + ); + if (!msg) return 0; + if (addr) { + var errno = writeSockaddr( + addr, + sock.family, + DNS.lookup_name(msg.addr), + msg.port, + addrlen + ); + } + HEAPU8.set(msg.buffer, buf); + return msg.buffer.byteLength; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + ___syscall_recvfrom.sig = 'iippipp'; + function ___syscall_renameat(olddirfd, oldpath, newdirfd, newpath) { + try { + oldpath = SYSCALLS.getStr(oldpath); + newpath = SYSCALLS.getStr(newpath); + oldpath = SYSCALLS.calculateAt(olddirfd, oldpath); + newpath = SYSCALLS.calculateAt(newdirfd, newpath); + FS.rename(oldpath, newpath); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + ___syscall_renameat.sig = 'iipip'; + function ___syscall_rmdir(path) { + try { + path = SYSCALLS.getStr(path); + FS.rmdir(path); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + ___syscall_rmdir.sig = 'ip'; + function ___syscall_sendto(fd, message, length, flags, addr, addr_len) { + try { + var sock = getSocketFromFD(fd); + if (!addr) { + return FS.write(sock.stream, HEAP8, message, length); + } + var dest = getSocketAddress(addr, addr_len); + return sock.sock_ops.sendmsg( + sock, + HEAP8, + message, + length, + dest.addr, + dest.port + ); + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + ___syscall_sendto.sig = 'iippipp'; + function ___syscall_socket(domain, type, protocol) { + try { + var sock = SOCKFS.createSocket(domain, type, protocol); + return sock.stream.fd; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + ___syscall_socket.sig = 'iiiiiii'; + function ___syscall_stat64(path, buf) { + try { + path = SYSCALLS.getStr(path); + return SYSCALLS.writeStat(buf, FS.stat(path)); + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + ___syscall_stat64.sig = 'ipp'; + function ___syscall_statfs64(path, size, buf) { + try { + SYSCALLS.writeStatFs(buf, FS.statfs(SYSCALLS.getStr(path))); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + ___syscall_statfs64.sig = 'ippp'; + function ___syscall_symlinkat(target, dirfd, linkpath) { + try { + target = SYSCALLS.getStr(target); + linkpath = SYSCALLS.getStr(linkpath); + linkpath = SYSCALLS.calculateAt(dirfd, linkpath); + FS.symlink(target, linkpath); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + ___syscall_symlinkat.sig = 'ipip'; + function ___syscall_unlinkat(dirfd, path, flags) { + try { + path = SYSCALLS.getStr(path); + path = SYSCALLS.calculateAt(dirfd, path); + if (!flags) { + FS.unlink(path); + } else if (flags === 512) { + FS.rmdir(path); + } else { + return -28; + } + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + ___syscall_unlinkat.sig = 'iipi'; + var readI53FromI64 = (ptr) => + HEAPU32[ptr >> 2] + HEAP32[(ptr + 4) >> 2] * 4294967296; + function ___syscall_utimensat(dirfd, path, times, flags) { + try { + path = SYSCALLS.getStr(path); + path = SYSCALLS.calculateAt(dirfd, path, true); + var now = Date.now(), + atime, + mtime; + if (!times) { + atime = now; + mtime = now; + } else { + var seconds = readI53FromI64(times); + var nanoseconds = HEAP32[(times + 8) >> 2]; + if (nanoseconds == 1073741823) { + atime = now; + } else if (nanoseconds == 1073741822) { + atime = null; + } else { + atime = seconds * 1e3 + nanoseconds / (1e3 * 1e3); + } + times += 16; + seconds = readI53FromI64(times); + nanoseconds = HEAP32[(times + 8) >> 2]; + if (nanoseconds == 1073741823) { + mtime = now; + } else if (nanoseconds == 1073741822) { + mtime = null; + } else { + mtime = seconds * 1e3 + nanoseconds / (1e3 * 1e3); + } + } + if ((mtime ?? atime) !== null) { + FS.utime(path, atime, mtime); + } + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + ___syscall_utimensat.sig = 'iippi'; + var __abort_js = () => abort(''); + __abort_js.sig = 'v'; + var dlSetError = (msg) => { + var sp = stackSave(); + var cmsg = stringToUTF8OnStack(msg); + ___dl_seterr(cmsg, 0); + stackRestore(sp); + }; + var dlopenInternal = (handle, jsflags) => { + var filename = UTF8ToString(handle + 36); + var flags = HEAP32[(handle + 4) >> 2]; + filename = PATH.normalize(filename); + var global = Boolean(flags & 256); + var localScope = global ? null : {}; + var combinedFlags = { + global, + nodelete: Boolean(flags & 4096), + loadAsync: jsflags.loadAsync, + }; + if (jsflags.loadAsync) { + return loadDynamicLibrary( + filename, + combinedFlags, + localScope, + handle + ); + } + try { + return loadDynamicLibrary( + filename, + combinedFlags, + localScope, + handle + ); + } catch (e) { + dlSetError(`could not load dynamic lib: ${filename}\n${e}`); + return 0; + } + }; + function __dlopen_js(handle) { + var jsflags = { loadAsync: false }; + return dlopenInternal(handle, jsflags); + } + __dlopen_js.sig = 'pp'; + var __dlsym_js = (handle, symbol, symbolIndex) => { + symbol = UTF8ToString(symbol); + var result; + var newSymIndex; + var lib = LDSO.loadedLibsByHandle[handle]; + newSymIndex = Object.keys(lib.exports).indexOf(symbol); + if (newSymIndex == -1 || lib.exports[symbol].stub) { + dlSetError( + `Tried to lookup unknown symbol "${symbol}" in dynamic lib: ${lib.name}` + ); + return 0; + } + result = lib.exports[symbol]; + if (typeof result == 'function') { + if (result.orig) { + result = result.orig; + } + var addr = getFunctionAddress(result); + if (addr) { + result = addr; + } else { + result = addFunction(result, result.sig); + HEAPU32[symbolIndex >> 2] = newSymIndex; + } + } + return result; + }; + __dlsym_js.sig = 'pppp'; + var __emscripten_lookup_name = (name) => { + var nameString = UTF8ToString(name); + return inetPton4(DNS.lookup_name(nameString)); + }; + __emscripten_lookup_name.sig = 'ip'; + var runtimeKeepaliveCounter = 0; + var __emscripten_runtime_keepalive_clear = () => { + noExitRuntime = false; + runtimeKeepaliveCounter = 0; + }; + __emscripten_runtime_keepalive_clear.sig = 'v'; + function __gmtime_js(time, tmPtr) { + time = bigintToI53Checked(time); + var date = new Date(time * 1e3); + HEAP32[tmPtr >> 2] = date.getUTCSeconds(); + HEAP32[(tmPtr + 4) >> 2] = date.getUTCMinutes(); + HEAP32[(tmPtr + 8) >> 2] = date.getUTCHours(); + HEAP32[(tmPtr + 12) >> 2] = date.getUTCDate(); + HEAP32[(tmPtr + 16) >> 2] = date.getUTCMonth(); + HEAP32[(tmPtr + 20) >> 2] = date.getUTCFullYear() - 1900; + HEAP32[(tmPtr + 24) >> 2] = date.getUTCDay(); + var start = Date.UTC(date.getUTCFullYear(), 0, 1, 0, 0, 0, 0); + var yday = ((date.getTime() - start) / (1e3 * 60 * 60 * 24)) | 0; + HEAP32[(tmPtr + 28) >> 2] = yday; + } + __gmtime_js.sig = 'vjp'; + var isLeapYear = (year) => + year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0); + var MONTH_DAYS_LEAP_CUMULATIVE = [ + 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, + ]; + var MONTH_DAYS_REGULAR_CUMULATIVE = [ + 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, + ]; + var ydayFromDate = (date) => { + var leap = isLeapYear(date.getFullYear()); + var monthDaysCumulative = leap + ? MONTH_DAYS_LEAP_CUMULATIVE + : MONTH_DAYS_REGULAR_CUMULATIVE; + var yday = monthDaysCumulative[date.getMonth()] + date.getDate() - 1; + return yday; + }; + function __localtime_js(time, tmPtr) { + time = bigintToI53Checked(time); + var date = new Date(time * 1e3); + HEAP32[tmPtr >> 2] = date.getSeconds(); + HEAP32[(tmPtr + 4) >> 2] = date.getMinutes(); + HEAP32[(tmPtr + 8) >> 2] = date.getHours(); + HEAP32[(tmPtr + 12) >> 2] = date.getDate(); + HEAP32[(tmPtr + 16) >> 2] = date.getMonth(); + HEAP32[(tmPtr + 20) >> 2] = date.getFullYear() - 1900; + HEAP32[(tmPtr + 24) >> 2] = date.getDay(); + var yday = ydayFromDate(date) | 0; + HEAP32[(tmPtr + 28) >> 2] = yday; + HEAP32[(tmPtr + 36) >> 2] = -(date.getTimezoneOffset() * 60); + var start = new Date(date.getFullYear(), 0, 1); + var summerOffset = new Date( + date.getFullYear(), + 6, + 1 + ).getTimezoneOffset(); + var winterOffset = start.getTimezoneOffset(); + var dst = + (summerOffset != winterOffset && + date.getTimezoneOffset() == + Math.min(winterOffset, summerOffset)) | 0; + HEAP32[(tmPtr + 32) >> 2] = dst; + } + __localtime_js.sig = 'vjp'; + var __mktime_js = function (tmPtr) { + var ret = (() => { + var date = new Date( + HEAP32[(tmPtr + 20) >> 2] + 1900, + HEAP32[(tmPtr + 16) >> 2], + HEAP32[(tmPtr + 12) >> 2], + HEAP32[(tmPtr + 8) >> 2], + HEAP32[(tmPtr + 4) >> 2], + HEAP32[tmPtr >> 2], + 0 + ); + var dst = HEAP32[(tmPtr + 32) >> 2]; + var guessedOffset = date.getTimezoneOffset(); + var start = new Date(date.getFullYear(), 0, 1); + var summerOffset = new Date( + date.getFullYear(), + 6, + 1 + ).getTimezoneOffset(); + var winterOffset = start.getTimezoneOffset(); + var dstOffset = Math.min(winterOffset, summerOffset); + if (dst < 0) { + HEAP32[(tmPtr + 32) >> 2] = Number( + summerOffset != winterOffset && dstOffset == guessedOffset + ); + } else if (dst > 0 != (dstOffset == guessedOffset)) { + var nonDstOffset = Math.max(winterOffset, summerOffset); + var trueOffset = dst > 0 ? dstOffset : nonDstOffset; + date.setTime( + date.getTime() + (trueOffset - guessedOffset) * 6e4 + ); + } + HEAP32[(tmPtr + 24) >> 2] = date.getDay(); + var yday = ydayFromDate(date) | 0; + HEAP32[(tmPtr + 28) >> 2] = yday; + HEAP32[tmPtr >> 2] = date.getSeconds(); + HEAP32[(tmPtr + 4) >> 2] = date.getMinutes(); + HEAP32[(tmPtr + 8) >> 2] = date.getHours(); + HEAP32[(tmPtr + 12) >> 2] = date.getDate(); + HEAP32[(tmPtr + 16) >> 2] = date.getMonth(); + HEAP32[(tmPtr + 20) >> 2] = date.getYear(); + var timeMs = date.getTime(); + if (isNaN(timeMs)) { + return -1; + } + return timeMs / 1e3; + })(); + return BigInt(ret); + }; + __mktime_js.sig = 'jp'; + function __mmap_js(len, prot, flags, fd, offset, allocated, addr) { + offset = bigintToI53Checked(offset); + try { + var stream = SYSCALLS.getStreamFromFD(fd); + var res = FS.mmap(stream, len, offset, prot, flags); + var ptr = res.ptr; + HEAP32[allocated >> 2] = res.allocated; + HEAPU32[addr >> 2] = ptr; + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + __mmap_js.sig = 'ipiiijpp'; + function __munmap_js(addr, len, prot, flags, fd, offset) { + offset = bigintToI53Checked(offset); + try { + var stream = SYSCALLS.getStreamFromFD(fd); + if (prot & 2) { + SYSCALLS.doMsync(addr, stream, len, flags, offset); + } + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return -e.errno; + } + } + __munmap_js.sig = 'ippiiij'; + var timers = {}; + var handleException = (e) => { + if (e instanceof ExitStatus || e == 'unwind') { + return EXITSTATUS; + } + quit_(1, e); + }; + var keepRuntimeAlive = () => noExitRuntime || runtimeKeepaliveCounter > 0; + var _proc_exit = (code) => { + EXITSTATUS = code; + if (!keepRuntimeAlive()) { + Module['onExit']?.(code); + ABORT = true; + } + quit_(code, new ExitStatus(code)); + }; + _proc_exit.sig = 'vi'; + var exitJS = (status, implicit) => { + EXITSTATUS = status; + if (!keepRuntimeAlive()) { + exitRuntime(); + } + _proc_exit(status); + }; + var _exit = exitJS; + _exit.sig = 'vi'; + var maybeExit = () => { + if (runtimeExited) { + return; + } + if (!keepRuntimeAlive()) { + try { + _exit(EXITSTATUS); + } catch (e) { + handleException(e); + } + } + }; + var callUserCallback = (func) => { + if (runtimeExited || ABORT) { + return; + } + try { + func(); + maybeExit(); + } catch (e) { + handleException(e); + } + }; + var _emscripten_get_now = () => performance.now(); + _emscripten_get_now.sig = 'd'; + var __setitimer_js = (which, timeout_ms) => { + if (timers[which]) { + clearTimeout(timers[which].id); + delete timers[which]; + } + if (!timeout_ms) return 0; + var id = setTimeout(() => { + delete timers[which]; + callUserCallback(() => + __emscripten_timeout(which, _emscripten_get_now()) + ); + }, timeout_ms); + timers[which] = { id, timeout_ms }; + return 0; + }; + __setitimer_js.sig = 'iid'; + var __tzset_js = (timezone, daylight, std_name, dst_name) => { + var currentYear = new Date().getFullYear(); + var winter = new Date(currentYear, 0, 1); + var summer = new Date(currentYear, 6, 1); + var winterOffset = winter.getTimezoneOffset(); + var summerOffset = summer.getTimezoneOffset(); + var stdTimezoneOffset = Math.max(winterOffset, summerOffset); + HEAPU32[timezone >> 2] = stdTimezoneOffset * 60; + HEAP32[daylight >> 2] = Number(winterOffset != summerOffset); + var extractZone = (timezoneOffset) => { + var sign = timezoneOffset >= 0 ? '-' : '+'; + var absOffset = Math.abs(timezoneOffset); + var hours = String(Math.floor(absOffset / 60)).padStart(2, '0'); + var minutes = String(absOffset % 60).padStart(2, '0'); + return `UTC${sign}${hours}${minutes}`; + }; + var winterName = extractZone(winterOffset); + var summerName = extractZone(summerOffset); + if (summerOffset < winterOffset) { + stringToUTF8(winterName, std_name, 17); + stringToUTF8(summerName, dst_name, 17); + } else { + stringToUTF8(winterName, dst_name, 17); + stringToUTF8(summerName, std_name, 17); + } + }; + __tzset_js.sig = 'vpppp'; + var _emscripten_date_now = () => Date.now(); + _emscripten_date_now.sig = 'd'; + var nowIsMonotonic = 1; + var checkWasiClock = (clock_id) => clock_id >= 0 && clock_id <= 3; + function _clock_time_get(clk_id, ignored_precision, ptime) { + ignored_precision = bigintToI53Checked(ignored_precision); + if (!checkWasiClock(clk_id)) { + return 28; + } + var now; + if (clk_id === 0) { + now = _emscripten_date_now(); + } else if (nowIsMonotonic) { + now = _emscripten_get_now(); + } else { + return 52; + } + var nsec = Math.round(now * 1e3 * 1e3); + HEAP64[ptime >> 3] = BigInt(nsec); + return 0; + } + _clock_time_get.sig = 'iijp'; + var getHeapMax = () => 2147483648; + var _emscripten_get_heap_max = () => getHeapMax(); + _emscripten_get_heap_max.sig = 'p'; + var growMemory = (size) => { + var oldHeapSize = wasmMemory.buffer.byteLength; + var pages = ((size - oldHeapSize + 65535) / 65536) | 0; + try { + wasmMemory.grow(pages); + updateMemoryViews(); + return 1; + } catch (e) {} + }; + var _emscripten_resize_heap = (requestedSize) => { + var oldSize = HEAPU8.length; + requestedSize >>>= 0; + var maxHeapSize = getHeapMax(); + if (requestedSize > maxHeapSize) { + return false; + } + for (var cutDown = 1; cutDown <= 4; cutDown *= 2) { + var overGrownHeapSize = oldSize * (1 + 0.2 / cutDown); + overGrownHeapSize = Math.min( + overGrownHeapSize, + requestedSize + 100663296 + ); + var newSize = Math.min( + maxHeapSize, + alignMemory(Math.max(requestedSize, overGrownHeapSize), 65536) + ); + var replacement = growMemory(newSize); + if (replacement) { + return true; + } + } + return false; + }; + _emscripten_resize_heap.sig = 'ip'; + var runtimeKeepalivePush = () => { + runtimeKeepaliveCounter += 1; + }; + runtimeKeepalivePush.sig = 'v'; + var runtimeKeepalivePop = () => { + runtimeKeepaliveCounter -= 1; + }; + runtimeKeepalivePop.sig = 'v'; + var safeSetTimeout = (func, timeout) => { + runtimeKeepalivePush(); + return setTimeout(() => { + runtimeKeepalivePop(); + callUserCallback(func); + }, timeout); + }; + var _emscripten_sleep = (ms) => + Asyncify.handleSleep((wakeUp) => safeSetTimeout(wakeUp, ms)); + _emscripten_sleep.sig = 'vi'; + _emscripten_sleep.isAsync = true; + var ENV = PHPLoader.ENV || {}; + var getExecutableName = () => thisProgram || './this.program'; + var getEnvStrings = () => { + if (!getEnvStrings.strings) { + var lang = + ( + (typeof navigator == 'object' && navigator.language) || + 'C' + ).replace('-', '_') + '.UTF-8'; + var env = { + USER: 'web_user', + LOGNAME: 'web_user', + PATH: '/', + PWD: '/', + HOME: '/home/web_user', + LANG: lang, + _: getExecutableName(), + }; + for (var x in ENV) { + if (ENV[x] === undefined) delete env[x]; + else env[x] = ENV[x]; + } + var strings = []; + for (var x in env) { + strings.push(`${x}=${env[x]}`); + } + getEnvStrings.strings = strings; + } + return getEnvStrings.strings; + }; + var _environ_get = (__environ, environ_buf) => { + var bufSize = 0; + var envp = 0; + for (var string of getEnvStrings()) { + var ptr = environ_buf + bufSize; + HEAPU32[(__environ + envp) >> 2] = ptr; + bufSize += stringToUTF8(string, ptr, Infinity) + 1; + envp += 4; + } + return 0; + }; + _environ_get.sig = 'ipp'; + var _environ_sizes_get = (penviron_count, penviron_buf_size) => { + var strings = getEnvStrings(); + HEAPU32[penviron_count >> 2] = strings.length; + var bufSize = 0; + for (var string of strings) { + bufSize += lengthBytesUTF8(string) + 1; + } + HEAPU32[penviron_buf_size >> 2] = bufSize; + return 0; + }; + _environ_sizes_get.sig = 'ipp'; + function _fd_close(fd) { + try { + var stream = SYSCALLS.getStreamFromFD(fd); + FS.close(stream); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return e.errno; + } + } + _fd_close.sig = 'ii'; + function _fd_fdstat_get(fd, pbuf) { + try { + var rightsBase = 0; + var rightsInheriting = 0; + var flags = 0; + { + var stream = SYSCALLS.getStreamFromFD(fd); + var type = stream.tty + ? 2 + : FS.isDir(stream.mode) + ? 3 + : FS.isLink(stream.mode) + ? 7 + : 4; + } + HEAP8[pbuf] = type; + HEAP16[(pbuf + 2) >> 1] = flags; + HEAP64[(pbuf + 8) >> 3] = BigInt(rightsBase); + HEAP64[(pbuf + 16) >> 3] = BigInt(rightsInheriting); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return e.errno; + } + } + _fd_fdstat_get.sig = 'iip'; + var doReadv = (stream, iov, iovcnt, offset) => { + var ret = 0; + for (var i = 0; i < iovcnt; i++) { + var ptr = HEAPU32[iov >> 2]; + var len = HEAPU32[(iov + 4) >> 2]; + iov += 8; + var curr = FS.read(stream, HEAP8, ptr, len, offset); + if (curr < 0) return -1; + ret += curr; + if (curr < len) break; + if (typeof offset != 'undefined') { + offset += curr; + } + } + return ret; + }; + function _fd_read(fd, iov, iovcnt, pnum) { + try { + var stream = SYSCALLS.getStreamFromFD(fd); + var num = doReadv(stream, iov, iovcnt); + HEAPU32[pnum >> 2] = num; + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return e.errno; + } + } + _fd_read.sig = 'iippp'; + function _fd_seek(fd, offset, whence, newOffset) { + offset = bigintToI53Checked(offset); + try { + if (isNaN(offset)) return 61; + var stream = SYSCALLS.getStreamFromFD(fd); + FS.llseek(stream, offset, whence); + HEAP64[newOffset >> 3] = BigInt(stream.position); + if (stream.getdents && offset === 0 && whence === 0) + stream.getdents = null; + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return e.errno; + } + } + _fd_seek.sig = 'iijip'; + var _fd_sync = function (fd) { + try { + var stream = SYSCALLS.getStreamFromFD(fd); + return Asyncify.handleSleep((wakeUp) => { + var mount = stream.node.mount; + if (!mount.type.syncfs) { + wakeUp(0); + return; + } + mount.type.syncfs(mount, false, (err) => { + wakeUp(err ? 29 : 0); + }); + }); + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return e.errno; + } + }; + _fd_sync.sig = 'ii'; + _fd_sync.isAsync = true; + var doWritev = (stream, iov, iovcnt, offset) => { + var ret = 0; + for (var i = 0; i < iovcnt; i++) { + var ptr = HEAPU32[iov >> 2]; + var len = HEAPU32[(iov + 4) >> 2]; + iov += 8; + var curr = FS.write(stream, HEAP8, ptr, len, offset); + if (curr < 0) return -1; + ret += curr; + if (curr < len) { + break; + } + if (typeof offset != 'undefined') { + offset += curr; + } + } + return ret; + }; + function _fd_write(fd, iov, iovcnt, pnum) { + try { + var stream = SYSCALLS.getStreamFromFD(fd); + var num = doWritev(stream, iov, iovcnt); + HEAPU32[pnum >> 2] = num; + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e; + return e.errno; + } + } + _fd_write.sig = 'iippp'; + var _getaddrinfo = (node, service, hint, out) => { + var addr = 0; + var port = 0; + var flags = 0; + var family = 0; + var type = 0; + var proto = 0; + var ai; + function allocaddrinfo(family, type, proto, canon, addr, port) { + var sa, salen, ai; + var errno; + salen = family === 10 ? 28 : 16; + addr = family === 10 ? inetNtop6(addr) : inetNtop4(addr); + sa = _malloc(salen); + errno = writeSockaddr(sa, family, addr, port); + ai = _malloc(32); + HEAP32[(ai + 4) >> 2] = family; + HEAP32[(ai + 8) >> 2] = type; + HEAP32[(ai + 12) >> 2] = proto; + HEAPU32[(ai + 24) >> 2] = canon; + HEAPU32[(ai + 20) >> 2] = sa; + if (family === 10) { + HEAP32[(ai + 16) >> 2] = 28; + } else { + HEAP32[(ai + 16) >> 2] = 16; + } + HEAP32[(ai + 28) >> 2] = 0; + return ai; + } + if (hint) { + flags = HEAP32[hint >> 2]; + family = HEAP32[(hint + 4) >> 2]; + type = HEAP32[(hint + 8) >> 2]; + proto = HEAP32[(hint + 12) >> 2]; + } + if (type && !proto) { + proto = type === 2 ? 17 : 6; + } + if (!type && proto) { + type = proto === 17 ? 2 : 1; + } + if (proto === 0) { + proto = 6; + } + if (type === 0) { + type = 1; + } + if (!node && !service) { + return -2; + } + if (flags & ~(1 | 2 | 4 | 1024 | 8 | 16 | 32)) { + return -1; + } + if (hint !== 0 && HEAP32[hint >> 2] & 2 && !node) { + return -1; + } + if (flags & 32) { + return -2; + } + if (type !== 0 && type !== 1 && type !== 2) { + return -7; + } + if (family !== 0 && family !== 2 && family !== 10) { + return -6; + } + if (service) { + service = UTF8ToString(service); + port = parseInt(service, 10); + if (isNaN(port)) { + if (flags & 1024) { + return -2; + } + return -8; + } + } + if (!node) { + if (family === 0) { + family = 2; + } + if ((flags & 1) === 0) { + if (family === 2) { + addr = _htonl(2130706433); + } else { + addr = [0, 0, 0, _htonl(1)]; + } + } + ai = allocaddrinfo(family, type, proto, null, addr, port); + HEAPU32[out >> 2] = ai; + return 0; + } + node = UTF8ToString(node); + addr = inetPton4(node); + if (addr !== null) { + if (family === 0 || family === 2) { + family = 2; + } else if (family === 10 && flags & 8) { + addr = [0, 0, _htonl(65535), addr]; + family = 10; + } else { + return -2; + } + } else { + addr = inetPton6(node); + if (addr !== null) { + if (family === 0 || family === 10) { + family = 10; + } else { + return -2; + } + } + } + if (addr != null) { + ai = allocaddrinfo(family, type, proto, node, addr, port); + HEAPU32[out >> 2] = ai; + return 0; + } + if (flags & 4) { + return -2; + } + node = DNS.lookup_name(node); + addr = inetPton4(node); + if (family === 0) { + family = 2; + } else if (family === 10) { + addr = [0, 0, _htonl(65535), addr]; + } + ai = allocaddrinfo(family, type, proto, null, addr, port); + HEAPU32[out >> 2] = ai; + return 0; + }; + _getaddrinfo.sig = 'ipppp'; + var _getnameinfo = (sa, salen, node, nodelen, serv, servlen, flags) => { + var info = readSockaddr(sa, salen); + if (info.errno) { + return -6; + } + var port = info.port; + var addr = info.addr; + var overflowed = false; + if (node && nodelen) { + var lookup; + if (flags & 1 || !(lookup = DNS.lookup_addr(addr))) { + if (flags & 8) { + return -2; + } + } else { + addr = lookup; + } + var numBytesWrittenExclNull = stringToUTF8(addr, node, nodelen); + if (numBytesWrittenExclNull + 1 >= nodelen) { + overflowed = true; + } + } + if (serv && servlen) { + port = '' + port; + var numBytesWrittenExclNull = stringToUTF8(port, serv, servlen); + if (numBytesWrittenExclNull + 1 >= servlen) { + overflowed = true; + } + } + if (overflowed) { + return -12; + } + return 0; + }; + _getnameinfo.sig = 'ipipipii'; + var Protocols = { list: [], map: {} }; + var stringToAscii = (str, buffer) => { + for (var i = 0; i < str.length; ++i) { + HEAP8[buffer++] = str.charCodeAt(i); + } + HEAP8[buffer] = 0; + }; + var _setprotoent = (stayopen) => { + function allocprotoent(name, proto, aliases) { + var nameBuf = _malloc(name.length + 1); + stringToAscii(name, nameBuf); + var j = 0; + var length = aliases.length; + var aliasListBuf = _malloc((length + 1) * 4); + for (var i = 0; i < length; i++, j += 4) { + var alias = aliases[i]; + var aliasBuf = _malloc(alias.length + 1); + stringToAscii(alias, aliasBuf); + HEAPU32[(aliasListBuf + j) >> 2] = aliasBuf; + } + HEAPU32[(aliasListBuf + j) >> 2] = 0; + var pe = _malloc(12); + HEAPU32[pe >> 2] = nameBuf; + HEAPU32[(pe + 4) >> 2] = aliasListBuf; + HEAP32[(pe + 8) >> 2] = proto; + return pe; + } + var list = Protocols.list; + var map = Protocols.map; + if (list.length === 0) { + var entry = allocprotoent('tcp', 6, ['TCP']); + list.push(entry); + map['tcp'] = map['6'] = entry; + entry = allocprotoent('udp', 17, ['UDP']); + list.push(entry); + map['udp'] = map['17'] = entry; + } + _setprotoent.index = 0; + }; + _setprotoent.sig = 'vi'; + var _getprotobyname = (name) => { + name = UTF8ToString(name); + _setprotoent(true); + var result = Protocols.map[name]; + return result; + }; + _getprotobyname.sig = 'pp'; + var _getprotobynumber = (number) => { + _setprotoent(true); + var result = Protocols.map[number]; + return result; + }; + _getprotobynumber.sig = 'pi'; + function _js_open_process( + command, + argsPtr, + argsLength, + descriptorsPtr, + descriptorsLength, + cwdPtr, + cwdLength, + envPtr, + envLength + ) { + if (!command) { + ___errno_location(ERRNO_CODES.EINVAL); + return -1; + } + const cmdstr = UTF8ToString(command); + if (!cmdstr.length) { + ___errno_location(ERRNO_CODES.EINVAL); + return -1; + } + let argsArray = []; + if (argsLength) { + for (var i = 0; i < argsLength; i++) { + const charPointer = argsPtr + i * 4; + argsArray.push(UTF8ToString(HEAPU32[charPointer >> 2])); + } + } + const cwdstr = cwdPtr ? UTF8ToString(cwdPtr) : FS.cwd(); + let envObject = null; + if (envLength) { + envObject = {}; + for (var i = 0; i < envLength; i++) { + const envPointer = envPtr + i * 4; + const envEntry = UTF8ToString(HEAPU32[envPointer >> 2]); + const splitAt = envEntry.indexOf('='); + if (splitAt === -1) { + continue; + } + const key = envEntry.substring(0, splitAt); + const value = envEntry.substring(splitAt + 1); + envObject[key] = value; + } + } + var std = {}; + for (var i = 0; i < descriptorsLength; i++) { + const descriptorPtr = HEAPU32[(descriptorsPtr + i * 4) >> 2]; + std[HEAPU32[descriptorPtr >> 2]] = { + child: HEAPU32[(descriptorPtr + 4) >> 2], + parent: HEAPU32[(descriptorPtr + 8) >> 2], + }; + if (i === 0) { + HEAPU32[(descriptorPtr + 8) >> 2] = + std[HEAPU32[descriptorPtr >> 2]].parent; + HEAPU32[(descriptorPtr + 4) >> 2] = + std[HEAPU32[descriptorPtr >> 2]].child; + } + } + return Asyncify.handleAsync(async () => { + let cp; + try { + const options = {}; + if (cwdstr !== null) { + options.cwd = cwdstr; + } + if (envObject !== null) { + options.env = envObject; + } + cp = PHPWASM.spawnProcess(cmdstr, argsArray, options); + if (cp instanceof Promise) { + cp = await cp; + } + } catch (e) { + if (e.code === 'SPAWN_UNSUPPORTED') { + ___errno_location(ERRNO_CODES.ENOSYS); + return -1; + } + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) + throw e; + ___errno_location(e.code); + return -1; + } + const ProcInfo = { pid: cp.pid, exited: false }; + PHPWASM.processTable[ProcInfo.pid] = ProcInfo; + const stdinParentFd = std[0]?.parent, + stdinChildFd = std[0]?.child, + stdoutChildFd = std[1]?.child, + stdoutParentFd = std[1]?.parent, + stderrChildFd = std[2]?.child, + stderrParentFd = std[2]?.parent; + cp.on('exit', function (code) { + for (const fd of [stdoutChildFd, stderrChildFd, stdinChildFd]) { + if (FS.streams[fd] && !FS.isClosed(FS.streams[fd])) { + FS.close(FS.streams[fd]); + } + } + ProcInfo.exitCode = code; + ProcInfo.exited = true; + }); + if (stdoutChildFd) { + const stdoutStream = SYSCALLS.getStreamFromFD(stdoutChildFd); + let stdoutAt = 0; + cp.stdout.on('data', function (data) { + stdoutStream.stream_ops.write( + stdoutStream, + data, + 0, + data.length, + stdoutAt + ); + stdoutAt += data.length; + }); + } + if (stderrChildFd) { + const stderrStream = SYSCALLS.getStreamFromFD(stderrChildFd); + let stderrAt = 0; + cp.stderr.on('data', function (data) { + stderrStream.stream_ops.write( + stderrStream, + data, + 0, + data.length, + stderrAt + ); + stderrAt += data.length; + }); + } + try { + await new Promise((resolve, reject) => { + let resolved = false; + cp.on('spawn', () => { + if (resolved) return; + resolved = true; + resolve(); + }); + cp.on('error', (e) => { + if (resolved) return; + resolved = true; + reject(e); + }); + cp.on('exit', function (code) { + if (resolved) return; + resolved = true; + if (code === 0) { + resolve(); + } else { + reject( + new Error(`Process exited with code ${code}`) + ); + } + }); + setTimeout(() => { + if (resolved) return; + resolved = true; + reject(new Error('Process timed out')); + }, 5e3); + }); + } catch (e) { + console.error(e); + return ProcInfo.pid; + } + if (stdinChildFd) { + let stdinStream; + try { + stdinStream = SYSCALLS.getStreamFromFD(stdinChildFd); + } catch (e) { + ___errno_location(ERRNO_CODES.EBADF); + return ProcInfo.pid; + } + if (!stdinStream?.node) { + return ProcInfo.pid; + } + const CHUNK_SIZE = 1024; + const iov = _malloc(16); + const pnum = _malloc(4); + const buffer = _malloc(CHUNK_SIZE); + HEAPU32[iov >> 2] = buffer; + HEAPU32[(iov + 4) >> 2] = CHUNK_SIZE; + function pump() { + try { + while (true) { + if (cp.killed) { + stopPumpingAndCloseStdin(); + return; + } + const result = js_fd_read( + stdinChildFd, + iov, + 1, + pnum, + false + ); + const bytesRead = HEAPU32[pnum >> 2]; + if (result === 0 && bytesRead > 0) { + const wrote = HEAPU8.subarray( + buffer, + buffer + bytesRead + ); + cp.stdin.write(wrote); + } else if (result === 0 && bytesRead === 0) { + stopPumpingAndCloseStdin(); + break; + } else if (result === ERRNO_CODES.EAGAIN) { + break; + } else { + throw new FS.ErrnoError(result); + } + } + } catch (e) { + if ( + typeof FS == 'undefined' || + !(e.name === 'ErrnoError') + ) { + throw e; + } + ___errno_location(e.errno); + stopPumpingAndCloseStdin(); + } + } + function stopPumpingAndCloseStdin() { + clearInterval(interval); + if (!cp.stdin.closed) { + cp.stdin.end(); + } + _wasm_free(buffer); + _wasm_free(iov); + _wasm_free(pnum); + } + const interval = setInterval(pump, 20); + pump(); + } + return ProcInfo.pid; + }); + } + var arraySum = (array, index) => { + var sum = 0; + for (var i = 0; i <= index; sum += array[i++]) {} + return sum; + }; + var MONTH_DAYS_LEAP = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + var MONTH_DAYS_REGULAR = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + var addDays = (date, days) => { + var newDate = new Date(date.getTime()); + while (days > 0) { + var leap = isLeapYear(newDate.getFullYear()); + var currentMonth = newDate.getMonth(); + var daysInCurrentMonth = ( + leap ? MONTH_DAYS_LEAP : MONTH_DAYS_REGULAR + )[currentMonth]; + if (days > daysInCurrentMonth - newDate.getDate()) { + days -= daysInCurrentMonth - newDate.getDate() + 1; + newDate.setDate(1); + if (currentMonth < 11) { + newDate.setMonth(currentMonth + 1); + } else { + newDate.setMonth(0); + newDate.setFullYear(newDate.getFullYear() + 1); + } + } else { + newDate.setDate(newDate.getDate() + days); + return newDate; + } + } + return newDate; + }; + var _strptime = (buf, format, tm) => { + var pattern = UTF8ToString(format); + var SPECIAL_CHARS = '\\!@#$^&*()+=-[]/{}|:<>?,.'; + for (var i = 0, ii = SPECIAL_CHARS.length; i < ii; ++i) { + pattern = pattern.replace( + new RegExp('\\' + SPECIAL_CHARS[i], 'g'), + '\\' + SPECIAL_CHARS[i] + ); + } + var EQUIVALENT_MATCHERS = { + A: '%a', + B: '%b', + c: '%a %b %d %H:%M:%S %Y', + D: '%m\\/%d\\/%y', + e: '%d', + F: '%Y-%m-%d', + h: '%b', + R: '%H\\:%M', + r: '%I\\:%M\\:%S\\s%p', + T: '%H\\:%M\\:%S', + x: '%m\\/%d\\/(?:%y|%Y)', + X: '%H\\:%M\\:%S', + }; + var DATE_PATTERNS = { + a: '(?:Sun(?:day)?)|(?:Mon(?:day)?)|(?:Tue(?:sday)?)|(?:Wed(?:nesday)?)|(?:Thu(?:rsday)?)|(?:Fri(?:day)?)|(?:Sat(?:urday)?)', + b: '(?:Jan(?:uary)?)|(?:Feb(?:ruary)?)|(?:Mar(?:ch)?)|(?:Apr(?:il)?)|May|(?:Jun(?:e)?)|(?:Jul(?:y)?)|(?:Aug(?:ust)?)|(?:Sep(?:tember)?)|(?:Oct(?:ober)?)|(?:Nov(?:ember)?)|(?:Dec(?:ember)?)', + C: '\\d\\d', + d: '0[1-9]|[1-9](?!\\d)|1\\d|2\\d|30|31', + H: '\\d(?!\\d)|[0,1]\\d|20|21|22|23', + I: '\\d(?!\\d)|0\\d|10|11|12', + j: '00[1-9]|0?[1-9](?!\\d)|0?[1-9]\\d(?!\\d)|[1,2]\\d\\d|3[0-6]\\d', + m: '0[1-9]|[1-9](?!\\d)|10|11|12', + M: '0\\d|\\d(?!\\d)|[1-5]\\d', + n: ' ', + p: 'AM|am|PM|pm|A\\.M\\.|a\\.m\\.|P\\.M\\.|p\\.m\\.', + S: '0\\d|\\d(?!\\d)|[1-5]\\d|60', + U: '0\\d|\\d(?!\\d)|[1-4]\\d|50|51|52|53', + W: '0\\d|\\d(?!\\d)|[1-4]\\d|50|51|52|53', + w: '[0-6]', + y: '\\d\\d', + Y: '\\d\\d\\d\\d', + t: ' ', + z: 'Z|(?:[\\+\\-]\\d\\d:?(?:\\d\\d)?)', + }; + var MONTH_NUMBERS = { + JAN: 0, + FEB: 1, + MAR: 2, + APR: 3, + MAY: 4, + JUN: 5, + JUL: 6, + AUG: 7, + SEP: 8, + OCT: 9, + NOV: 10, + DEC: 11, + }; + var DAY_NUMBERS_SUN_FIRST = { + SUN: 0, + MON: 1, + TUE: 2, + WED: 3, + THU: 4, + FRI: 5, + SAT: 6, + }; + var DAY_NUMBERS_MON_FIRST = { + MON: 0, + TUE: 1, + WED: 2, + THU: 3, + FRI: 4, + SAT: 5, + SUN: 6, + }; + var capture = []; + var pattern_out = pattern + .replace(/%(.)/g, (m, c) => EQUIVALENT_MATCHERS[c] || m) + .replace(/%(.)/g, (_, c) => { + let pat = DATE_PATTERNS[c]; + if (pat) { + capture.push(c); + return `(${pat})`; + } else { + return c; + } + }) + .replace(/\s+/g, '\\s*'); + var matches = new RegExp('^' + pattern_out, 'i').exec( + UTF8ToString(buf) + ); + function initDate() { + function fixup(value, min, max) { + return typeof value != 'number' || isNaN(value) + ? min + : value >= min + ? value <= max + ? value + : max + : min; + } + return { + year: fixup(HEAP32[(tm + 20) >> 2] + 1900, 1970, 9999), + month: fixup(HEAP32[(tm + 16) >> 2], 0, 11), + day: fixup(HEAP32[(tm + 12) >> 2], 1, 31), + hour: fixup(HEAP32[(tm + 8) >> 2], 0, 23), + min: fixup(HEAP32[(tm + 4) >> 2], 0, 59), + sec: fixup(HEAP32[tm >> 2], 0, 59), + gmtoff: 0, + }; + } + if (matches) { + var date = initDate(); + var value; + var getMatch = (symbol) => { + var pos = capture.indexOf(symbol); + if (pos >= 0) { + return matches[pos + 1]; + } + return; + }; + if ((value = getMatch('S'))) { + date.sec = Number(value); + } + if ((value = getMatch('M'))) { + date.min = Number(value); + } + if ((value = getMatch('H'))) { + date.hour = Number(value); + } else if ((value = getMatch('I'))) { + var hour = Number(value); + if ((value = getMatch('p'))) { + hour += value.toUpperCase()[0] === 'P' ? 12 : 0; + } + date.hour = hour; + } + if ((value = getMatch('Y'))) { + date.year = Number(value); + } else if ((value = getMatch('y'))) { + var year = Number(value); + if ((value = getMatch('C'))) { + year += Number(value) * 100; + } else { + year += year < 69 ? 2e3 : 1900; + } + date.year = year; + } + if ((value = getMatch('m'))) { + date.month = Number(value) - 1; + } else if ((value = getMatch('b'))) { + date.month = + MONTH_NUMBERS[value.substring(0, 3).toUpperCase()] || 0; + } + if ((value = getMatch('d'))) { + date.day = Number(value); + } else if ((value = getMatch('j'))) { + var day = Number(value); + var leapYear = isLeapYear(date.year); + for (var month = 0; month < 12; ++month) { + var daysUntilMonth = arraySum( + leapYear ? MONTH_DAYS_LEAP : MONTH_DAYS_REGULAR, + month - 1 + ); + if ( + day <= + daysUntilMonth + + (leapYear ? MONTH_DAYS_LEAP : MONTH_DAYS_REGULAR)[ + month + ] + ) { + date.day = day - daysUntilMonth; + } + } + } else if ((value = getMatch('a'))) { + var weekDay = value.substring(0, 3).toUpperCase(); + if ((value = getMatch('U'))) { + var weekDayNumber = DAY_NUMBERS_SUN_FIRST[weekDay]; + var weekNumber = Number(value); + var janFirst = new Date(date.year, 0, 1); + var endDate; + if (janFirst.getDay() === 0) { + endDate = addDays( + janFirst, + weekDayNumber + 7 * (weekNumber - 1) + ); + } else { + endDate = addDays( + janFirst, + 7 - + janFirst.getDay() + + weekDayNumber + + 7 * (weekNumber - 1) + ); + } + date.day = endDate.getDate(); + date.month = endDate.getMonth(); + } else if ((value = getMatch('W'))) { + var weekDayNumber = DAY_NUMBERS_MON_FIRST[weekDay]; + var weekNumber = Number(value); + var janFirst = new Date(date.year, 0, 1); + var endDate; + if (janFirst.getDay() === 1) { + endDate = addDays( + janFirst, + weekDayNumber + 7 * (weekNumber - 1) + ); + } else { + endDate = addDays( + janFirst, + 7 - + janFirst.getDay() + + 1 + + weekDayNumber + + 7 * (weekNumber - 1) + ); + } + date.day = endDate.getDate(); + date.month = endDate.getMonth(); + } + } + if ((value = getMatch('z'))) { + if (value.toLowerCase() === 'z') { + date.gmtoff = 0; + } else { + var match = value.match(/^((?:\-|\+)\d\d):?(\d\d)?/); + date.gmtoff = match[1] * 3600; + if (match[2]) { + date.gmtoff += + date.gmtoff > 0 ? match[2] * 60 : -match[2] * 60; + } + } + } + var fullDate = new Date( + date.year, + date.month, + date.day, + date.hour, + date.min, + date.sec, + 0 + ); + HEAP32[tm >> 2] = fullDate.getSeconds(); + HEAP32[(tm + 4) >> 2] = fullDate.getMinutes(); + HEAP32[(tm + 8) >> 2] = fullDate.getHours(); + HEAP32[(tm + 12) >> 2] = fullDate.getDate(); + HEAP32[(tm + 16) >> 2] = fullDate.getMonth(); + HEAP32[(tm + 20) >> 2] = fullDate.getFullYear() - 1900; + HEAP32[(tm + 24) >> 2] = fullDate.getDay(); + HEAP32[(tm + 28) >> 2] = + arraySum( + isLeapYear(fullDate.getFullYear()) + ? MONTH_DAYS_LEAP + : MONTH_DAYS_REGULAR, + fullDate.getMonth() - 1 + ) + + fullDate.getDate() - + 1; + HEAP32[(tm + 32) >> 2] = 0; + HEAP32[(tm + 36) >> 2] = date.gmtoff; + return buf + lengthBytesUTF8(matches[0]); + } + return 0; + }; + _strptime.sig = 'pppp'; + function _wasm_close(socketd) { + return PHPWASM.shutdownSocket(socketd, 2); + } + function _wasm_setsockopt( + socketd, + level, + optionName, + optionValuePtr, + optionLen + ) { + const optionValue = HEAPU8[optionValuePtr]; + const SOL_SOCKET = 1; + const SO_KEEPALIVE = 9; + const SO_RCVTIMEO = 66; + const SO_SNDTIMEO = 67; + const IPPROTO_TCP = 6; + const TCP_NODELAY = 1; + const isForwardable = + (level === SOL_SOCKET && optionName === SO_KEEPALIVE) || + (level === IPPROTO_TCP && optionName === TCP_NODELAY); + const isIgnorable = + level === SOL_SOCKET && + (optionName === SO_RCVTIMEO || optionName === SO_SNDTIMEO); + if (!isForwardable && !isIgnorable) { + console.warn( + `Unsupported socket option: ${level}, ${optionName}, ${optionValue}` + ); + return -1; + } + if (isIgnorable) { + return 0; + } + const ws = PHPWASM.getAllWebSockets(socketd)[0]; + if (!ws) { + return -1; + } + ws.setSocketOpt(level, optionName, optionValuePtr); + return 0; + } + var Asyncify = { + instrumentWasmImports(imports) { + var importPattern = + /^(js_open_process|js_fd_read|js_waitpid|js_process_status|js_create_input_device|wasm_setsockopt|wasm_shutdown|wasm_close|wasm_recv|wasm_connect|__syscall_fcntl64|js_flock|js_release_file_locks|js_waitpid|invoke_.*|__asyncjs__.*)$/; + for (let [x, original] of Object.entries(imports)) { + if (typeof original == 'function') { + let isAsyncifyImport = + original.isAsync || importPattern.test(x); + if (isAsyncifyImport) { + imports[x] = original = new WebAssembly.Suspending( + original + ); + } + } + } + }, + instrumentFunction(original) { + var wrapper = (...args) => original(...args); + wrapper.orig = original; + return wrapper; + }, + instrumentWasmExports(exports) { + var exportPattern = + /^(php_wasm_init|wasm_sleep|wasm_read|emscripten_sleep|wasm_sapi_handle_request|wasm_sapi_request_shutdown|wasm_poll_socket|wrap_select|__wrap_select|select|php_pollfd_for|fflush|wasm_popen|wasm_read|wasm_php_exec|run_cli|wasm_recv|wasm_connect|__wasm_call_ctors|__errno_location|__funcs_on_exit|main|__main_argc_argv)$/; + Asyncify.asyncExports = new Set(); + var ret = {}; + for (let [x, original] of Object.entries(exports)) { + if (typeof original == 'function') { + let isAsyncifyExport = exportPattern.test(x); + if (isAsyncifyExport) { + Asyncify.asyncExports.add(original); + original = Asyncify.makeAsyncFunction(original); + } + var wrapper = Asyncify.instrumentFunction(original); + ret[x] = wrapper; + } else { + ret[x] = original; + } + } + return ret; + }, + asyncExports: null, + isAsyncExport(func) { + return Asyncify.asyncExports?.has(func); + }, + handleAsync: async (startAsync) => { + runtimeKeepalivePush(); + try { + return await startAsync(); + } finally { + runtimeKeepalivePop(); + } + }, + handleSleep: (startAsync) => + Asyncify.handleAsync(() => new Promise(startAsync)), + makeAsyncFunction(original) { + return WebAssembly.promising(original); + }, + }; + var getCFunc = (ident) => { + var func = Module['_' + ident]; + return func; + }; + var writeArrayToMemory = (array, buffer) => { + HEAP8.set(array, buffer); + }; + var ccall = (ident, returnType, argTypes, args, opts) => { + var toC = { + string: (str) => { + var ret = 0; + if (str !== null && str !== undefined && str !== 0) { + ret = stringToUTF8OnStack(str); + } + return ret; + }, + array: (arr) => { + var ret = stackAlloc(arr.length); + writeArrayToMemory(arr, ret); + return ret; + }, + }; + function convertReturnValue(ret) { + if (returnType === 'string') { + return UTF8ToString(ret); + } + if (returnType === 'boolean') return Boolean(ret); + return ret; + } + var func = getCFunc(ident); + var cArgs = []; + var stack = 0; + if (args) { + for (var i = 0; i < args.length; i++) { + var converter = toC[argTypes[i]]; + if (converter) { + if (stack === 0) stack = stackSave(); + cArgs[i] = converter(args[i]); + } else { + cArgs[i] = args[i]; + } + } + } + var ret = func(...cArgs); + function onDone(ret) { + if (stack !== 0) stackRestore(stack); + return convertReturnValue(ret); + } + var asyncMode = opts?.async; + if (asyncMode) return ret.then(onDone); + ret = onDone(ret); + return ret; + }; + var FS_createPath = (...args) => FS.createPath(...args); + var FS_unlink = (...args) => FS.unlink(...args); + var FS_createLazyFile = (...args) => FS.createLazyFile(...args); + var FS_createDevice = (...args) => FS.createDevice(...args); + registerWasmPlugin(); + FS.createPreloadedFile = FS_createPreloadedFile; + FS.preloadFile = FS_preloadFile; + FS.staticInit(); + PHPWASM.init(); + { + if (Module['preloadPlugins']) preloadPlugins = Module['preloadPlugins']; + if (Module['noExitRuntime']) noExitRuntime = Module['noExitRuntime']; + if (Module['print']) out = Module['print']; + if (Module['printErr']) err = Module['printErr']; + if (Module['dynamicLibraries']) + dynamicLibraries = Module['dynamicLibraries']; + if (Module['wasmBinary']) wasmBinary = Module['wasmBinary']; + if (Module['arguments']) arguments_ = Module['arguments']; + if (Module['thisProgram']) thisProgram = Module['thisProgram']; + if (Module['quit']) quit_ = Module['quit']; + if (Module['preInit']) { + if (typeof Module['preInit'] == 'function') + Module['preInit'] = [Module['preInit']]; + while (Module['preInit'].length > 0) { + Module['preInit'].shift()(); + } + } + } + Module['wasmExports'] = wasmExports; + Module['addRunDependency'] = addRunDependency; + Module['removeRunDependency'] = removeRunDependency; + Module['ccall'] = ccall; + Module['FS_preloadFile'] = FS_preloadFile; + Module['FS_unlink'] = FS_unlink; + Module['FS_createPath'] = FS_createPath; + Module['FS_createDevice'] = FS_createDevice; + Module['FS_createDataFile'] = FS_createDataFile; + Module['FS_createLazyFile'] = FS_createLazyFile; + Module['PROXYFS'] = PROXYFS; + Module['UTF8ToString'] = UTF8ToString; + Module['lengthBytesUTF8'] = lengthBytesUTF8; + Module['stringToUTF8'] = stringToUTF8; + Module['FS'] = FS; + Module['_exit'] = _exit; + Module['_emscripten_sleep'] = _emscripten_sleep; + var ASM_CONSTS = {}; + function __asyncjs__js_popen_to_file(command, mode, exitCodePtr) { + return Asyncify.handleAsync(async () => { + const returnCallback = (resolver) => new Promise(resolver); + if (!command) return 1; + const cmdstr = UTF8ToString(command); + if (!cmdstr.length) return 0; + const modestr = UTF8ToString(mode); + if (!modestr.length) return 0; + if (modestr === 'w') { + console.error('popen($cmd, "w") is not implemented yet'); + } + return returnCallback(async (wakeUp) => { + let cp; + try { + cp = PHPWASM.spawnProcess(cmdstr, []); + if (cp instanceof Promise) { + cp = await cp; + } + } catch (e) { + console.error(e); + if (e.code === 'SPAWN_UNSUPPORTED') { + return 1; + } + throw e; + } + const outByteArrays = []; + cp.stdout.on('data', function (data) { + outByteArrays.push(data); + }); + const outputPath = '/tmp/popen_output'; + cp.on('exit', function (exitCode) { + const outBytes = new Uint8Array( + outByteArrays.reduce( + (acc, curr) => acc + curr.length, + 0 + ) + ); + let offset = 0; + for (const byteArray of outByteArrays) { + outBytes.set(byteArray, offset); + offset += byteArray.length; + } + FS.writeFile(outputPath, outBytes); + HEAPU8[exitCodePtr] = exitCode; + wakeUp(allocateUTF8OnStack(outputPath)); + }); + }); + }); + } + __asyncjs__js_popen_to_file.sig = 'iiii'; + function __asyncjs__wasm_poll_socket(socketd, events, timeout) { + return Asyncify.handleAsync(async () => { + const returnCallback = (resolver) => new Promise(resolver); + const POLLIN = 1; + const POLLPRI = 2; + const POLLOUT = 4; + const POLLERR = 8; + const POLLHUP = 16; + const POLLNVAL = 32; + return returnCallback((wakeUp) => { + const polls = []; + const stream = FS.getStream(socketd); + if (FS.isSocket(stream?.node.mode)) { + const sock = getSocketFromFD(socketd); + if (!sock) { + wakeUp(0); + return; + } + const lookingFor = new Set(); + if (events & POLLIN || events & POLLPRI) { + if (sock.server) { + for (const client of sock.pending) { + if ((client.recv_queue || []).length > 0) { + wakeUp(1); + return; + } + } + } else if ((sock.recv_queue || []).length > 0) { + wakeUp(1); + return; + } + } + const webSockets = PHPWASM.getAllWebSockets(sock); + if (!webSockets.length) { + wakeUp(0); + return; + } + for (const ws of webSockets) { + if (events & POLLIN || events & POLLPRI) { + polls.push(PHPWASM.awaitData(ws)); + lookingFor.add('POLLIN'); + } + if (events & POLLOUT) { + polls.push(PHPWASM.awaitConnection(ws)); + lookingFor.add('POLLOUT'); + } + if ( + events & POLLHUP || + events & POLLIN || + events & POLLOUT || + events & POLLERR + ) { + polls.push(PHPWASM.awaitClose(ws)); + lookingFor.add('POLLHUP'); + } + if (events & POLLERR || events & POLLNVAL) { + polls.push(PHPWASM.awaitError(ws)); + lookingFor.add('POLLERR'); + } + } + } else if (stream?.stream_ops?.poll) { + let interrupted = false; + async function poll() { + try { + while (true) { + var mask = POLLNVAL; + mask = SYSCALLS.DEFAULT_POLLMASK; + if (FS.isClosed(stream)) { + return ERRNO_CODES.EBADF; + } + if (stream.stream_ops?.poll) { + mask = stream.stream_ops.poll(stream, -1); + } + mask &= events | POLLERR | POLLHUP; + if (mask) { + return mask; + } + if (interrupted) { + return ERRNO_CODES.ETIMEDOUT; + } + await new Promise((resolve) => + setTimeout(resolve, 10) + ); + } + } catch (e) { + if ( + typeof FS == 'undefined' || + !(e.name === 'ErrnoError') + ) + throw e; + return -e.errno; + } + } + polls.push([ + poll(), + () => { + interrupted = true; + }, + ]); + } else { + setTimeout(function () { + wakeUp(1); + }, timeout); + return; + } + if (polls.length === 0) { + console.warn( + 'Unsupported poll event ' + + events + + ', defaulting to setTimeout().' + ); + setTimeout(function () { + wakeUp(0); + }, timeout); + return; + } + const promises = polls.map(([promise]) => promise); + const clearPolling = () => + polls.forEach(([, clear]) => clear()); + let awaken = false; + let timeoutId; + Promise.race(promises).then(function (results) { + if (!awaken) { + awaken = true; + wakeUp(1); + if (timeoutId) { + clearTimeout(timeoutId); + } + clearPolling(); + } + }); + if (timeout !== -1) { + timeoutId = setTimeout(function () { + if (!awaken) { + awaken = true; + wakeUp(0); + clearPolling(); + } + }, timeout); + } + }); + }); + } + __asyncjs__wasm_poll_socket.sig = 'iiii'; + function js_fd_read(fd, iov, iovcnt, pnum) { + const returnCallback = (resolver) => new Promise(resolver); + const pollAsync = arguments[4] === undefined ? true : !!arguments[4]; + if ( + Asyncify?.State?.Normal === undefined || + Asyncify?.state === Asyncify?.State?.Normal + ) { + var stream; + try { + stream = SYSCALLS.getStreamFromFD(fd); + HEAPU32[pnum >> 2] = doReadv(stream, iov, iovcnt); + return 0; + } catch (e) { + if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) { + throw e; + } + if ( + e.errno !== ERRNO_CODES.EWOULDBLOCK && + e.errno !== ERRNO_CODES.EAGAIN + ) { + return e.errno; + } + const nonBlocking = stream.flags & PHPWASM.O_NONBLOCK; + if (nonBlocking) { + return e.errno; + } + } + } + if (false === pollAsync) { + return ERRNO_CODES.EWOULDBLOCK; + } + return returnCallback(async (wakeUp) => { + var retries = 0; + var interval = 50; + var timeout = 5e3; + var maxRetries = timeout / interval; + while (true) { + var returnCode; + var stream; + let num; + try { + stream = SYSCALLS.getStreamFromFD(fd); + num = doReadv(stream, iov, iovcnt); + returnCode = 0; + } catch (e) { + if ( + typeof FS == 'undefined' || + !(e.name === 'ErrnoError') + ) { + console.error(e); + throw e; + } + returnCode = e.errno; + } + if (returnCode === 0) { + HEAPU32[pnum >> 2] = num; + return wakeUp(0); + } + if ( + ++retries > maxRetries || + !stream || + FS.isClosed(stream) || + returnCode !== ERRNO_CODES.EWOULDBLOCK || + ('pipe' in stream.node && stream.node.pipe.refcnt < 2) + ) { + HEAPU32[pnum >> 2] = num; + return wakeUp(returnCode); + } + await new Promise((resolve) => setTimeout(resolve, interval)); + } + }); + } + js_fd_read.sig = 'iiiii'; + function __asyncjs__js_module_onMessage(data, response_buffer) { + return Asyncify.handleAsync(async () => { + if (Module['onMessage']) { + const dataStr = UTF8ToString(data); + return Module['onMessage'](dataStr) + .then((response) => { + const responseBytes = + typeof response === 'string' + ? new TextEncoder().encode(response) + : response; + const responseSize = responseBytes.byteLength; + const responsePtr = _malloc(responseSize + 1); + HEAPU8.set(responseBytes, responsePtr); + HEAPU8[responsePtr + responseSize] = 0; + HEAPU8[response_buffer] = responsePtr; + HEAPU8[response_buffer + 1] = responsePtr >> 8; + HEAPU8[response_buffer + 2] = responsePtr >> 16; + HEAPU8[response_buffer + 3] = responsePtr >> 24; + return responseSize; + }) + .catch((e) => { + console.error(e); + return -1; + }); + } + }); + } + __asyncjs__js_module_onMessage.sig = 'iii'; + var _malloc, + ___errno_location, + _calloc, + _wasm_sleep, + _ntohs, + _htons, + _htonl, + _wasm_read, + _fflush, + _wasm_popen, + _wasm_php_exec, + _php_pollfd_for, + ___wrap_usleep, + ___wrap_select, + _wasm_set_sapi_name, + _wasm_set_phpini_path, + _wasm_add_cli_arg, + _run_cli, + _wasm_add_SERVER_entry, + _wasm_add_ENV_entry, + _wasm_set_query_string, + _wasm_set_path_translated, + _wasm_set_skip_shebang, + _wasm_set_request_uri, + _wasm_set_request_method, + _wasm_set_request_host, + _wasm_set_content_type, + _wasm_set_request_body, + _wasm_set_content_length, + _wasm_set_cookies, + _wasm_set_request_port, + _wasm_sapi_request_shutdown, + _wasm_sapi_handle_request, + _php_wasm_init, + _wasm_free, + _wasm_trace, + _initgroups, + ___funcs_on_exit, + ___dl_seterr, + __emscripten_find_dylib, + _emscripten_builtin_memalign, + __emscripten_timeout, + _emscripten_get_sbrk_ptr, + ___trap, + __emscripten_stack_restore, + __emscripten_stack_alloc, + _emscripten_stack_get_current, + __ZNSt3__211__call_onceERVmPvPFvS2_E, + __ZNSt3__218condition_variable10notify_allEv, + __ZNSt3__25mutex4lockEv, + __ZNSt3__25mutex6unlockEv, + memory, + __indirect_function_table, + wasmTable, + wasmMemory; + function assignWasmExports(wasmExports) { + _malloc = + PHPLoader['malloc'] = + Module['_malloc'] = + wasmExports['malloc']; + ___errno_location = Module['___errno_location'] = + wasmExports['__errno_location']; + _calloc = wasmExports['calloc']; + _wasm_sleep = Module['_wasm_sleep'] = wasmExports['wasm_sleep']; + _ntohs = wasmExports['ntohs']; + _htons = wasmExports['htons']; + _htonl = wasmExports['htonl']; + _wasm_read = Module['_wasm_read'] = wasmExports['wasm_read']; + _fflush = wasmExports['fflush']; + _wasm_popen = Module['_wasm_popen'] = wasmExports['wasm_popen']; + _wasm_php_exec = Module['_wasm_php_exec'] = + wasmExports['wasm_php_exec']; + _php_pollfd_for = Module['_php_pollfd_for'] = + wasmExports['php_pollfd_for']; + ___wrap_usleep = Module['___wrap_usleep'] = + wasmExports['__wrap_usleep']; + ___wrap_select = Module['___wrap_select'] = + wasmExports['__wrap_select']; + _wasm_set_sapi_name = Module['_wasm_set_sapi_name'] = + wasmExports['wasm_set_sapi_name']; + _wasm_set_phpini_path = Module['_wasm_set_phpini_path'] = + wasmExports['wasm_set_phpini_path']; + _wasm_add_cli_arg = Module['_wasm_add_cli_arg'] = + wasmExports['wasm_add_cli_arg']; + _run_cli = Module['_run_cli'] = wasmExports['run_cli']; + _wasm_add_SERVER_entry = Module['_wasm_add_SERVER_entry'] = + wasmExports['wasm_add_SERVER_entry']; + _wasm_add_ENV_entry = Module['_wasm_add_ENV_entry'] = + wasmExports['wasm_add_ENV_entry']; + _wasm_set_query_string = Module['_wasm_set_query_string'] = + wasmExports['wasm_set_query_string']; + _wasm_set_path_translated = Module['_wasm_set_path_translated'] = + wasmExports['wasm_set_path_translated']; + _wasm_set_skip_shebang = Module['_wasm_set_skip_shebang'] = + wasmExports['wasm_set_skip_shebang']; + _wasm_set_request_uri = Module['_wasm_set_request_uri'] = + wasmExports['wasm_set_request_uri']; + _wasm_set_request_method = Module['_wasm_set_request_method'] = + wasmExports['wasm_set_request_method']; + _wasm_set_request_host = Module['_wasm_set_request_host'] = + wasmExports['wasm_set_request_host']; + _wasm_set_content_type = Module['_wasm_set_content_type'] = + wasmExports['wasm_set_content_type']; + _wasm_set_request_body = Module['_wasm_set_request_body'] = + wasmExports['wasm_set_request_body']; + _wasm_set_content_length = Module['_wasm_set_content_length'] = + wasmExports['wasm_set_content_length']; + _wasm_set_cookies = Module['_wasm_set_cookies'] = + wasmExports['wasm_set_cookies']; + _wasm_set_request_port = Module['_wasm_set_request_port'] = + wasmExports['wasm_set_request_port']; + _wasm_sapi_request_shutdown = Module['_wasm_sapi_request_shutdown'] = + wasmExports['wasm_sapi_request_shutdown']; + _wasm_sapi_handle_request = Module['_wasm_sapi_handle_request'] = + wasmExports['wasm_sapi_handle_request']; + _php_wasm_init = Module['_php_wasm_init'] = + wasmExports['php_wasm_init']; + _wasm_free = + PHPLoader['free'] = + Module['_wasm_free'] = + wasmExports['wasm_free']; + _wasm_trace = Module['_wasm_trace'] = wasmExports['wasm_trace']; + _initgroups = Module['_initgroups'] = wasmExports['initgroups']; + ___funcs_on_exit = wasmExports['__funcs_on_exit']; + ___dl_seterr = wasmExports['__dl_seterr']; + __emscripten_find_dylib = wasmExports['_emscripten_find_dylib']; + _emscripten_builtin_memalign = + wasmExports['emscripten_builtin_memalign']; + __emscripten_timeout = wasmExports['_emscripten_timeout']; + _emscripten_get_sbrk_ptr = wasmExports['emscripten_get_sbrk_ptr']; + ___trap = wasmExports['__trap']; + __emscripten_stack_restore = wasmExports['_emscripten_stack_restore']; + __emscripten_stack_alloc = wasmExports['_emscripten_stack_alloc']; + _emscripten_stack_get_current = + wasmExports['emscripten_stack_get_current']; + __ZNSt3__211__call_onceERVmPvPFvS2_E = Module[ + '__ZNSt3__211__call_onceERVmPvPFvS2_E' + ] = wasmExports['_ZNSt3__211__call_onceERVmPvPFvS2_E']; + __ZNSt3__218condition_variable10notify_allEv = Module[ + '__ZNSt3__218condition_variable10notify_allEv' + ] = wasmExports['_ZNSt3__218condition_variable10notify_allEv']; + __ZNSt3__25mutex4lockEv = Module['__ZNSt3__25mutex4lockEv'] = + wasmExports['_ZNSt3__25mutex4lockEv']; + __ZNSt3__25mutex6unlockEv = Module['__ZNSt3__25mutex6unlockEv'] = + wasmExports['_ZNSt3__25mutex6unlockEv']; + memory = wasmMemory = wasmExports['memory']; + __indirect_function_table = wasmTable = + wasmExports['__indirect_function_table']; + } + var ___heap_base = 8286992; + var wasmImports = { + __assert_fail: ___assert_fail, + __asyncjs__js_module_onMessage, + __asyncjs__js_popen_to_file, + __asyncjs__wasm_poll_socket, + __call_sighandler: ___call_sighandler, + __syscall_accept4: ___syscall_accept4, + __syscall_bind: ___syscall_bind, + __syscall_chdir: ___syscall_chdir, + __syscall_chmod: ___syscall_chmod, + __syscall_connect: ___syscall_connect, + __syscall_dup: ___syscall_dup, + __syscall_dup3: ___syscall_dup3, + __syscall_faccessat: ___syscall_faccessat, + __syscall_fchmod: ___syscall_fchmod, + __syscall_fchown32: ___syscall_fchown32, + __syscall_fchownat: ___syscall_fchownat, + __syscall_fcntl64: ___syscall_fcntl64, + __syscall_fstat64: ___syscall_fstat64, + __syscall_ftruncate64: ___syscall_ftruncate64, + __syscall_getcwd: ___syscall_getcwd, + __syscall_getdents64: ___syscall_getdents64, + __syscall_getpeername: ___syscall_getpeername, + __syscall_getsockname: ___syscall_getsockname, + __syscall_getsockopt: ___syscall_getsockopt, + __syscall_ioctl: ___syscall_ioctl, + __syscall_listen: ___syscall_listen, + __syscall_lstat64: ___syscall_lstat64, + __syscall_mkdirat: ___syscall_mkdirat, + __syscall_newfstatat: ___syscall_newfstatat, + __syscall_openat: ___syscall_openat, + __syscall_pipe: ___syscall_pipe, + __syscall_poll: ___syscall_poll, + __syscall_readlinkat: ___syscall_readlinkat, + __syscall_recvfrom: ___syscall_recvfrom, + __syscall_renameat: ___syscall_renameat, + __syscall_rmdir: ___syscall_rmdir, + __syscall_sendto: ___syscall_sendto, + __syscall_socket: ___syscall_socket, + __syscall_stat64: ___syscall_stat64, + __syscall_statfs64: ___syscall_statfs64, + __syscall_symlinkat: ___syscall_symlinkat, + __syscall_unlinkat: ___syscall_unlinkat, + __syscall_utimensat: ___syscall_utimensat, + _abort_js: __abort_js, + _dlopen_js: __dlopen_js, + _dlsym_js: __dlsym_js, + _emscripten_lookup_name: __emscripten_lookup_name, + _emscripten_runtime_keepalive_clear: + __emscripten_runtime_keepalive_clear, + _gmtime_js: __gmtime_js, + _localtime_js: __localtime_js, + _mktime_js: __mktime_js, + _mmap_js: __mmap_js, + _munmap_js: __munmap_js, + _setitimer_js: __setitimer_js, + _tzset_js: __tzset_js, + clock_time_get: _clock_time_get, + emscripten_date_now: _emscripten_date_now, + emscripten_get_heap_max: _emscripten_get_heap_max, + emscripten_get_now: _emscripten_get_now, + emscripten_resize_heap: _emscripten_resize_heap, + emscripten_sleep: _emscripten_sleep, + environ_get: _environ_get, + environ_sizes_get: _environ_sizes_get, + exit: _exit, + fd_close: _fd_close, + fd_fdstat_get: _fd_fdstat_get, + fd_read: _fd_read, + fd_seek: _fd_seek, + fd_sync: _fd_sync, + fd_write: _fd_write, + getaddrinfo: _getaddrinfo, + getnameinfo: _getnameinfo, + getprotobyname: _getprotobyname, + getprotobynumber: _getprotobynumber, + js_fd_read, + js_open_process: _js_open_process, + js_wasm_trace: _js_wasm_trace, + proc_exit: _proc_exit, + strptime: _strptime, + wasm_close: _wasm_close, + wasm_setsockopt: _wasm_setsockopt, + }; + async function callMain(args = []) { + var entryFunction = resolveGlobalSymbol('main').sym; + if (!entryFunction) return; + args.unshift(thisProgram); + var argc = args.length; + var argv = stackAlloc((argc + 1) * 4); + var argv_ptr = argv; + for (var arg of args) { + HEAPU32[argv_ptr >> 2] = stringToUTF8OnStack(arg); + argv_ptr += 4; + } + HEAPU32[argv_ptr >> 2] = 0; + try { + var ret = entryFunction(argc, argv); + ret = await ret; + exitJS(ret, true); + return ret; + } catch (e) { + return handleException(e); + } + } + function run(args = arguments_) { + if (runDependencies > 0) { + dependenciesFulfilled = run; + return; + } + preRun(); + if (runDependencies > 0) { + dependenciesFulfilled = run; + return; + } + async function doRun() { + Module['calledRun'] = true; + if (ABORT) return; + initRuntime(); + preMain(); + Module['onRuntimeInitialized']?.(); + var noInitialRun = Module['noInitialRun'] || true; + if (!noInitialRun) await callMain(args); + postRun(); + } + if (Module['setStatus']) { + Module['setStatus']('Running...'); + setTimeout(() => { + setTimeout(() => Module['setStatus'](''), 1); + doRun(); + }, 1); + } else { + doRun(); + } + } + var wasmExports; + createWasm(); + run(); + /** + * Emscripten resolves `localhost` to a random IP address. Let's + * make it always resolve to 127.0.0.1. + */ + DNS.address_map.addrs.localhost = '127.0.0.1'; + + /** + * Debugging Asyncify errors is tricky because the stack trace is lost when the + * error is thrown. This code saves the stack trace in a global variable + * so that it can be inspected later. + */ + PHPLoader.debug = 'debug' in PHPLoader ? PHPLoader.debug : true; + if (PHPLoader.debug && typeof Asyncify !== 'undefined') { + const originalHandleSleep = Asyncify.handleSleep; + Asyncify.handleSleep = function (startAsync) { + if (!ABORT) { + Module['lastAsyncifyStackSource'] = new Error(); + } + return originalHandleSleep(startAsync); + }; + } + + /** + * Data dependencies call removeRunDependency() when they are loaded. + * The synchronous call stack then continues to run. If an error occurs + * in PHP initialization, e.g. Out Of Memory error, it will not be + * caught by any try/catch. This override propagates the failure to + * PHPLoader.onAbort() so that it can be handled. + */ + const originalRemoveRunDependency = PHPLoader['removeRunDependency']; + PHPLoader['removeRunDependency'] = function (...args) { + try { + originalRemoveRunDependency(...args); + } catch (e) { + PHPLoader['onAbort'](e); + } + }; + + if (typeof NODEFS === 'object') { + // We override NODEFS.createNode() to add an `isSharedFS` flag to all NODEFS + // nodes. This way we can tell whether file-locking is needed and possible + // for an FS node, even if wrapped with PROXYFS. + const originalNodeFsCreateNode = NODEFS.createNode; + NODEFS.createNode = function createNodeWithSharedFlag() { + const node = originalNodeFsCreateNode.apply(NODEFS, arguments); + node.isSharedFS = true; + return node; + }; + + var originalHashAddNode = FS.hashAddNode; + FS.hashAddNode = function hashAddNodeIfNotSharedFS(node) { + if (node?.isSharedFS) { + // Avoid caching shared VFS nodes so multiple instances + // can access the same underlying filesystem without + // conflicting caches. + return; + } + return originalHashAddNode.apply(FS, arguments); + }; + } + + /** + * Expose the PHP version so the PHP class can make version-specific + * adjustments to `php.ini`. + */ + PHPLoader['phpVersion'] = (() => { + const [major, minor, patch] = phpVersionString.split('.').map(Number); + return { major, minor, patch }; + })(); + + return PHPLoader; + + // Close the opening bracket from esm-prefix.js: +} diff --git a/packages/php-wasm/web-builds/5-6/package.json b/packages/php-wasm/web-builds/5-6/package.json new file mode 100644 index 00000000000..154cc7cb99e --- /dev/null +++ b/packages/php-wasm/web-builds/5-6/package.json @@ -0,0 +1,38 @@ +{ + "name": "@php-wasm/web-5-6", + "version": "3.1.15", + "description": "PHP 5.6 WebAssembly binaries for web (legacy)", + "repository": { + "type": "git", + "url": "https://github.com/WordPress/wordpress-playground" + }, + "homepage": "https://developer.wordpress.org/playground", + "author": "The WordPress contributors", + "contributors": [ + { + "name": "Adam Zielinski", + "email": "adam@adamziel.com", + "url": "https://github.com/adamziel" + } + ], + "exports": { + ".": { + "import": "./index.js", + "require": "./index.cjs" + }, + "./package.json": "./package.json" + }, + "publishConfig": { + "access": "public", + "directory": "../../../../dist/packages/php-wasm/web-builds/5-6" + }, + "type": "module", + "main": "./index.cjs", + "module": "./index.js", + "types": "index.d.ts", + "license": "GPL-2.0-or-later", + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } +} diff --git a/packages/php-wasm/web-builds/5-6/project.json b/packages/php-wasm/web-builds/5-6/project.json new file mode 100644 index 00000000000..d4217b023af --- /dev/null +++ b/packages/php-wasm/web-builds/5-6/project.json @@ -0,0 +1,61 @@ +{ + "name": "php-wasm-web-5-6", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/php-wasm/web-builds/5-6/src", + "projectType": "library", + "implicitDependencies": ["php-wasm-compile"], + "targets": { + "build": { + "executor": "nx:noop", + "dependsOn": ["build:copy-assets"] + }, + "build:mkdir": { + "executor": "nx:run-commands", + "options": { + "commands": ["mkdir -p dist/packages/php-wasm/web-builds/5-6"], + "parallel": false + } + }, + "build:package-json": { + "executor": "@wp-playground/nx-extensions:package-json", + "options": { + "tsConfig": "packages/php-wasm/web-builds/5-6/tsconfig.lib.json", + "outputPath": "dist/packages/php-wasm/web-builds/5-6", + "buildTarget": "php-wasm-web-5-6:build:bundle:production" + }, + "dependsOn": ["build:mkdir"] + }, + "build:bundle": { + "executor": "nx:run-commands", + "options": { + "command": "node packages/php-wasm/web-builds/5-6/build.js", + "parallel": false + }, + "dependsOn": ["build:mkdir"] + }, + "build:copy-assets": { + "executor": "nx:run-commands", + "options": { + "commands": [ + "cp -rf packages/php-wasm/web-builds/5-6/jspi dist/packages/php-wasm/web-builds/5-6/", + "cp -rf packages/php-wasm/web-builds/5-6/asyncify dist/packages/php-wasm/web-builds/5-6/" + ], + "parallel": false + }, + "dependsOn": ["build:package-json"] + }, + "publish": { + "executor": "nx:run-commands", + "options": { + "command": "node tools/scripts/publish.mjs php-wasm-web-5-6 {args.ver} {args.tag}", + "parallel": false + }, + "dependsOn": ["build"] + }, + "package-for-self-hosting": { + "executor": "@wp-playground/nx-extensions:package-for-self-hosting", + "dependsOn": ["build"] + } + }, + "tags": ["scope:php-binaries", "scope:browser-only"] +} diff --git a/packages/php-wasm/web-builds/5-6/src/index.ts b/packages/php-wasm/web-builds/5-6/src/index.ts new file mode 100644 index 00000000000..6786b7ff084 --- /dev/null +++ b/packages/php-wasm/web-builds/5-6/src/index.ts @@ -0,0 +1,14 @@ +import type { PHPLoaderModule } from '@php-wasm/universal'; +import { jspi } from 'wasm-feature-detect'; + +export async function getPHPLoaderModule(): Promise { + if (await jspi()) { + // @ts-ignore + return await import('../jspi/php_5_6.js'); + } else { + // @ts-ignore + return await import('../asyncify/php_5_6.js'); + } +} + +export { jspi }; diff --git a/packages/php-wasm/web-builds/5-6/tsconfig.json b/packages/php-wasm/web-builds/5-6/tsconfig.json new file mode 100644 index 00000000000..b943cbfe62a --- /dev/null +++ b/packages/php-wasm/web-builds/5-6/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "ESNext", + "moduleResolution": "bundler", + "allowJs": true, + "checkJs": false + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/php-wasm/web-builds/5-6/tsconfig.lib.json b/packages/php-wasm/web-builds/5-6/tsconfig.lib.json new file mode 100644 index 00000000000..67bfeb7b15f --- /dev/null +++ b/packages/php-wasm/web-builds/5-6/tsconfig.lib.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../../dist/packages/php-wasm/web-builds/5-6", + "declaration": true, + "types": ["node"] + }, + "include": ["src/**/*.ts"], + "exclude": ["**/*.spec.ts", "**/*.test.ts"] +} diff --git a/packages/php-wasm/web/src/lib/extensions/intl/get-intl-extension-module.ts b/packages/php-wasm/web/src/lib/extensions/intl/get-intl-extension-module.ts index 0a4a9c001aa..d26e627649f 100644 --- a/packages/php-wasm/web/src/lib/extensions/intl/get-intl-extension-module.ts +++ b/packages/php-wasm/web/src/lib/extensions/intl/get-intl-extension-module.ts @@ -1,5 +1,5 @@ import { LatestSupportedPHPVersion } from '@php-wasm/universal'; -import type { SupportedPHPVersion } from '@php-wasm/universal'; +import type { AllPHPVersion } from '@php-wasm/universal'; /** * Returns the path to the intl extension for the specified PHP version. @@ -11,7 +11,7 @@ import type { SupportedPHPVersion } from '@php-wasm/universal'; * - etc. */ export async function getIntlExtensionModule( - version: SupportedPHPVersion = LatestSupportedPHPVersion + version: AllPHPVersion = LatestSupportedPHPVersion ): Promise { switch (version) { case '8.5': diff --git a/packages/php-wasm/web/src/lib/extensions/intl/with-intl.ts b/packages/php-wasm/web/src/lib/extensions/intl/with-intl.ts index 6383a3a0fcb..2e10674f35d 100644 --- a/packages/php-wasm/web/src/lib/extensions/intl/with-intl.ts +++ b/packages/php-wasm/web/src/lib/extensions/intl/with-intl.ts @@ -1,14 +1,14 @@ import type { EmscriptenOptions, PHPRuntime, - SupportedPHPVersion, + AllPHPVersion, } from '@php-wasm/universal'; import { LatestSupportedPHPVersion, FSHelpers } from '@php-wasm/universal'; import { getIntlExtensionModule } from './get-intl-extension-module'; import { createMemoizedFetch } from '@wp-playground/common'; export async function withIntl( - version: SupportedPHPVersion = LatestSupportedPHPVersion, + version: AllPHPVersion = LatestSupportedPHPVersion, options: EmscriptenOptions ): Promise { const memoizedFetch = createMemoizedFetch(fetch); diff --git a/packages/php-wasm/web/src/lib/get-php-loader-module.ts b/packages/php-wasm/web/src/lib/get-php-loader-module.ts index 6c32e81ec7c..2011a1b4666 100644 --- a/packages/php-wasm/web/src/lib/get-php-loader-module.ts +++ b/packages/php-wasm/web/src/lib/get-php-loader-module.ts @@ -1,4 +1,4 @@ -import type { PHPLoaderModule, SupportedPHPVersion } from '@php-wasm/universal'; +import type { AllPHPVersion, PHPLoaderModule } from '@php-wasm/universal'; import { LatestSupportedPHPVersion } from '@php-wasm/universal'; /** @@ -14,7 +14,7 @@ import { LatestSupportedPHPVersion } from '@php-wasm/universal'; * @returns The PHP loader module. */ export async function getPHPLoaderModule( - version: SupportedPHPVersion = LatestSupportedPHPVersion + version: AllPHPVersion = LatestSupportedPHPVersion ): Promise { switch (version) { case '8.5': @@ -38,6 +38,9 @@ export async function getPHPLoaderModule( case '7.4': // @ts-ignore return (await import('@php-wasm/web-7-4')).getPHPLoaderModule(); + case '5.6': + // @ts-ignore + return (await import('@php-wasm/web-5-6')).getPHPLoaderModule(); } throw new Error(`Unsupported PHP version ${version}`); } diff --git a/packages/php-wasm/web/src/lib/load-runtime.ts b/packages/php-wasm/web/src/lib/load-runtime.ts index 4886e8c0bee..9a653469ff3 100644 --- a/packages/php-wasm/web/src/lib/load-runtime.ts +++ b/packages/php-wasm/web/src/lib/load-runtime.ts @@ -1,9 +1,10 @@ import type { - SupportedPHPVersion, + AllPHPVersion, EmscriptenOptions, PHPLoaderModule, + SupportedPHPVersion, } from '@php-wasm/universal'; -import { loadPHPRuntime } from '@php-wasm/universal'; +import { LegacyPHPVersions, loadPHPRuntime } from '@php-wasm/universal'; import { getPHPLoaderModule } from './get-php-loader-module'; import type { TCPOverFetchOptions } from './tcp-over-fetch-websocket'; import { tcpOverFetchWebsocket } from './tcp-over-fetch-websocket'; @@ -47,7 +48,7 @@ interface PHPWorkerGlobalScope extends WorkerGlobalScope { } export async function loadWebRuntime( - phpVersion: SupportedPHPVersion, + phpVersion: AllPHPVersion, loaderOptions: LoaderOptions = {} ) { /* @@ -75,8 +76,65 @@ export async function loadWebRuntime( ); } + const isLegacy = (LegacyPHPVersions as readonly string[]).includes( + phpVersion + ); + + // For legacy PHP: pre-create php.ini with disable_functions BEFORE + // the PHP SAPI starts. ini_get_all() crashes PHP 5.6 WASM (null + // function pointer in asyncify instrumentation), and OPcache's + // shared memory allocation fails. preRun runs before initRuntime(). + if (isLegacy) { + const resolvedOptions = await emscriptenOptions; + // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type + const existingPreRun: Function[] = resolvedOptions['preRun'] || []; + emscriptenOptions = { + ...resolvedOptions, + ['preRun']: [ + ...existingPreRun, + (module: any) => { + module.FS.mkdirTree('/internal/shared'); + module.FS.writeFile( + '/internal/shared/php.ini', + [ + 'auto_prepend_file=/internal/shared/auto_prepend_file.php', + 'memory_limit=256M', + 'ignore_repeated_errors = 1', + 'error_reporting = E_ALL', + 'display_errors = 1', + 'html_errors = 1', + 'display_startup_errors = On', + 'log_errors = 1', + 'always_populate_raw_post_data = -1', + 'upload_max_filesize = 2000M', + 'post_max_size = 2000M', + 'allow_url_fopen = On', + 'allow_url_include = Off', + 'session.save_path = /home/web_user', + 'implicit_flush = 1', + 'output_buffering = 0', + 'max_execution_time = 0', + 'max_input_time = -1', + 'disable_functions = ini_get_all', + 'opcache.enable = 0', + 'opcache.enable_cli = 0', + ].join('\n') + ); + }, + ], + }; + } + if (loaderOptions.withIntl) { - emscriptenOptions = withIntl(phpVersion, emscriptenOptions); + if (isLegacy) { + throw new Error( + `The intl extension is not available for legacy PHP ${phpVersion}.` + ); + } + emscriptenOptions = withIntl( + phpVersion as SupportedPHPVersion, + emscriptenOptions + ); } const [phpLoaderModule, options] = await Promise.all([ diff --git a/packages/playground/blueprints/public/blueprint-schema-validator.js b/packages/playground/blueprints/public/blueprint-schema-validator.js index 3b1856aa350..f1d9e0d1d6f 100644 --- a/packages/playground/blueprints/public/blueprint-schema-validator.js +++ b/packages/playground/blueprints/public/blueprint-schema-validator.js @@ -160,17 +160,24 @@ const schema11 = { }, BlueprintPHPVersion: { anyOf: [ - { $ref: '#/definitions/SupportedPHPVersion' }, + { $ref: '#/definitions/AllPHPVersion' }, { type: 'string', const: '7.2' }, { type: 'string', const: '7.3' }, ], description: 'PHP versions accepted in Blueprint schema. Includes deprecated versions (7.2, 7.3) which are automatically upgraded to 7.4 during compilation.', }, + AllPHPVersion: { + anyOf: [ + { $ref: '#/definitions/SupportedPHPVersion' }, + { $ref: '#/definitions/LegacyPHPVersion' }, + ], + }, SupportedPHPVersion: { type: 'string', enum: ['8.5', '8.4', '8.3', '8.2', '8.1', '8.0', '7.4'], }, + LegacyPHPVersion: { type: 'string', enum: ['5.6'] }, ExtraLibrary: { type: 'string', const: 'wp-cli' }, PHPConstants: { type: 'object', @@ -1561,15 +1568,15 @@ const schema12 = { description: 'The Blueprint declaration, typically stored in a blueprint.json file.', }; -const schema15 = { type: 'string', const: 'wp-cli' }; -const schema16 = { +const schema17 = { type: 'string', const: 'wp-cli' }; +const schema18 = { type: 'object', additionalProperties: { type: ['string', 'boolean', 'number'] }, }; const func2 = Object.prototype.hasOwnProperty; const schema13 = { anyOf: [ - { $ref: '#/definitions/SupportedPHPVersion' }, + { $ref: '#/definitions/AllPHPVersion' }, { type: 'string', const: '7.2' }, { type: 'string', const: '7.3' }, ], @@ -1577,10 +1584,17 @@ const schema13 = { 'PHP versions accepted in Blueprint schema. Includes deprecated versions (7.2, 7.3) which are automatically upgraded to 7.4 during compilation.', }; const schema14 = { + anyOf: [ + { $ref: '#/definitions/SupportedPHPVersion' }, + { $ref: '#/definitions/LegacyPHPVersion' }, + ], +}; +const schema15 = { type: 'string', enum: ['8.5', '8.4', '8.3', '8.2', '8.1', '8.0', '7.4'], }; -function validate12( +const schema16 = { type: 'string', enum: ['5.6'] }; +function validate13( data, { instancePath = '', parentData, parentDataProperty, rootData = data } = {} ) { @@ -1619,7 +1633,7 @@ function validate12( instancePath, schemaPath: '#/definitions/SupportedPHPVersion/enum', keyword: 'enum', - params: { allowedValues: schema14.enum }, + params: { allowedValues: schema15.enum }, message: 'must be equal to one of the allowed values', }; if (vErrors === null) { @@ -1636,7 +1650,7 @@ function validate12( if (typeof data !== 'string') { const err2 = { instancePath, - schemaPath: '#/anyOf/1/type', + schemaPath: '#/definitions/LegacyPHPVersion/type', keyword: 'type', params: { type: 'string' }, message: 'must be string', @@ -1648,8 +1662,97 @@ function validate12( } errors++; } - if ('7.2' !== data) { + if (!(data === '5.6')) { const err3 = { + instancePath, + schemaPath: '#/definitions/LegacyPHPVersion/enum', + keyword: 'enum', + params: { allowedValues: schema16.enum }, + message: 'must be equal to one of the allowed values', + }; + if (vErrors === null) { + vErrors = [err3]; + } else { + vErrors.push(err3); + } + errors++; + } + var _valid0 = _errs4 === errors; + valid0 = valid0 || _valid0; + } + if (!valid0) { + const err4 = { + instancePath, + schemaPath: '#/anyOf', + keyword: 'anyOf', + params: {}, + message: 'must match a schema in anyOf', + }; + if (vErrors === null) { + vErrors = [err4]; + } else { + vErrors.push(err4); + } + errors++; + validate13.errors = vErrors; + return false; + } else { + errors = _errs0; + if (vErrors !== null) { + if (_errs0) { + vErrors.length = _errs0; + } else { + vErrors = null; + } + } + } + validate13.errors = vErrors; + return errors === 0; +} +function validate12( + data, + { instancePath = '', parentData, parentDataProperty, rootData = data } = {} +) { + let vErrors = null; + let errors = 0; + const _errs0 = errors; + let valid0 = false; + const _errs1 = errors; + if ( + !validate13(data, { + instancePath, + parentData, + parentDataProperty, + rootData, + }) + ) { + vErrors = + vErrors === null + ? validate13.errors + : vErrors.concat(validate13.errors); + errors = vErrors.length; + } + var _valid0 = _errs1 === errors; + valid0 = valid0 || _valid0; + if (!valid0) { + const _errs2 = errors; + if (typeof data !== 'string') { + const err0 = { + instancePath, + schemaPath: '#/anyOf/1/type', + keyword: 'type', + params: { type: 'string' }, + message: 'must be string', + }; + if (vErrors === null) { + vErrors = [err0]; + } else { + vErrors.push(err0); + } + errors++; + } + if ('7.2' !== data) { + const err1 = { instancePath, schemaPath: '#/anyOf/1/const', keyword: 'const', @@ -1657,18 +1760,18 @@ function validate12( message: 'must be equal to constant', }; if (vErrors === null) { - vErrors = [err3]; + vErrors = [err1]; } else { - vErrors.push(err3); + vErrors.push(err1); } errors++; } - var _valid0 = _errs4 === errors; + var _valid0 = _errs2 === errors; valid0 = valid0 || _valid0; if (!valid0) { - const _errs6 = errors; + const _errs4 = errors; if (typeof data !== 'string') { - const err4 = { + const err2 = { instancePath, schemaPath: '#/anyOf/2/type', keyword: 'type', @@ -1676,14 +1779,14 @@ function validate12( message: 'must be string', }; if (vErrors === null) { - vErrors = [err4]; + vErrors = [err2]; } else { - vErrors.push(err4); + vErrors.push(err2); } errors++; } if ('7.3' !== data) { - const err5 = { + const err3 = { instancePath, schemaPath: '#/anyOf/2/const', keyword: 'const', @@ -1691,18 +1794,18 @@ function validate12( message: 'must be equal to constant', }; if (vErrors === null) { - vErrors = [err5]; + vErrors = [err3]; } else { - vErrors.push(err5); + vErrors.push(err3); } errors++; } - var _valid0 = _errs6 === errors; + var _valid0 = _errs4 === errors; valid0 = valid0 || _valid0; } } if (!valid0) { - const err6 = { + const err4 = { instancePath, schemaPath: '#/anyOf', keyword: 'anyOf', @@ -1710,9 +1813,9 @@ function validate12( message: 'must match a schema in anyOf', }; if (vErrors === null) { - vErrors = [err6]; + vErrors = [err4]; } else { - vErrors.push(err6); + vErrors.push(err4); } errors++; validate12.errors = vErrors; @@ -1730,7 +1833,7 @@ function validate12( validate12.errors = vErrors; return errors === 0; } -const schema17 = { +const schema19 = { anyOf: [ { $ref: '#/definitions/VFSReference' }, { $ref: '#/definitions/LiteralReference' }, @@ -1741,7 +1844,7 @@ const schema17 = { { $ref: '#/definitions/ZipWrapperReference' }, ], }; -const schema18 = { +const schema20 = { type: 'object', properties: { resource: { @@ -1758,7 +1861,7 @@ const schema18 = { required: ['resource', 'path'], additionalProperties: false, }; -const schema19 = { +const schema21 = { type: 'object', properties: { resource: { @@ -1800,7 +1903,7 @@ const schema19 = { required: ['resource', 'name', 'contents'], additionalProperties: false, }; -const schema20 = { +const schema22 = { type: 'object', properties: { resource: { @@ -1817,7 +1920,7 @@ const schema20 = { required: ['resource', 'slug'], additionalProperties: false, }; -const schema21 = { +const schema23 = { type: 'object', properties: { resource: { @@ -1834,7 +1937,7 @@ const schema21 = { required: ['resource', 'slug'], additionalProperties: false, }; -const schema22 = { +const schema24 = { type: 'object', properties: { resource: { @@ -1851,7 +1954,7 @@ const schema22 = { required: ['resource', 'url'], additionalProperties: false, }; -const schema23 = { +const schema25 = { type: 'object', properties: { resource: { @@ -1867,7 +1970,7 @@ const schema23 = { required: ['resource', 'path'], additionalProperties: false, }; -const schema24 = { +const schema26 = { type: 'object', properties: { resource: { @@ -1891,14 +1994,14 @@ const schema24 = { required: ['resource', 'inner'], additionalProperties: false, }; -const wrapper0 = { validate: validate14 }; -const schema25 = { +const wrapper0 = { validate: validate16 }; +const schema27 = { anyOf: [ { $ref: '#/definitions/GitDirectoryReference' }, { $ref: '#/definitions/DirectoryLiteralReference' }, ], }; -const schema26 = { +const schema28 = { type: 'object', properties: { resource: { @@ -1931,11 +2034,11 @@ const schema26 = { required: ['resource', 'url', 'ref'], additionalProperties: false, }; -const schema27 = { +const schema29 = { type: 'string', enum: ['branch', 'tag', 'commit', 'refname'], }; -function validate17( +function validate19( data, { instancePath = '', parentData, parentDataProperty, rootData = data } = {} ) { @@ -1949,7 +2052,7 @@ function validate17( (data.url === undefined && (missing0 = 'url')) || (data.ref === undefined && (missing0 = 'ref')) ) { - validate17.errors = [ + validate19.errors = [ { instancePath, schemaPath: '#/required', @@ -1973,7 +2076,7 @@ function validate17( key0 === '.git' ) ) { - validate17.errors = [ + validate19.errors = [ { instancePath, schemaPath: '#/additionalProperties', @@ -1991,7 +2094,7 @@ function validate17( let data0 = data.resource; const _errs2 = errors; if (typeof data0 !== 'string') { - validate17.errors = [ + validate19.errors = [ { instancePath: instancePath + '/resource', schemaPath: '#/properties/resource/type', @@ -2003,7 +2106,7 @@ function validate17( return false; } if ('git:directory' !== data0) { - validate17.errors = [ + validate19.errors = [ { instancePath: instancePath + '/resource', schemaPath: '#/properties/resource/const', @@ -2022,7 +2125,7 @@ function validate17( if (data.url !== undefined) { const _errs4 = errors; if (typeof data.url !== 'string') { - validate17.errors = [ + validate19.errors = [ { instancePath: instancePath + '/url', schemaPath: '#/properties/url/type', @@ -2041,7 +2144,7 @@ function validate17( if (data.ref !== undefined) { const _errs6 = errors; if (typeof data.ref !== 'string') { - validate17.errors = [ + validate19.errors = [ { instancePath: instancePath + '/ref', schemaPath: '#/properties/ref/type', @@ -2061,7 +2164,7 @@ function validate17( let data3 = data.refType; const _errs8 = errors; if (typeof data3 !== 'string') { - validate17.errors = [ + validate19.errors = [ { instancePath: instancePath + '/refType', @@ -2082,7 +2185,7 @@ function validate17( data3 === 'refname' ) ) { - validate17.errors = [ + validate19.errors = [ { instancePath: instancePath + '/refType', @@ -2091,7 +2194,7 @@ function validate17( keyword: 'enum', params: { allowedValues: - schema27.enum, + schema29.enum, }, message: 'must be equal to one of the allowed values', @@ -2107,7 +2210,7 @@ function validate17( if (data.path !== undefined) { const _errs11 = errors; if (typeof data.path !== 'string') { - validate17.errors = [ + validate19.errors = [ { instancePath: instancePath + '/path', @@ -2131,7 +2234,7 @@ function validate17( typeof data['.git'] !== 'boolean' ) { - validate17.errors = [ + validate19.errors = [ { instancePath: instancePath + @@ -2160,7 +2263,7 @@ function validate17( } } } else { - validate17.errors = [ + validate19.errors = [ { instancePath, schemaPath: '#/type', @@ -2172,10 +2275,10 @@ function validate17( return false; } } - validate17.errors = vErrors; + validate19.errors = vErrors; return errors === 0; } -const schema28 = { +const schema30 = { type: 'object', additionalProperties: false, properties: { @@ -2189,7 +2292,7 @@ const schema28 = { }, required: ['files', 'name', 'resource'], }; -const schema29 = { +const schema31 = { type: 'object', additionalProperties: { anyOf: [ @@ -2199,8 +2302,8 @@ const schema29 = { }, properties: {}, }; -const wrapper1 = { validate: validate20 }; -function validate20( +const wrapper1 = { validate: validate22 }; +function validate22( data, { instancePath = '', parentData, parentDataProperty, rootData = data } = {} ) { @@ -2251,7 +2354,7 @@ function validate20( schemaPath: '#/additionalProperties/anyOf/1/type', keyword: 'type', params: { - type: schema29.additionalProperties.anyOf[1] + type: schema31.additionalProperties.anyOf[1] .type, }, message: 'must be object,string', @@ -2283,7 +2386,7 @@ function validate20( vErrors.push(err1); } errors++; - validate20.errors = vErrors; + validate22.errors = vErrors; return false; } else { errors = _errs3; @@ -2301,7 +2404,7 @@ function validate20( } } } else { - validate20.errors = [ + validate22.errors = [ { instancePath, schemaPath: '#/type', @@ -2313,10 +2416,10 @@ function validate20( return false; } } - validate20.errors = vErrors; + validate22.errors = vErrors; return errors === 0; } -function validate19( +function validate21( data, { instancePath = '', parentData, parentDataProperty, rootData = data } = {} ) { @@ -2330,7 +2433,7 @@ function validate19( (data.name === undefined && (missing0 = 'name')) || (data.resource === undefined && (missing0 = 'resource')) ) { - validate19.errors = [ + validate21.errors = [ { instancePath, schemaPath: '#/required', @@ -2351,7 +2454,7 @@ function validate19( key0 === 'name' ) ) { - validate19.errors = [ + validate21.errors = [ { instancePath, schemaPath: '#/additionalProperties', @@ -2369,7 +2472,7 @@ function validate19( let data0 = data.resource; const _errs2 = errors; if (typeof data0 !== 'string') { - validate19.errors = [ + validate21.errors = [ { instancePath: instancePath + '/resource', schemaPath: '#/properties/resource/type', @@ -2381,7 +2484,7 @@ function validate19( return false; } if ('literal:directory' !== data0) { - validate19.errors = [ + validate21.errors = [ { instancePath: instancePath + '/resource', schemaPath: '#/properties/resource/const', @@ -2402,7 +2505,7 @@ function validate19( if (data.files !== undefined) { const _errs4 = errors; if ( - !validate20(data.files, { + !validate22(data.files, { instancePath: instancePath + '/files', parentData: data, parentDataProperty: 'files', @@ -2411,8 +2514,8 @@ function validate19( ) { vErrors = vErrors === null - ? validate20.errors - : vErrors.concat(validate20.errors); + ? validate22.errors + : vErrors.concat(validate22.errors); errors = vErrors.length; } var valid0 = _errs4 === errors; @@ -2423,7 +2526,7 @@ function validate19( if (data.name !== undefined) { const _errs5 = errors; if (typeof data.name !== 'string') { - validate19.errors = [ + validate21.errors = [ { instancePath: instancePath + '/name', @@ -2445,7 +2548,7 @@ function validate19( } } } else { - validate19.errors = [ + validate21.errors = [ { instancePath, schemaPath: '#/type', @@ -2457,10 +2560,10 @@ function validate19( return false; } } - validate19.errors = vErrors; + validate21.errors = vErrors; return errors === 0; } -function validate16( +function validate18( data, { instancePath = '', parentData, parentDataProperty, rootData = data } = {} ) { @@ -2470,7 +2573,7 @@ function validate16( let valid0 = false; const _errs1 = errors; if ( - !validate17(data, { + !validate19(data, { instancePath, parentData, parentDataProperty, @@ -2479,8 +2582,8 @@ function validate16( ) { vErrors = vErrors === null - ? validate17.errors - : vErrors.concat(validate17.errors); + ? validate19.errors + : vErrors.concat(validate19.errors); errors = vErrors.length; } var _valid0 = _errs1 === errors; @@ -2488,7 +2591,7 @@ function validate16( if (!valid0) { const _errs2 = errors; if ( - !validate19(data, { + !validate21(data, { instancePath, parentData, parentDataProperty, @@ -2497,8 +2600,8 @@ function validate16( ) { vErrors = vErrors === null - ? validate19.errors - : vErrors.concat(validate19.errors); + ? validate21.errors + : vErrors.concat(validate21.errors); errors = vErrors.length; } var _valid0 = _errs2 === errors; @@ -2518,7 +2621,7 @@ function validate16( vErrors.push(err0); } errors++; - validate16.errors = vErrors; + validate18.errors = vErrors; return false; } else { errors = _errs0; @@ -2530,10 +2633,10 @@ function validate16( } } } - validate16.errors = vErrors; + validate18.errors = vErrors; return errors === 0; } -function validate15( +function validate17( data, { instancePath = '', parentData, parentDataProperty, rootData = data } = {} ) { @@ -2546,7 +2649,7 @@ function validate15( (data.resource === undefined && (missing0 = 'resource')) || (data.inner === undefined && (missing0 = 'inner')) ) { - validate15.errors = [ + validate17.errors = [ { instancePath, schemaPath: '#/required', @@ -2567,7 +2670,7 @@ function validate15( key0 === 'name' ) ) { - validate15.errors = [ + validate17.errors = [ { instancePath, schemaPath: '#/additionalProperties', @@ -2585,7 +2688,7 @@ function validate15( let data0 = data.resource; const _errs2 = errors; if (typeof data0 !== 'string') { - validate15.errors = [ + validate17.errors = [ { instancePath: instancePath + '/resource', schemaPath: '#/properties/resource/type', @@ -2597,7 +2700,7 @@ function validate15( return false; } if ('zip' !== data0) { - validate15.errors = [ + validate17.errors = [ { instancePath: instancePath + '/resource', schemaPath: '#/properties/resource/const', @@ -2640,7 +2743,7 @@ function validate15( if (!valid1) { const _errs7 = errors; if ( - !validate16(data1, { + !validate18(data1, { instancePath: instancePath + '/inner', parentData: data, parentDataProperty: 'inner', @@ -2649,8 +2752,8 @@ function validate15( ) { vErrors = vErrors === null - ? validate16.errors - : vErrors.concat(validate16.errors); + ? validate18.errors + : vErrors.concat(validate18.errors); errors = vErrors.length; } var _valid0 = _errs7 === errors; @@ -2670,7 +2773,7 @@ function validate15( vErrors.push(err0); } errors++; - validate15.errors = vErrors; + validate17.errors = vErrors; return false; } else { errors = _errs5; @@ -2690,7 +2793,7 @@ function validate15( if (data.name !== undefined) { const _errs8 = errors; if (typeof data.name !== 'string') { - validate15.errors = [ + validate17.errors = [ { instancePath: instancePath + '/name', @@ -2712,7 +2815,7 @@ function validate15( } } } else { - validate15.errors = [ + validate17.errors = [ { instancePath, schemaPath: '#/type', @@ -2724,10 +2827,10 @@ function validate15( return false; } } - validate15.errors = vErrors; + validate17.errors = vErrors; return errors === 0; } -function validate14( +function validate16( data, { instancePath = '', parentData, parentDataProperty, rootData = data } = {} ) { @@ -4255,7 +4358,7 @@ function validate14( if (!valid0) { const _errs73 = errors; if ( - !validate15(data, { + !validate17(data, { instancePath, parentData, parentDataProperty, @@ -4264,8 +4367,8 @@ function validate14( ) { vErrors = vErrors === null - ? validate15.errors - : vErrors.concat(validate15.errors); + ? validate17.errors + : vErrors.concat(validate17.errors); errors = vErrors.length; } var _valid0 = _errs73 === errors; @@ -4290,7 +4393,7 @@ function validate14( vErrors.push(err50); } errors++; - validate14.errors = vErrors; + validate16.errors = vErrors; return false; } else { errors = _errs0; @@ -4302,10 +4405,10 @@ function validate14( } } } - validate14.errors = vErrors; + validate16.errors = vErrors; return errors === 0; } -const schema30 = { +const schema32 = { type: 'object', discriminator: { propertyName: 'step' }, required: ['step'], @@ -5042,7 +5145,7 @@ const schema30 = { }, ], }; -const schema31 = { +const schema33 = { type: 'object', properties: { activate: { @@ -5057,7 +5160,7 @@ const schema31 = { }, additionalProperties: false, }; -const schema32 = { +const schema34 = { type: 'object', properties: { activate: { @@ -5077,7 +5180,7 @@ const schema32 = { }, additionalProperties: false, }; -const schema39 = { +const schema41 = { type: 'object', properties: { adminUsername: { type: 'string' }, @@ -5085,7 +5188,7 @@ const schema39 = { }, additionalProperties: false, }; -const schema33 = { +const schema35 = { type: 'object', properties: { method: { @@ -5182,12 +5285,12 @@ const schema33 = { required: ['url'], additionalProperties: false, }; -const schema34 = { +const schema36 = { type: 'string', enum: ['GET', 'POST', 'HEAD', 'OPTIONS', 'PATCH', 'PUT', 'DELETE'], }; -const schema35 = { type: 'object', additionalProperties: { type: 'string' } }; -function validate35( +const schema37 = { type: 'object', additionalProperties: { type: 'string' } }; +function validate37( data, { instancePath = '', parentData, parentDataProperty, rootData = data } = {} ) { @@ -5197,7 +5300,7 @@ function validate35( if (data && typeof data == 'object' && !Array.isArray(data)) { let missing0; if (data.url === undefined && (missing0 = 'url')) { - validate35.errors = [ + validate37.errors = [ { instancePath, schemaPath: '#/required', @@ -5219,7 +5322,7 @@ function validate35( key0 === 'body' ) ) { - validate35.errors = [ + validate37.errors = [ { instancePath, schemaPath: '#/additionalProperties', @@ -5237,7 +5340,7 @@ function validate35( let data0 = data.method; const _errs2 = errors; if (typeof data0 !== 'string') { - validate35.errors = [ + validate37.errors = [ { instancePath: instancePath + '/method', schemaPath: '#/definitions/HTTPMethod/type', @@ -5259,12 +5362,12 @@ function validate35( data0 === 'DELETE' ) ) { - validate35.errors = [ + validate37.errors = [ { instancePath: instancePath + '/method', schemaPath: '#/definitions/HTTPMethod/enum', keyword: 'enum', - params: { allowedValues: schema34.enum }, + params: { allowedValues: schema36.enum }, message: 'must be equal to one of the allowed values', }, @@ -5279,7 +5382,7 @@ function validate35( if (data.url !== undefined) { const _errs5 = errors; if (typeof data.url !== 'string') { - validate35.errors = [ + validate37.errors = [ { instancePath: instancePath + '/url', schemaPath: '#/properties/url/type', @@ -5310,7 +5413,7 @@ function validate35( if ( typeof data2[key1] !== 'string' ) { - validate35.errors = [ + validate37.errors = [ { instancePath: instancePath + @@ -5342,7 +5445,7 @@ function validate35( } } } else { - validate35.errors = [ + validate37.errors = [ { instancePath: instancePath + '/headers', @@ -7331,7 +7434,7 @@ function validate35( vErrors.push(err34); } errors++; - validate35.errors = vErrors; + validate37.errors = vErrors; return false; } else { errors = _errs14; @@ -7353,7 +7456,7 @@ function validate35( } } } else { - validate35.errors = [ + validate37.errors = [ { instancePath, schemaPath: '#/type', @@ -7365,10 +7468,10 @@ function validate35( return false; } } - validate35.errors = vErrors; + validate37.errors = vErrors; return errors === 0; } -const schema36 = { +const schema38 = { type: 'object', properties: { relativeUri: { @@ -7435,7 +7538,7 @@ const schema36 = { }, additionalProperties: false, }; -function validate37( +function validate39( data, { instancePath = '', parentData, parentDataProperty, rootData = data } = {} ) { @@ -7445,8 +7548,8 @@ function validate37( if (data && typeof data == 'object' && !Array.isArray(data)) { const _errs1 = errors; for (const key0 in data) { - if (!func2.call(schema36.properties, key0)) { - validate37.errors = [ + if (!func2.call(schema38.properties, key0)) { + validate39.errors = [ { instancePath, schemaPath: '#/additionalProperties', @@ -7463,7 +7566,7 @@ function validate37( if (data.relativeUri !== undefined) { const _errs2 = errors; if (typeof data.relativeUri !== 'string') { - validate37.errors = [ + validate39.errors = [ { instancePath: instancePath + '/relativeUri', schemaPath: '#/properties/relativeUri/type', @@ -7482,7 +7585,7 @@ function validate37( if (data.scriptPath !== undefined) { const _errs4 = errors; if (typeof data.scriptPath !== 'string') { - validate37.errors = [ + validate39.errors = [ { instancePath: instancePath + '/scriptPath', schemaPath: '#/properties/scriptPath/type', @@ -7501,7 +7604,7 @@ function validate37( if (data.protocol !== undefined) { const _errs6 = errors; if (typeof data.protocol !== 'string') { - validate37.errors = [ + validate39.errors = [ { instancePath: instancePath + '/protocol', @@ -7523,7 +7626,7 @@ function validate37( let data3 = data.method; const _errs8 = errors; if (typeof data3 !== 'string') { - validate37.errors = [ + validate39.errors = [ { instancePath: instancePath + '/method', @@ -7547,7 +7650,7 @@ function validate37( data3 === 'DELETE' ) ) { - validate37.errors = [ + validate39.errors = [ { instancePath: instancePath + '/method', @@ -7555,7 +7658,7 @@ function validate37( '#/definitions/HTTPMethod/enum', keyword: 'enum', params: { - allowedValues: schema34.enum, + allowedValues: schema36.enum, }, message: 'must be equal to one of the allowed values', @@ -7584,7 +7687,7 @@ function validate37( typeof data4[key1] !== 'string' ) { - validate37.errors = [ + validate39.errors = [ { instancePath: instancePath + @@ -7616,7 +7719,7 @@ function validate37( } } } else { - validate37.errors = [ + validate39.errors = [ { instancePath: instancePath + @@ -8254,7 +8357,7 @@ function validate37( vErrors.push(err12); } errors++; - validate37.errors = vErrors; + validate39.errors = vErrors; return false; } else { errors = _errs18; @@ -8287,7 +8390,7 @@ function validate37( key4 ] !== 'string' ) { - validate37.errors = + validate39.errors = [ { instancePath: @@ -8322,7 +8425,7 @@ function validate37( } } } else { - validate37.errors = [ + validate39.errors = [ { instancePath: instancePath + @@ -8363,7 +8466,7 @@ function validate37( key5 ] !== 'string' ) { - validate37.errors = + validate39.errors = [ { instancePath: @@ -8399,7 +8502,7 @@ function validate37( } } } else { - validate37.errors = [ + validate39.errors = [ { instancePath: instancePath + @@ -8428,7 +8531,7 @@ function validate37( typeof data.code !== 'string' ) { - validate37.errors = [ + validate39.errors = [ { instancePath: instancePath + @@ -8460,7 +8563,7 @@ function validate37( } } } else { - validate37.errors = [ + validate39.errors = [ { instancePath, schemaPath: '#/type', @@ -8472,10 +8575,10 @@ function validate37( return false; } } - validate37.errors = vErrors; + validate39.errors = vErrors; return errors === 0; } -function validate26( +function validate28( data, { instancePath = '', parentData, parentDataProperty, rootData = data } = {} ) { @@ -8485,7 +8588,7 @@ function validate26( if (data && typeof data == 'object' && !Array.isArray(data)) { let missing0; if (data.step === undefined && (missing0 = 'step')) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/required', @@ -8514,7 +8617,7 @@ function validate26( (data.step === undefined && (missing1 = 'step')) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/0/required', @@ -8540,7 +8643,7 @@ function validate26( key0 === 'pluginName' ) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: @@ -8579,7 +8682,7 @@ function validate26( 'caption' ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -8619,7 +8722,7 @@ function validate26( ) ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -8655,7 +8758,7 @@ function validate26( typeof data0.caption !== 'string' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -8683,7 +8786,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -8710,7 +8813,7 @@ function validate26( let data3 = data.step; const _errs12 = errors; if (typeof data3 !== 'string') { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -8730,7 +8833,7 @@ function validate26( if ( 'activatePlugin' !== data3 ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -8762,7 +8865,7 @@ function validate26( typeof data.pluginPath !== 'string' ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -8794,7 +8897,7 @@ function validate26( typeof data.pluginName !== 'string' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -8824,7 +8927,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/0/type', @@ -8851,7 +8954,7 @@ function validate26( (data.themeFolderName === undefined && (missing2 = 'themeFolderName')) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/1/required', @@ -8876,7 +8979,7 @@ function validate26( key2 === 'themeFolderName' ) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: @@ -8915,7 +9018,7 @@ function validate26( 'caption' ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -8955,7 +9058,7 @@ function validate26( ) ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -8991,7 +9094,7 @@ function validate26( typeof data6.caption !== 'string' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -9019,7 +9122,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -9046,7 +9149,7 @@ function validate26( let data9 = data.step; const _errs28 = errors; if (typeof data9 !== 'string') { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -9064,7 +9167,7 @@ function validate26( return false; } if ('activateTheme' !== data9) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -9096,7 +9199,7 @@ function validate26( typeof data.themeFolderName !== 'string' ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -9123,7 +9226,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/1/type', @@ -9152,7 +9255,7 @@ function validate26( (data.toPath === undefined && (missing3 = 'toPath')) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/2/required', @@ -9178,7 +9281,7 @@ function validate26( key4 === 'toPath' ) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: @@ -9217,7 +9320,7 @@ function validate26( 'caption' ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -9257,7 +9360,7 @@ function validate26( ) ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -9293,7 +9396,7 @@ function validate26( typeof data11.caption !== 'string' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -9321,7 +9424,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -9350,7 +9453,7 @@ function validate26( if ( typeof data14 !== 'string' ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -9368,7 +9471,7 @@ function validate26( return false; } if ('cp' !== data14) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -9399,7 +9502,7 @@ function validate26( typeof data.fromPath !== 'string' ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -9431,7 +9534,7 @@ function validate26( typeof data.toPath !== 'string' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -9461,7 +9564,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/2/type', @@ -9488,7 +9591,7 @@ function validate26( (data.step === undefined && (missing4 = 'step')) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/3/required', @@ -9515,7 +9618,7 @@ function validate26( key6 === 'virtualize' ) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: @@ -9554,7 +9657,7 @@ function validate26( 'caption' ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -9594,7 +9697,7 @@ function validate26( ) ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -9630,7 +9733,7 @@ function validate26( typeof data17.caption !== 'string' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -9658,7 +9761,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -9687,7 +9790,7 @@ function validate26( if ( typeof data20 !== 'string' ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -9708,7 +9811,7 @@ function validate26( 'defineWpConfigConsts' !== data20 ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -9755,7 +9858,7 @@ function validate26( } } } else { - validate26.errors = + validate28.errors = [ { instancePath: @@ -9792,7 +9895,7 @@ function validate26( typeof data23 !== 'string' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -9819,7 +9922,7 @@ function validate26( 'define-before-run' ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -9831,7 +9934,7 @@ function validate26( 'enum', params: { allowedValues: - schema30 + schema32 .oneOf[3] .properties .method @@ -9859,7 +9962,7 @@ function validate26( typeof data.virtualize !== 'boolean' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -9891,7 +9994,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/3/type', @@ -9918,7 +10021,7 @@ function validate26( (data.step === undefined && (missing5 = 'step')) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/4/required', @@ -9943,7 +10046,7 @@ function validate26( key9 === 'siteUrl' ) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: @@ -9982,7 +10085,7 @@ function validate26( 'caption' ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -10022,7 +10125,7 @@ function validate26( ) ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -10058,7 +10161,7 @@ function validate26( typeof data25.caption !== 'string' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -10086,7 +10189,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -10115,7 +10218,7 @@ function validate26( if ( typeof data28 !== 'string' ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -10135,7 +10238,7 @@ function validate26( if ( 'defineSiteUrl' !== data28 ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -10167,7 +10270,7 @@ function validate26( typeof data.siteUrl !== 'string' ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -10194,7 +10297,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/4/type', @@ -10219,7 +10322,7 @@ function validate26( data.step === undefined && (missing6 = 'step') ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/5/required', @@ -10244,7 +10347,7 @@ function validate26( key11 === 'wpCliPath' ) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: @@ -10283,7 +10386,7 @@ function validate26( 'caption' ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -10323,7 +10426,7 @@ function validate26( ) ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -10359,7 +10462,7 @@ function validate26( typeof data30.caption !== 'string' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -10387,7 +10490,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -10416,7 +10519,7 @@ function validate26( if ( typeof data33 !== 'string' ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -10436,7 +10539,7 @@ function validate26( if ( 'enableMultisite' !== data33 ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -10468,7 +10571,7 @@ function validate26( typeof data.wpCliPath !== 'string' ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -10495,7 +10598,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/5/type', @@ -10522,7 +10625,7 @@ function validate26( (data.step === undefined && (missing7 = 'step')) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/6/required', @@ -10548,7 +10651,7 @@ function validate26( key13 === 'importer' ) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: @@ -10587,7 +10690,7 @@ function validate26( 'caption' ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -10627,7 +10730,7 @@ function validate26( ) ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -10663,7 +10766,7 @@ function validate26( typeof data35.caption !== 'string' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -10691,7 +10794,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -10720,7 +10823,7 @@ function validate26( if ( typeof data38 !== 'string' ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -10738,7 +10841,7 @@ function validate26( return false; } if ('importWxr' !== data38) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -10765,7 +10868,7 @@ function validate26( if (data.file !== undefined) { const _errs108 = errors; if ( - !validate14(data.file, { + !validate16(data.file, { instancePath: instancePath + '/file', @@ -10777,9 +10880,9 @@ function validate26( ) { vErrors = vErrors === null - ? validate14.errors + ? validate16.errors : vErrors.concat( - validate14.errors + validate16.errors ); errors = vErrors.length; } @@ -10800,7 +10903,7 @@ function validate26( typeof data40 !== 'string' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -10827,7 +10930,7 @@ function validate26( 'default' ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -10839,7 +10942,7 @@ function validate26( 'enum', params: { allowedValues: - schema30 + schema32 .oneOf[6] .properties .importer @@ -10862,7 +10965,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/6/type', @@ -10887,7 +10990,7 @@ function validate26( data.step === undefined && (missing8 = 'step') ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/7/required', @@ -10912,7 +11015,7 @@ function validate26( key15 === 'themeSlug' ) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: @@ -10951,7 +11054,7 @@ function validate26( 'caption' ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -10991,7 +11094,7 @@ function validate26( ) ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -11027,7 +11130,7 @@ function validate26( typeof data41.caption !== 'string' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -11055,7 +11158,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -11084,7 +11187,7 @@ function validate26( if ( typeof data44 !== 'string' ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -11105,7 +11208,7 @@ function validate26( 'importThemeStarterContent' !== data44 ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -11137,7 +11240,7 @@ function validate26( typeof data.themeSlug !== 'string' ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -11164,7 +11267,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/7/type', @@ -11191,7 +11294,7 @@ function validate26( (data.wordPressFilesZip === undefined && (missing9 = 'wordPressFilesZip')) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/8/required', @@ -11217,7 +11320,7 @@ function validate26( key17 === 'pathInZip' ) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: @@ -11256,7 +11359,7 @@ function validate26( 'caption' ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -11296,7 +11399,7 @@ function validate26( ) ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -11332,7 +11435,7 @@ function validate26( typeof data46.caption !== 'string' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -11360,7 +11463,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -11389,7 +11492,7 @@ function validate26( if ( typeof data49 !== 'string' ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -11410,7 +11513,7 @@ function validate26( 'importWordPressFiles' !== data49 ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -11440,7 +11543,7 @@ function validate26( ) { const _errs137 = errors; if ( - !validate14( + !validate16( data.wordPressFilesZip, { instancePath: @@ -11456,9 +11559,9 @@ function validate26( ) { vErrors = vErrors === null - ? validate14.errors + ? validate16.errors : vErrors.concat( - validate14.errors + validate16.errors ); errors = vErrors.length; } @@ -11477,7 +11580,7 @@ function validate26( typeof data.pathInZip !== 'string' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -11507,7 +11610,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/8/type', @@ -11534,7 +11637,7 @@ function validate26( (data.step === undefined && (missing10 = 'step')) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/9/required', @@ -11563,7 +11666,7 @@ function validate26( key19 === 'options' ) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: @@ -11602,7 +11705,7 @@ function validate26( 'caption' ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -11642,7 +11745,7 @@ function validate26( ) ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -11678,7 +11781,7 @@ function validate26( typeof data52.caption !== 'string' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -11706,7 +11809,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -11739,7 +11842,7 @@ function validate26( if ( typeof data55 !== 'string' ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -11764,7 +11867,7 @@ function validate26( data55 === 'error' ) ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -11774,7 +11877,7 @@ function validate26( keyword: 'enum', params: { allowedValues: - schema30 + schema32 .oneOf[9] .properties .ifAlreadyInstalled @@ -11799,7 +11902,7 @@ function validate26( typeof data56 !== 'string' ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -11820,7 +11923,7 @@ function validate26( 'installPlugin' !== data56 ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -11856,7 +11959,7 @@ function validate26( let valid32 = false; const _errs156 = errors; if ( - !validate14( + !validate16( data57, { instancePath: @@ -11872,9 +11975,9 @@ function validate26( ) { vErrors = vErrors === null - ? validate14.errors + ? validate16.errors : vErrors.concat( - validate14.errors + validate16.errors ); errors = vErrors.length; @@ -11887,7 +11990,7 @@ function validate26( const _errs157 = errors; if ( - !validate16( + !validate18( data57, { instancePath: @@ -11904,9 +12007,9 @@ function validate26( vErrors = vErrors === null - ? validate16.errors + ? validate18.errors : vErrors.concat( - validate16.errors + validate18.errors ); errors = vErrors.length; @@ -11943,7 +12046,7 @@ function validate26( ); } errors++; - validate26.errors = + validate28.errors = vErrors; return false; } else { @@ -11973,7 +12076,7 @@ function validate26( const _errs158 = errors; if ( - !validate14( + !validate16( data.pluginZipFile, { instancePath: @@ -11990,9 +12093,9 @@ function validate26( vErrors = vErrors === null - ? validate14.errors + ? validate16.errors : vErrors.concat( - validate14.errors + validate16.errors ); errors = vErrors.length; @@ -12037,7 +12140,7 @@ function validate26( 'targetFolderName' ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -12073,7 +12176,7 @@ function validate26( typeof data59.activate !== 'boolean' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -12111,7 +12214,7 @@ function validate26( typeof data59.targetFolderName !== 'string' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -12139,7 +12242,7 @@ function validate26( } } } else { - validate26.errors = + validate28.errors = [ { instancePath: @@ -12173,7 +12276,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/9/type', @@ -12200,7 +12303,7 @@ function validate26( (data.themeData === undefined && (missing11 = 'themeData')) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/10/required', @@ -12229,7 +12332,7 @@ function validate26( key22 === 'options' ) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: @@ -12268,7 +12371,7 @@ function validate26( 'caption' ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -12308,7 +12411,7 @@ function validate26( ) ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -12344,7 +12447,7 @@ function validate26( typeof data62.caption !== 'string' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -12372,7 +12475,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -12405,7 +12508,7 @@ function validate26( if ( typeof data65 !== 'string' ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -12430,7 +12533,7 @@ function validate26( data65 === 'error' ) ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -12440,7 +12543,7 @@ function validate26( keyword: 'enum', params: { allowedValues: - schema30 + schema32 .oneOf[10] .properties .ifAlreadyInstalled @@ -12465,7 +12568,7 @@ function validate26( typeof data66 !== 'string' ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -12486,7 +12589,7 @@ function validate26( 'installTheme' !== data66 ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -12522,7 +12625,7 @@ function validate26( let valid38 = false; const _errs183 = errors; if ( - !validate14( + !validate16( data67, { instancePath: @@ -12538,9 +12641,9 @@ function validate26( ) { vErrors = vErrors === null - ? validate14.errors + ? validate16.errors : vErrors.concat( - validate14.errors + validate16.errors ); errors = vErrors.length; @@ -12553,7 +12656,7 @@ function validate26( const _errs184 = errors; if ( - !validate16( + !validate18( data67, { instancePath: @@ -12570,9 +12673,9 @@ function validate26( vErrors = vErrors === null - ? validate16.errors + ? validate18.errors : vErrors.concat( - validate16.errors + validate18.errors ); errors = vErrors.length; @@ -12609,7 +12712,7 @@ function validate26( ); } errors++; - validate26.errors = + validate28.errors = vErrors; return false; } else { @@ -12639,7 +12742,7 @@ function validate26( const _errs185 = errors; if ( - !validate14( + !validate16( data.themeZipFile, { instancePath: @@ -12656,9 +12759,9 @@ function validate26( vErrors = vErrors === null - ? validate14.errors + ? validate16.errors : vErrors.concat( - validate14.errors + validate16.errors ); errors = vErrors.length; @@ -12705,7 +12808,7 @@ function validate26( 'targetFolderName' ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -12741,7 +12844,7 @@ function validate26( typeof data69.activate !== 'boolean' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -12779,7 +12882,7 @@ function validate26( typeof data69.importStarterContent !== 'boolean' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -12817,7 +12920,7 @@ function validate26( typeof data69.targetFolderName !== 'string' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -12846,7 +12949,7 @@ function validate26( } } } else { - validate26.errors = + validate28.errors = [ { instancePath: @@ -12880,7 +12983,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/10/type', @@ -12905,7 +13008,7 @@ function validate26( data.step === undefined && (missing12 = 'step') ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/11/required', @@ -12931,7 +13034,7 @@ function validate26( key25 === 'password' ) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: @@ -12970,7 +13073,7 @@ function validate26( 'caption' ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -13010,7 +13113,7 @@ function validate26( ) ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -13046,7 +13149,7 @@ function validate26( typeof data73.caption !== 'string' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -13074,7 +13177,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -13103,7 +13206,7 @@ function validate26( if ( typeof data76 !== 'string' ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -13121,7 +13224,7 @@ function validate26( return false; } if ('login' !== data76) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -13153,7 +13256,7 @@ function validate26( typeof data.username !== 'string' ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -13185,7 +13288,7 @@ function validate26( typeof data.password !== 'string' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -13215,7 +13318,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/11/type', @@ -13242,7 +13345,7 @@ function validate26( (data.step === undefined && (missing13 = 'step')) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/12/required', @@ -13267,7 +13370,7 @@ function validate26( key27 === 'path' ) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: @@ -13306,7 +13409,7 @@ function validate26( 'caption' ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -13346,7 +13449,7 @@ function validate26( ) ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -13382,7 +13485,7 @@ function validate26( typeof data79.caption !== 'string' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -13410,7 +13513,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -13439,7 +13542,7 @@ function validate26( if ( typeof data82 !== 'string' ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -13457,7 +13560,7 @@ function validate26( return false; } if ('mkdir' !== data82) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -13487,7 +13590,7 @@ function validate26( typeof data.path !== 'string' ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -13514,7 +13617,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/12/type', @@ -13543,7 +13646,7 @@ function validate26( (data.toPath === undefined && (missing14 = 'toPath')) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/13/required', @@ -13569,7 +13672,7 @@ function validate26( key29 === 'toPath' ) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: @@ -13608,7 +13711,7 @@ function validate26( 'caption' ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -13648,7 +13751,7 @@ function validate26( ) ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -13684,7 +13787,7 @@ function validate26( typeof data84.caption !== 'string' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -13712,7 +13815,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -13741,7 +13844,7 @@ function validate26( if ( typeof data87 !== 'string' ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -13759,7 +13862,7 @@ function validate26( return false; } if ('mv' !== data87) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -13791,7 +13894,7 @@ function validate26( typeof data.fromPath !== 'string' ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -13823,7 +13926,7 @@ function validate26( typeof data.toPath !== 'string' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -13853,7 +13956,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/13/type', @@ -13878,7 +13981,7 @@ function validate26( data.step === undefined && (missing15 = 'step') ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/14/required', @@ -13902,7 +14005,7 @@ function validate26( key31 === 'step' ) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: @@ -13941,7 +14044,7 @@ function validate26( 'caption' ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -13981,7 +14084,7 @@ function validate26( ) ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -14017,7 +14120,7 @@ function validate26( typeof data90.caption !== 'string' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -14045,7 +14148,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -14074,7 +14177,7 @@ function validate26( if ( typeof data93 !== 'string' ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -14092,7 +14195,7 @@ function validate26( return false; } if ('resetData' !== data93) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -14119,7 +14222,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/14/type', @@ -14146,7 +14249,7 @@ function validate26( (data.step === undefined && (missing16 = 'step')) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/15/required', @@ -14171,7 +14274,7 @@ function validate26( key33 === 'request' ) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: @@ -14210,7 +14313,7 @@ function validate26( 'caption' ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -14250,7 +14353,7 @@ function validate26( ) ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -14286,7 +14389,7 @@ function validate26( typeof data94.caption !== 'string' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -14314,7 +14417,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -14343,7 +14446,7 @@ function validate26( if ( typeof data97 !== 'string' ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -14361,7 +14464,7 @@ function validate26( return false; } if ('request' !== data97) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -14390,7 +14493,7 @@ function validate26( ) { const _errs266 = errors; if ( - !validate35( + !validate37( data.request, { instancePath: @@ -14406,9 +14509,9 @@ function validate26( ) { vErrors = vErrors === null - ? validate35.errors + ? validate37.errors : vErrors.concat( - validate35.errors + validate37.errors ); errors = vErrors.length; } @@ -14422,7 +14525,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/15/type', @@ -14449,7 +14552,7 @@ function validate26( (data.step === undefined && (missing17 = 'step')) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/16/required', @@ -14474,7 +14577,7 @@ function validate26( key35 === 'path' ) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: @@ -14513,7 +14616,7 @@ function validate26( 'caption' ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -14553,7 +14656,7 @@ function validate26( ) ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -14589,7 +14692,7 @@ function validate26( typeof data99.caption !== 'string' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -14617,7 +14720,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -14646,7 +14749,7 @@ function validate26( if ( typeof data102 !== 'string' ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -14664,7 +14767,7 @@ function validate26( return false; } if ('rm' !== data102) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -14694,7 +14797,7 @@ function validate26( typeof data.path !== 'string' ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -14721,7 +14824,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/16/type', @@ -14748,7 +14851,7 @@ function validate26( (data.step === undefined && (missing18 = 'step')) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/17/required', @@ -14773,7 +14876,7 @@ function validate26( key37 === 'path' ) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: @@ -14813,7 +14916,7 @@ function validate26( 'caption' ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -14853,7 +14956,7 @@ function validate26( ) ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -14889,7 +14992,7 @@ function validate26( typeof data104.caption !== 'string' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -14917,7 +15020,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -14946,7 +15049,7 @@ function validate26( if ( typeof data107 !== 'string' ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -14964,7 +15067,7 @@ function validate26( return false; } if ('rmdir' !== data107) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -14994,7 +15097,7 @@ function validate26( typeof data.path !== 'string' ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -15021,7 +15124,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/17/type', @@ -15048,7 +15151,7 @@ function validate26( (data.step === undefined && (missing19 = 'step')) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/18/required', @@ -15073,7 +15176,7 @@ function validate26( key39 === 'code' ) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: @@ -15113,7 +15216,7 @@ function validate26( 'caption' ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -15153,7 +15256,7 @@ function validate26( ) ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -15189,7 +15292,7 @@ function validate26( typeof data109.caption !== 'string' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -15217,7 +15320,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -15246,7 +15349,7 @@ function validate26( if ( typeof data112 !== 'string' ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -15264,7 +15367,7 @@ function validate26( return false; } if ('runPHP' !== data112) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -15578,7 +15681,7 @@ function validate26( vErrors.push(err8); } errors++; - validate26.errors = + validate28.errors = vErrors; return false; } else { @@ -15602,7 +15705,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/18/type', @@ -15629,7 +15732,7 @@ function validate26( (data.step === undefined && (missing21 = 'step')) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/19/required', @@ -15654,7 +15757,7 @@ function validate26( key42 === 'options' ) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: @@ -15694,7 +15797,7 @@ function validate26( 'caption' ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -15734,7 +15837,7 @@ function validate26( ) ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -15770,7 +15873,7 @@ function validate26( typeof data116.caption !== 'string' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -15798,7 +15901,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -15827,7 +15930,7 @@ function validate26( if ( typeof data119 !== 'string' ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -15848,7 +15951,7 @@ function validate26( 'runPHPWithOptions' !== data119 ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -15877,7 +15980,7 @@ function validate26( ) { const _errs330 = errors; if ( - !validate37( + !validate39( data.options, { instancePath: @@ -15893,9 +15996,9 @@ function validate26( ) { vErrors = vErrors === null - ? validate37.errors + ? validate39.errors : vErrors.concat( - validate37.errors + validate39.errors ); errors = vErrors.length; } @@ -15909,7 +16012,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/19/type', @@ -15936,7 +16039,7 @@ function validate26( (data.step === undefined && (missing22 = 'step')) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/20/required', @@ -15961,7 +16064,7 @@ function validate26( key44 === 'options' ) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: @@ -16001,7 +16104,7 @@ function validate26( 'caption' ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -16041,7 +16144,7 @@ function validate26( ) ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -16077,7 +16180,7 @@ function validate26( typeof data121.caption !== 'string' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -16105,7 +16208,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -16134,7 +16237,7 @@ function validate26( if ( typeof data124 !== 'string' ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -16155,7 +16258,7 @@ function validate26( 'runWpInstallationWizard' !== data124 ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -16205,7 +16308,7 @@ function validate26( 'adminPassword' ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -16241,7 +16344,7 @@ function validate26( typeof data125.adminUsername !== 'string' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -16277,7 +16380,7 @@ function validate26( typeof data125.adminPassword !== 'string' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -16305,7 +16408,7 @@ function validate26( } } } else { - validate26.errors = + validate28.errors = [ { instancePath: @@ -16335,7 +16438,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/20/type', @@ -16362,7 +16465,7 @@ function validate26( (data.step === undefined && (missing23 = 'step')) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/21/required', @@ -16387,7 +16490,7 @@ function validate26( key47 === 'sql' ) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: @@ -16427,7 +16530,7 @@ function validate26( 'caption' ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -16467,7 +16570,7 @@ function validate26( ) ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -16503,7 +16606,7 @@ function validate26( typeof data128.caption !== 'string' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -16531,7 +16634,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -16560,7 +16663,7 @@ function validate26( if ( typeof data131 !== 'string' ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -16578,7 +16681,7 @@ function validate26( return false; } if ('runSql' !== data131) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -16605,7 +16708,7 @@ function validate26( if (data.sql !== undefined) { const _errs363 = errors; if ( - !validate14(data.sql, { + !validate16(data.sql, { instancePath: instancePath + '/sql', @@ -16617,9 +16720,9 @@ function validate26( ) { vErrors = vErrors === null - ? validate14.errors + ? validate16.errors : vErrors.concat( - validate14.errors + validate16.errors ); errors = vErrors.length; } @@ -16633,7 +16736,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/21/type', @@ -16660,7 +16763,7 @@ function validate26( (data.step === undefined && (missing24 = 'step')) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/22/required', @@ -16685,7 +16788,7 @@ function validate26( key49 === 'options' ) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: @@ -16725,7 +16828,7 @@ function validate26( 'caption' ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -16765,7 +16868,7 @@ function validate26( ) ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -16801,7 +16904,7 @@ function validate26( typeof data133.caption !== 'string' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -16829,7 +16932,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -16858,7 +16961,7 @@ function validate26( if ( typeof data136 !== 'string' ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -16878,7 +16981,7 @@ function validate26( if ( 'setSiteOptions' !== data136 ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -16927,7 +17030,7 @@ function validate26( } } } else { - validate26.errors = + validate28.errors = [ { instancePath: @@ -16957,7 +17060,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/22/type', @@ -16984,7 +17087,7 @@ function validate26( (data.step === undefined && (missing25 = 'step')) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/23/required', @@ -17011,7 +17114,7 @@ function validate26( key52 === 'extractToPath' ) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: @@ -17051,7 +17154,7 @@ function validate26( 'caption' ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -17091,7 +17194,7 @@ function validate26( ) ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -17127,7 +17230,7 @@ function validate26( typeof data139.caption !== 'string' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -17155,7 +17258,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -17184,7 +17287,7 @@ function validate26( if ( typeof data142 !== 'string' ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -17202,7 +17305,7 @@ function validate26( return false; } if ('unzip' !== data142) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -17231,7 +17334,7 @@ function validate26( ) { const _errs392 = errors; if ( - !validate14( + !validate16( data.zipFile, { instancePath: @@ -17247,9 +17350,9 @@ function validate26( ) { vErrors = vErrors === null - ? validate14.errors + ? validate16.errors : vErrors.concat( - validate14.errors + validate16.errors ); errors = vErrors.length; } @@ -17268,7 +17371,7 @@ function validate26( typeof data.zipPath !== 'string' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -17303,7 +17406,7 @@ function validate26( typeof data.extractToPath !== 'string' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -17335,7 +17438,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/23/type', @@ -17364,7 +17467,7 @@ function validate26( (data.userId === undefined && (missing26 = 'userId')) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/24/required', @@ -17390,7 +17493,7 @@ function validate26( key54 === 'userId' ) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: @@ -17430,7 +17533,7 @@ function validate26( 'caption' ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -17470,7 +17573,7 @@ function validate26( ) ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -17506,7 +17609,7 @@ function validate26( typeof data146.caption !== 'string' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -17534,7 +17637,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -17563,7 +17666,7 @@ function validate26( if ( typeof data149 !== 'string' ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -17583,7 +17686,7 @@ function validate26( if ( 'updateUserMeta' !== data149 ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -17630,7 +17733,7 @@ function validate26( } } } else { - validate26.errors = + validate28.errors = [ { instancePath: @@ -17672,7 +17775,7 @@ function validate26( ) ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -17702,7 +17805,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/24/type', @@ -17731,7 +17834,7 @@ function validate26( (data.step === undefined && (missing27 = 'step')) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/25/required', @@ -17757,7 +17860,7 @@ function validate26( key57 === 'data' ) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: @@ -17797,7 +17900,7 @@ function validate26( 'caption' ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -17837,7 +17940,7 @@ function validate26( ) ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -17873,7 +17976,7 @@ function validate26( typeof data153.caption !== 'string' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -17901,7 +18004,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -17930,7 +18033,7 @@ function validate26( if ( typeof data156 !== 'string' ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -17948,7 +18051,7 @@ function validate26( return false; } if ('writeFile' !== data156) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -17978,7 +18081,7 @@ function validate26( typeof data.path !== 'string' ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -18010,7 +18113,7 @@ function validate26( let valid92 = false; const _errs431 = errors; if ( - !validate14( + !validate16( data158, { instancePath: @@ -18026,9 +18129,9 @@ function validate26( ) { vErrors = vErrors === null - ? validate14.errors + ? validate16.errors : vErrors.concat( - validate14.errors + validate16.errors ); errors = vErrors.length; @@ -18721,7 +18824,7 @@ function validate26( ); } errors++; - validate26.errors = + validate28.errors = vErrors; return false; } else { @@ -18749,7 +18852,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/25/type', @@ -18778,7 +18881,7 @@ function validate26( (data.writeToPath === undefined && (missing30 = 'writeToPath')) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/26/required', @@ -18804,7 +18907,7 @@ function validate26( key61 === 'filesTree' ) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: @@ -18844,7 +18947,7 @@ function validate26( 'caption' ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -18884,7 +18987,7 @@ function validate26( ) ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -18920,7 +19023,7 @@ function validate26( typeof data166.caption !== 'string' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -18948,7 +19051,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -18977,7 +19080,7 @@ function validate26( if ( typeof data169 !== 'string' ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -18995,7 +19098,7 @@ function validate26( return false; } if ('writeFiles' !== data169) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -19028,7 +19131,7 @@ function validate26( typeof data.writeToPath !== 'string' ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -19057,7 +19160,7 @@ function validate26( ) { const _errs466 = errors; if ( - !validate16( + !validate18( data.filesTree, { instancePath: @@ -19073,9 +19176,9 @@ function validate26( ) { vErrors = vErrors === null - ? validate16.errors + ? validate18.errors : vErrors.concat( - validate16.errors + validate18.errors ); errors = vErrors.length; @@ -19091,7 +19194,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/26/type', @@ -19118,7 +19221,7 @@ function validate26( (data.step === undefined && (missing31 = 'step')) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/27/required', @@ -19144,7 +19247,7 @@ function validate26( key63 === 'wpCliPath' ) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: @@ -19184,7 +19287,7 @@ function validate26( 'caption' ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -19224,7 +19327,7 @@ function validate26( ) ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -19260,7 +19363,7 @@ function validate26( typeof data172.caption !== 'string' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -19288,7 +19391,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -19317,7 +19420,7 @@ function validate26( if ( typeof data175 !== 'string' ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -19335,7 +19438,7 @@ function validate26( return false; } if ('wp-cli' !== data175) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -19514,7 +19617,7 @@ function validate26( vErrors.push(err25); } errors++; - validate26.errors = + validate28.errors = vErrors; return false; } else { @@ -19543,7 +19646,7 @@ function validate26( typeof data.wpCliPath !== 'string' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -19573,7 +19676,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/27/type', @@ -19600,7 +19703,7 @@ function validate26( (data.step === undefined && (missing32 = 'step')) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/28/required', @@ -19625,7 +19728,7 @@ function validate26( key65 === 'language' ) ) { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: @@ -19665,7 +19768,7 @@ function validate26( 'caption' ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -19705,7 +19808,7 @@ function validate26( ) ) ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -19741,7 +19844,7 @@ function validate26( typeof data179.caption !== 'string' ) { - validate26.errors = + validate28.errors = [ { instancePath: @@ -19769,7 +19872,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -19798,7 +19901,7 @@ function validate26( if ( typeof data182 !== 'string' ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -19819,7 +19922,7 @@ function validate26( 'setSiteLanguage' !== data182 ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -19851,7 +19954,7 @@ function validate26( typeof data.language !== 'string' ) { - validate26.errors = [ + validate28.errors = [ { instancePath: instancePath + @@ -19878,7 +19981,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/oneOf/28/type', @@ -19891,7 +19994,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/discriminator', @@ -19907,7 +20010,7 @@ function validate26( return false; } } else { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/discriminator', @@ -19924,7 +20027,7 @@ function validate26( } } } else { - validate26.errors = [ + validate28.errors = [ { instancePath, schemaPath: '#/type', @@ -19936,7 +20039,7 @@ function validate26( return false; } } - validate26.errors = vErrors; + validate28.errors = vErrors; return errors === 0; } function validate11( @@ -20789,7 +20892,7 @@ function validate11( keyword: 'type', params: { - type: schema16 + type: schema18 .additionalProperties .type, }, @@ -20894,7 +20997,7 @@ function validate11( const _errs53 = errors; if ( - !validate14( + !validate16( data19, { instancePath: @@ -20912,9 +21015,9 @@ function validate11( vErrors = vErrors === null - ? validate14.errors + ? validate16.errors : vErrors.concat( - validate14.errors + validate16.errors ); errors = vErrors.length; @@ -21508,7 +21611,7 @@ function validate11( const _errs76 = errors; if ( - !validate26( + !validate28( data27, { instancePath: @@ -21526,9 +21629,9 @@ function validate11( vErrors = vErrors === null - ? validate26.errors + ? validate28.errors : vErrors.concat( - validate26.errors + validate28.errors ); errors = vErrors.length; diff --git a/packages/playground/blueprints/public/blueprint-schema.json b/packages/playground/blueprints/public/blueprint-schema.json index 4079f430a01..e4604b0c78e 100644 --- a/packages/playground/blueprints/public/blueprint-schema.json +++ b/packages/playground/blueprints/public/blueprint-schema.json @@ -175,7 +175,7 @@ "BlueprintPHPVersion": { "anyOf": [ { - "$ref": "#/definitions/SupportedPHPVersion" + "$ref": "#/definitions/AllPHPVersion" }, { "type": "string", @@ -188,10 +188,24 @@ ], "description": "PHP versions accepted in Blueprint schema. Includes deprecated versions (7.2, 7.3) which are automatically upgraded to 7.4 during compilation." }, + "AllPHPVersion": { + "anyOf": [ + { + "$ref": "#/definitions/SupportedPHPVersion" + }, + { + "$ref": "#/definitions/LegacyPHPVersion" + } + ] + }, "SupportedPHPVersion": { "type": "string", "enum": ["8.5", "8.4", "8.3", "8.2", "8.1", "8.0", "7.4"] }, + "LegacyPHPVersion": { + "type": "string", + "enum": ["5.6"] + }, "ExtraLibrary": { "type": "string", "const": "wp-cli" diff --git a/packages/playground/blueprints/src/lib/types.ts b/packages/playground/blueprints/src/lib/types.ts index 2088f8e257a..d7d253be054 100644 --- a/packages/playground/blueprints/src/lib/types.ts +++ b/packages/playground/blueprints/src/lib/types.ts @@ -9,7 +9,7 @@ import type { BlueprintV2, BlueprintV2Declaration, } from './v2/blueprint-v2-declaration'; -import type { SupportedPHPVersion } from '@php-wasm/universal'; +import type { AllPHPVersion } from '@php-wasm/universal'; /** * A filesystem structure containing a /blueprint.json file and any @@ -23,7 +23,7 @@ export type BlueprintDeclaration = export type Blueprint = BlueprintV1 | BlueprintV2; export interface RuntimeConfiguration { - phpVersion: SupportedPHPVersion; + phpVersion: AllPHPVersion; wpVersion: string; intl: boolean; networking: boolean; diff --git a/packages/playground/blueprints/src/lib/v1/compile.ts b/packages/playground/blueprints/src/lib/v1/compile.ts index 60b021a61e4..c2a4f9fe7eb 100644 --- a/packages/playground/blueprints/src/lib/v1/compile.ts +++ b/packages/playground/blueprints/src/lib/v1/compile.ts @@ -1,10 +1,7 @@ import { ProgressTracker } from '@php-wasm/progress'; import { Semaphore } from '@php-wasm/util'; -import type { SupportedPHPVersion, UniversalPHP } from '@php-wasm/universal'; -import { - LatestSupportedPHPVersion, - SupportedPHPVersions, -} from '@php-wasm/universal'; +import type { AllPHPVersion, UniversalPHP } from '@php-wasm/universal'; +import { AllPHPVersions, LatestSupportedPHPVersion } from '@php-wasm/universal'; import type { FileReference } from './resources'; import { isResourceReference, Resource } from './resources'; import type { Step, StepDefinition, WriteFileStep } from '../steps'; @@ -94,7 +91,7 @@ export type CompiledV1Step = (php: UniversalPHP) => Promise | void; export interface CompiledBlueprintV1 { /** The requested versions of PHP and WordPress for the blueprint */ versions: { - php: SupportedPHPVersion; + php: AllPHPVersion; wp: string; }; features: { @@ -400,7 +397,7 @@ function compileBlueprintJson( versions: { php: compileVersion( blueprint.preferredVersions?.php, - SupportedPHPVersions, + AllPHPVersions, LatestSupportedPHPVersion ), wp: blueprint.preferredVersions?.wp || 'latest', diff --git a/packages/playground/blueprints/src/lib/v1/types.ts b/packages/playground/blueprints/src/lib/v1/types.ts index 44fa8066d14..d0f247cab3f 100644 --- a/packages/playground/blueprints/src/lib/v1/types.ts +++ b/packages/playground/blueprints/src/lib/v1/types.ts @@ -1,4 +1,4 @@ -import type { SupportedPHPVersion } from '@php-wasm/universal'; +import type { AllPHPVersion } from '@php-wasm/universal'; import type { StepDefinition } from '../steps'; import type { FileReference } from './resources'; import type { StreamedFile } from '@php-wasm/stream-compression'; @@ -19,7 +19,7 @@ export type BlueprintV1 = BlueprintV1Declaration | BlueprintBundle; * Includes deprecated versions (7.2, 7.3) which are automatically * upgraded to 7.4 during compilation. */ -export type BlueprintPHPVersion = SupportedPHPVersion | '7.2' | '7.3'; +export type BlueprintPHPVersion = AllPHPVersion | '7.2' | '7.3'; /** * The Blueprint declaration, typically stored in a blueprint.json file. diff --git a/packages/playground/cli/src/blueprints-v1/blueprints-v1-handler.ts b/packages/playground/cli/src/blueprints-v1/blueprints-v1-handler.ts index b03fb70ef06..01950e4cc0a 100644 --- a/packages/playground/cli/src/blueprints-v1/blueprints-v1-handler.ts +++ b/packages/playground/cli/src/blueprints-v1/blueprints-v1-handler.ts @@ -19,6 +19,7 @@ import { CACHE_FOLDER, cachedDownload, fetchSqliteIntegration, + getPrebuiltWordPressPath, readAsFile, } from './download'; import type { PlaygroundCliBlueprintV1Worker } from './worker-thread-v1'; @@ -94,20 +95,29 @@ export class BlueprintsV1Handler { }) as any); wpDetails = await resolveWordPressRelease(this.args.wp); - preinstalledWpContentPath = path.join( - CACHE_FOLDER, - `prebuilt-wp-content-for-wp-${wpDetails.version}.zip` - ); - wordPressZip = fs.existsSync(preinstalledWpContentPath) - ? readAsFile(preinstalledWpContentPath) - : await cachedDownload( - wpDetails.releaseUrl, - `${wpDetails.version}.zip`, - monitor - ); - logger.debug( - `Resolved WordPress release URL: ${wpDetails?.releaseUrl}` - ); + + // Check for a pre-built WordPress module first (faster, + // minified, with set_time_limit stripped). + const prebuiltPath = getPrebuiltWordPressPath(wpDetails.version); + if (prebuiltPath) { + wordPressZip = readAsFile(prebuiltPath); + logger.debug(`Using pre-built WordPress: ${prebuiltPath}`); + } else { + preinstalledWpContentPath = path.join( + CACHE_FOLDER, + `prebuilt-wp-content-for-wp-${wpDetails.version}.zip` + ); + wordPressZip = fs.existsSync(preinstalledWpContentPath) + ? readAsFile(preinstalledWpContentPath) + : await cachedDownload( + wpDetails.releaseUrl, + `${wpDetails.version}.zip`, + monitor + ); + logger.debug( + `Resolved WordPress release URL: ${wpDetails?.releaseUrl}` + ); + } } let sqliteIntegrationPluginZip; @@ -116,7 +126,13 @@ export class BlueprintsV1Handler { sqliteIntegrationPluginZip = undefined; } else { this.cliOutput.updateProgress('Preparing SQLite database'); - sqliteIntegrationPluginZip = await fetchSqliteIntegration(); + // Use pre-patched v2.2.22 for legacy PHP (PHP 7+ syntax + // already removed offline, no runtime patching needed). + const phpVersion = this.args.php || RecommendedPHPVersion; + const isLegacyPhp = parseFloat(phpVersion) < 7; + sqliteIntegrationPluginZip = await fetchSqliteIntegration( + isLegacyPhp ? 'v2.2.22-php56' : 'trunk' + ); } this.cliOutput.updateProgress('Booting WordPress'); @@ -130,6 +146,7 @@ export class BlueprintsV1Handler { playground as unknown as PlaygroundCliBlueprintV1Worker ).bootWordPress( { + phpVersion: runtimeConfiguration.phpVersion, wpVersion: runtimeConfiguration.wpVersion, siteUrl: this.siteUrl, wordpressInstallMode: diff --git a/packages/playground/cli/src/blueprints-v1/download.ts b/packages/playground/cli/src/blueprints-v1/download.ts index 93c9f424dc5..eead8045492 100644 --- a/packages/playground/cli/src/blueprints-v1/download.ts +++ b/packages/playground/cli/src/blueprints-v1/download.ts @@ -6,7 +6,9 @@ import path, { basename } from 'path'; export const CACHE_FOLDER = path.join(os.homedir(), '.wordpress-playground'); -export async function fetchSqliteIntegration(): Promise { +export async function fetchSqliteIntegration( + version: 'trunk' | 'v2.1.16' | 'v2.2.22' | 'v2.2.22-php56' = 'trunk' +): Promise { // Production builds: the ZIP sits next to the bundled JS. const dir = typeof __dirname !== 'undefined' ? __dirname : import.meta.dirname; @@ -22,13 +24,31 @@ export async function fetchSqliteIntegration(): Promise { wpBuildsDir, 'src', 'sqlite-database-integration', - 'sqlite-database-integration-trunk.zip' + `sqlite-database-integration-${version}.zip` ); } return new File([await fs.readFile(zipPath)], path.basename(zipPath)); } +/** + * Returns the path to a pre-built WordPress ZIP if one exists for the + * given version slug (e.g. '4.9', '6.3'). Returns null if not found. + */ +export function getPrebuiltWordPressPath(versionSlug: string): string | null { + const require = createRequire(import.meta.url); + const wpBuildsDir = path.dirname( + require.resolve('@wp-playground/wordpress-builds/package.json') + ); + const zipPath = path.join( + wpBuildsDir, + 'src', + 'wordpress', + `wp-${versionSlug}.zip` + ); + return fs.existsSync(zipPath) ? zipPath : null; +} + // @TODO: Support HTTP cache, invalidate the local file if the remote file has // changed export async function cachedDownload( diff --git a/packages/playground/cli/src/blueprints-v1/worker-thread-v1.ts b/packages/playground/cli/src/blueprints-v1/worker-thread-v1.ts index c28cd4a159e..a5defa6d70b 100644 --- a/packages/playground/cli/src/blueprints-v1/worker-thread-v1.ts +++ b/packages/playground/cli/src/blueprints-v1/worker-thread-v1.ts @@ -1,7 +1,7 @@ import type { FileLockManager } from '@php-wasm/universal'; import { loadNodeRuntime } from '@php-wasm/node'; import { EmscriptenDownloadMonitor } from '@php-wasm/progress'; -import type { PathAlias, SupportedPHPVersion } from '@php-wasm/universal'; +import type { AllPHPVersion, PathAlias } from '@php-wasm/universal'; import { PHPWorker, releaseApiProxy, @@ -27,6 +27,7 @@ import type { Mount } from '@php-wasm/cli-util'; export type WorkerBootWordPressOptions = { siteUrl: string; + phpVersion?: string; wpVersion?: string; wordpressInstallMode: WordPressInstallMode; wordPressZip?: ArrayBuffer; @@ -40,7 +41,7 @@ export type WorkerBootWordPressOptions = { interface WorkerBootRequestHandlerOptions { siteUrl: string; - phpVersion: SupportedPHPVersion; + phpVersion: AllPHPVersion; processId: number; trace: boolean; nativeInternalDirPath: string; @@ -102,6 +103,7 @@ export class PlaygroundCliBlueprintV1Worker extends PHPWorker { this.bootedWordPress = true; const { siteUrl, + phpVersion, wordpressInstallMode, wordPressZip, sqliteIntegrationPluginZip, @@ -112,6 +114,7 @@ export class PlaygroundCliBlueprintV1Worker extends PHPWorker { try { await bootWordPress(this.__internal_getRequestHandler()!, { siteUrl, + phpVersion, wordpressInstallMode, wordPressZip: wordPressZip !== undefined @@ -161,6 +164,7 @@ export class PlaygroundCliBlueprintV1Worker extends PHPWorker { try { const requestHandler = await bootRequestHandler({ siteUrl: options.siteUrl, + phpVersion: options.phpVersion, maxPhpInstances: 1, createPhpRuntime: createPhpRuntimeFactory( options, diff --git a/packages/playground/cli/src/run-cli.ts b/packages/playground/cli/src/run-cli.ts index 57157b93c99..2924779999a 100644 --- a/packages/playground/cli/src/run-cli.ts +++ b/packages/playground/cli/src/run-cli.ts @@ -6,7 +6,7 @@ import { type PHPRequest, type PathAlias, type RemoteAPI, - type SupportedPHPVersion, + type AllPHPVersion, } from '@php-wasm/universal'; import { PHPResponse, @@ -46,6 +46,8 @@ import type { PlaygroundCliBlueprintV2Worker } from './blueprints-v2/worker-thre import type { XdebugOptions } from '@php-wasm/node'; /* eslint-disable no-console */ import { + AllPHPVersions, + LegacyPHPVersions, SupportedPHPVersions, FileLockManagerInMemory, } from '@php-wasm/universal'; @@ -117,7 +119,7 @@ export async function parseOptionsAndRunCLI(argsToParse: string[]) { describe: 'PHP version to use.', type: 'string', default: RecommendedPHPVersion, - choices: SupportedPHPVersions, + choices: AllPHPVersions, }, wp: { describe: 'WordPress version to use.', @@ -372,7 +374,7 @@ export async function parseOptionsAndRunCLI(argsToParse: string[]) { describe: 'PHP version to use.', type: 'string', default: RecommendedPHPVersion, - choices: SupportedPHPVersions, + choices: AllPHPVersions, }, wp: { describe: 'WordPress version to use.', @@ -810,7 +812,7 @@ export interface RunCLIArgs { mount?: Mount[]; 'mount-before-install'?: Mount[]; outfile?: string; - php?: SupportedPHPVersion; + php?: AllPHPVersion; port?: number; 'site-url'?: string; quiet?: boolean; @@ -989,6 +991,17 @@ export async function runCLI(args: RunCLIArgs): Promise { args.memcached = await jspi(); } + // Disable all extensions for legacy PHP versions — they're not available. + const isLegacyPhp = (LegacyPHPVersions as readonly string[]).includes( + args.php || RecommendedPHPVersion + ); + if (isLegacyPhp) { + args.intl = false; + args.redis = false; + args.memcached = false; + args.xdebug = false; + } + // Setup phpMyAdmin if enabled. if (args.phpmyadmin) { if (true === args.phpmyadmin) { @@ -1097,10 +1110,11 @@ export async function runCLI(args: RunCLIArgs): Promise { vfsPath: '/', }; + const phpVer = args.php || RecommendedPHPVersion; const isPHP85orHigher = - SupportedPHPVersions.indexOf( - args.php || RecommendedPHPVersion - ) <= SupportedPHPVersions.indexOf('8.5'); + SupportedPHPVersions.includes(phpVer as any) && + SupportedPHPVersions.indexOf(phpVer as any) <= + SupportedPHPVersions.indexOf('8.5'); // And, if PHP >= 8.5, add the new Xdebug config. if (isPHP85orHigher) { diff --git a/packages/playground/cli/tests/run-cli.spec.ts b/packages/playground/cli/tests/run-cli.spec.ts index bd354ab3773..bd272c01d36 100644 --- a/packages/playground/cli/tests/run-cli.spec.ts +++ b/packages/playground/cli/tests/run-cli.spec.ts @@ -171,10 +171,11 @@ describe.each(blueprintVersions)( test('should set WordPress version', async () => { const { MinifiedWordPressVersionsList } = await import('@wp-playground/wordpress-builds'); - const oldestSupportedVersion = - MinifiedWordPressVersionsList[ - MinifiedWordPressVersionsList.length - 1 - ]; + // Use the oldest non-legacy version. Legacy versions + // (< 5.0) require PHP 5.6 and can't boot on modern PHP. + const oldestSupportedVersion = MinifiedWordPressVersionsList.filter( + (v) => parseFloat(v) >= 5 + ).pop()!; await using cliServer = await runCLI({ ...suiteCliArgs, command: 'server', diff --git a/packages/playground/client/src/blueprints-v1-handler.ts b/packages/playground/client/src/blueprints-v1-handler.ts index 5b125b030db..e0818b9c798 100644 --- a/packages/playground/client/src/blueprints-v1-handler.ts +++ b/packages/playground/client/src/blueprints-v1-handler.ts @@ -87,10 +87,14 @@ export class BlueprintsV1Handler { /** * Pre-fetch WordPress update checks to speed up the initial wp-admin load. + * Skip for old WordPress versions — the functions called by prefetch + * (wp_check_php_version, wp_update_plugins, etc.) don't exist or crash + * on legacy WP, and the resulting PHP errors create noise. * * @see https://github.com/WordPress/wordpress-playground/pull/2295 */ - if (runtimeConfiguration.networking) { + const wpMajor = parseFloat(runtimeConfiguration.wpVersion) || 99; + if (runtimeConfiguration.networking && wpMajor >= 5) { await playground.prefetchUpdateChecks(); } diff --git a/packages/playground/common/src/index.ts b/packages/playground/common/src/index.ts index fb5455b998e..3e7b808f78e 100644 --- a/packages/playground/common/src/index.ts +++ b/packages/playground/common/src/index.ts @@ -18,6 +18,10 @@ export const RecommendedPHPVersion = '8.3'; /** * Unzip a zip file inside Playground. + * + * Uses PHP's ZipArchive when available. Falls back to a + * JavaScript-based implementation for legacy PHP builds + * that lack the zip extension. */ export const unzipFile = async ( php: UniversalPHP, @@ -25,26 +29,28 @@ export const unzipFile = async ( extractToPath: string, overwriteFiles = true ) => { - /** - * Use a random file name to avoid conflicts across concurrent unzipFile() - * calls. - */ + // Resolve File objects to bytes for the JS fallback path, + // and to a temp path for the PHP path. + let zipBytes: Uint8Array | undefined; const tmpPath = `/tmp/file-${Math.random()}.zip`; if (zipPath instanceof File) { - const zipFile = zipPath; + zipBytes = new Uint8Array(await zipPath.arrayBuffer()); zipPath = tmpPath; - await php.writeFile( - zipPath, - new Uint8Array(await zipFile.arrayBuffer()) - ); + await php.writeFile(zipPath, zipBytes); } - const js = phpVars({ - zipPath, - extractToPath, - overwriteFiles, - }); - await php.run({ - code: ` $data, 'url' => $url, 'method' => $options['type'], - 'blocking' => $options['blocking'] ?? true, + 'blocking' => isset($options['blocking']) ? $options['blocking'] : true, ] ) ); @@ -132,9 +132,14 @@ class Wp_Http_Fetch extends Wp_Http_Fetch_Base implements \WpOrg\Requests\Transp { } -} else { +} elseif (interface_exists('Requests_Transport')) { class Wp_Http_Fetch extends Wp_Http_Fetch_Base implements Requests_Transport { + } +} else { + class Wp_Http_Fetch extends Wp_Http_Fetch_Base + { + } } diff --git a/packages/playground/remote/src/lib/playground-worker-endpoint-blueprints-v1.ts b/packages/playground/remote/src/lib/playground-worker-endpoint-blueprints-v1.ts index 90530f21015..a7acf9a3445 100644 --- a/packages/playground/remote/src/lib/playground-worker-endpoint-blueprints-v1.ts +++ b/packages/playground/remote/src/lib/playground-worker-endpoint-blueprints-v1.ts @@ -12,6 +12,7 @@ import { LatestSqliteDriverVersion, MinifiedWordPressVersionsList, } from '@wp-playground/wordpress-builds'; +import { LegacyPHPVersions } from '@php-wasm/universal'; import { directoryHandleFromMountDevice } from '@wp-playground/storage'; import { bootWordPress } from '@wp-playground/wordpress'; import { createDirectoryHandleMountHandler } from '@php-wasm/web'; @@ -74,9 +75,10 @@ class PlaygroundWorkerEndpointBlueprintsV1 extends PlaygroundWorkerEndpoint { this.requestedWordPressVersion = wpVersion === 'nightly' ? 'trunk' : wpVersion; - wpVersion = MinifiedWordPressVersionsList.includes( + const isMinifiedVersion = MinifiedWordPressVersionsList.includes( this.requestedWordPressVersion - ) + ); + wpVersion = isMinifiedVersion ? this.requestedWordPressVersion : LatestMinifiedWordPressVersion; @@ -113,6 +115,26 @@ class PlaygroundWorkerEndpointBlueprintsV1 extends PlaygroundWorkerEndpoint { } ); }); + } else if (!isMinifiedVersion) { + // Non-minified version like "4.9" or "1.5": + // download directly from wordpress.org. + const normalizedVersion = normalizeWordPressVersion( + this.requestedWordPressVersion! + ); + const wpOrgUrl = `https://wordpress.org/wordpress-${normalizedVersion}.zip`; + const downloadUrl = corsProxyUrl + ? `${corsProxyUrl}${wpOrgUrl}` + : wpOrgUrl; + wordPressRequest = this.downloadMonitor + .monitorFetch(fetch(downloadUrl)) + .then((response) => { + if (!response.ok) { + throw new Error( + `Failed to download WordPress ${normalizedVersion} (HTTP ${response.status})` + ); + } + return response; + }); } else { const downloadUrl = maybeProxyUrl( wpDetails.url, @@ -127,8 +149,22 @@ class PlaygroundWorkerEndpointBlueprintsV1 extends PlaygroundWorkerEndpoint { } } + // Select the right SQLite version: + // - PHP 5.6: pre-patched v2.2.22 (AST driver, PHP 7+ removed) + // - Old WP (< 6.x) on modern PHP: v2.1.16 (old translator + // that works with WP 4.x's database schema) + // - Modern WP: trunk + const isLegacyPhp = ( + LegacyPHPVersions as readonly string[] + ).includes(phpVersion!); + const wpMajor = parseFloat(this.requestedWordPressVersion!) || 99; + const effectiveSqliteVersion = isLegacyPhp + ? 'v2.2.22-php56' + : wpMajor < 6 + ? 'v2.1.16' + : sqliteDriverVersion!; const sqliteDriverModuleDetails = getSqliteDriverModuleDetails( - sqliteDriverVersion! + effectiveSqliteVersion ); this.downloadMonitor.expectAssets({ [sqliteDriverModuleDetails.url]: sqliteDriverModuleDetails.size, @@ -139,9 +175,14 @@ class PlaygroundWorkerEndpointBlueprintsV1 extends PlaygroundWorkerEndpoint { await bootWordPress(requestHandler, { siteUrl, + phpVersion, constants: shouldInstallWordPress ? { - WP_DEBUG: true, + // Disable WP_DEBUG for legacy PHP (< 7) because + // old WordPress (< 3.1) doesn't have WP_DEBUG_DISPLAY + // and shows all notices when WP_DEBUG is true, + // breaking header output and install responses. + WP_DEBUG: !isLegacyPhp, WP_DEBUG_LOG: true, WP_DEBUG_DISPLAY: false, AUTH_KEY: randomString(40), @@ -212,6 +253,21 @@ const [setApiReady, setAPIError] = exposeAPI( new PlaygroundWorkerEndpointBlueprintsV1(downloadMonitor) ); +/** + * Normalizes WordPress version strings for wordpress.org downloads. + * Versions >= 2.0 work as `.` (wordpress.org redirects + * to the latest patch). Versions < 2.0 need explicit patch versions + * because wordpress.org doesn't host `wordpress-1.x.zip` files. + */ +function normalizeWordPressVersion(version: string): string { + const legacyVersionMap: Record = { + '1.0': '1.0.2', + '1.2': '1.2.2', + '1.5': '1.5.2', + }; + return legacyVersionMap[version] ?? version; +} + function maybeProxyUrl(url: string, corsProxyUrl?: string) { if ( !corsProxyUrl || diff --git a/packages/playground/remote/src/lib/playground-worker-endpoint.ts b/packages/playground/remote/src/lib/playground-worker-endpoint.ts index 6316c9e335f..e9a8716f678 100644 --- a/packages/playground/remote/src/lib/playground-worker-endpoint.ts +++ b/packages/playground/remote/src/lib/playground-worker-endpoint.ts @@ -25,8 +25,9 @@ import transportFetch from './playground-mu-plugin/playground-includes/wp_http_f /* @ts-ignore */ import transportDummy from './playground-mu-plugin/playground-includes/wp_http_dummy.php?raw'; import { logger } from '@php-wasm/logger'; -import type { PathAlias, PHP, SupportedPHPVersion } from '@php-wasm/universal'; +import type { AllPHPVersion, PathAlias, PHP } from '@php-wasm/universal'; import { + LegacyPHPVersions, PHPResponse, PHPWorker, isPathToSharedFS, @@ -56,7 +57,7 @@ export interface MountDescriptor { export type WorkerBootOptions = { wpVersion?: string; sqliteDriverVersion?: string; - phpVersion?: SupportedPHPVersion; + phpVersion?: AllPHPVersion; sapiName?: string; scope: string; withIntl: boolean; @@ -140,7 +141,7 @@ export abstract class PlaygroundWorkerEndpoint extends PHPWorker { knownRemoteAssetPaths: Set; withIntl: boolean; withNetworking: boolean; - phpVersion: SupportedPHPVersion; + phpVersion: AllPHPVersion; pathAliases?: PathAlias[]; }) { const phpIniEntries: Record = { @@ -189,8 +190,18 @@ export abstract class PlaygroundWorkerEndpoint extends PHPWorker { } const parsedSiteUrl = new URL(siteUrl); + // Legacy PHP 5.6 has a parser state bug that corrupts large + // include chains when a secondary PHP instance accesses the + // WordPress source via PROXYFS. Force single-instance mode so + // all requests run on the primary and the PROXYFS bug never + // surfaces. The SinglePHPInstanceManager serializes concurrent + // requests on the primary via a 1-concurrency semaphore. + const isLegacyPhp = (LegacyPHPVersions as readonly string[]).includes( + phpVersion + ); const requestHandler = await bootRequestHandler({ siteUrl, + phpVersion, createPhpRuntime: async () => { let wasmUrl = ''; return await loadWebRuntime(phpVersion, { @@ -238,7 +249,18 @@ export abstract class PlaygroundWorkerEndpoint extends PHPWorker { await proxyFileSystem( await requestHandler.getPrimaryPhp(), php, - pathsToProxy + pathsToProxy, + // PHP 5.6's zend_compile_file uses mmap() to read + // source files. PROXYFS mmap trusts fstat for the + // buffer size, but fstat can return stale sizes when + // the primary has rewritten files after the secondary + // was created. This causes the parser to read past + // the real EOF and report spurious syntax errors. + // Disabling mmap makes PHP fall back to a read-based + // path that handles size mismatches correctly. + // PHP 7+ removed the mmap path from zend_stream_fixup + // entirely, so this only matters for legacy PHP. + { withMmap: !isLegacyPhp } ); } if (withNetworking) { @@ -443,8 +465,14 @@ export abstract class PlaygroundWorkerEndpoint extends PHPWorker { * improve the first wp-admin load time. */ async prefetchUpdateChecks() { - const primaryPhp = this.__internal_getPHP()!; - await this.networkTransport!.prefetchUpdateChecks(primaryPhp); + try { + const primaryPhp = this.__internal_getPHP()!; + await this.networkTransport!.prefetchUpdateChecks(primaryPhp); + } catch (e) { + // Non-fatal: prefetching is a performance optimization. + // Old WordPress versions may crash here. + logger.warn('prefetchUpdateChecks failed:', e); + } } // These methods are only here for the time traveling Playground demo. diff --git a/packages/playground/wordpress-builds/src/sqlite-database-integration/get-sqlite-driver-module-details.ts b/packages/playground/wordpress-builds/src/sqlite-database-integration/get-sqlite-driver-module-details.ts index be4f2e00fd0..c1c4e3a83fd 100644 --- a/packages/playground/wordpress-builds/src/sqlite-database-integration/get-sqlite-driver-module-details.ts +++ b/packages/playground/wordpress-builds/src/sqlite-database-integration/get-sqlite-driver-module-details.ts @@ -2,6 +2,10 @@ import url_trunk from './sqlite-database-integration-trunk.zip?url'; // @ts-ignore import url_v2_1_16 from './sqlite-database-integration-v2.1.16.zip?url'; +// @ts-ignore +import url_v2_2_22 from './sqlite-database-integration-v2.2.22.zip?url'; +// @ts-ignore +import url_v2_2_22_php56 from './sqlite-database-integration-v2.2.22-php56.zip?url'; /** * This file was auto generated by: @@ -32,6 +36,18 @@ export function getSqliteDriverModuleDetails( size: 84250, url: url_v2_1_16, }; + case 'v2.2.22': + /** @ts-ignore */ + return { + size: 263816, + url: url_v2_2_22, + }; + case 'v2.2.22-php56': + /** @ts-ignore */ + return { + size: 263749, + url: url_v2_2_22_php56, + }; } throw new Error( 'Unsupported SQLite integration plugin version: ' + version diff --git a/packages/playground/wordpress-builds/src/sqlite-database-integration/sqlite-database-integration-v2.2.22-php56.zip b/packages/playground/wordpress-builds/src/sqlite-database-integration/sqlite-database-integration-v2.2.22-php56.zip new file mode 100644 index 00000000000..fded7f07be5 Binary files /dev/null and b/packages/playground/wordpress-builds/src/sqlite-database-integration/sqlite-database-integration-v2.2.22-php56.zip differ diff --git a/packages/playground/wordpress-builds/src/sqlite-database-integration/sqlite-database-integration-v2.2.22.zip b/packages/playground/wordpress-builds/src/sqlite-database-integration/sqlite-database-integration-v2.2.22.zip new file mode 100644 index 00000000000..c43197580dc Binary files /dev/null and b/packages/playground/wordpress-builds/src/sqlite-database-integration/sqlite-database-integration-v2.2.22.zip differ diff --git a/packages/playground/wordpress/project.json b/packages/playground/wordpress/project.json index d6d4008f66d..dbf4ad91802 100644 --- a/packages/playground/wordpress/project.json +++ b/packages/playground/wordpress/project.json @@ -71,6 +71,14 @@ "reportsDirectory": "../../../coverage/packages/playground/wordpress" } }, + "test-legacy-wp-version-boot": { + "executor": "nx:run-commands", + "options": { + "commands": [ + "node packages/playground/wordpress/tests/test-legacy-wp-version-boot.mjs" + ] + } + }, "lint": { "executor": "@nx/eslint:lint", "outputs": ["{options.outputFile}"], diff --git a/packages/playground/wordpress/src/boot.ts b/packages/playground/wordpress/src/boot.ts index 6fd0d5ef8ac..13bfc3c8b2f 100644 --- a/packages/playground/wordpress/src/boot.ts +++ b/packages/playground/wordpress/src/boot.ts @@ -26,6 +26,12 @@ import { import { basename, dirname, joinPaths } from '@php-wasm/util'; import { logger } from '@php-wasm/logger'; import { ensureWpConfig } from './wp-config'; +import { + generateDbPhpContent, + patchWordPressSourceFiles, + preCreateLegacyTables, + runPostInstallLegacyFixups, +} from './legacy-wp-fixes'; export type PhpIniOptions = Record; export type Hook = (php: PHP) => void | Promise; @@ -51,6 +57,8 @@ export async function bootWordPressAndRequestHandler( export interface BootRequestHandlerOptions { createPhpRuntime: (isPrimary?: boolean) => Promise; + /** PHP version string (e.g. '8.3', '5.6'). */ + phpVersion?: string; onPHPInstanceCreated?: PHPInstanceCreatedHook; maxPhpInstances?: number; /** @@ -141,6 +149,8 @@ export type WordPressInstallMode = | 'do-not-attempt-installing'; export interface BootWordPressOptions { + /** PHP version string (e.g. '8.3', '5.6'). */ + phpVersion?: string; /** * Mounting and Copying is handled via hooks for starters. * @@ -237,7 +247,43 @@ export async function bootWordPress( * them. This is needed because some WordPress backups and exports may not * include definitions for some of the necessary constants. */ - await ensureWpConfig(php, requestHandler.documentRoot); + const phpMajor = parseInt(options.phpVersion ?? '8', 10); + if (phpMajor >= 7) { + await ensureWpConfig(php, requestHandler.documentRoot); + } else { + // For legacy PHP, skip ensureWpConfig since the pre-built + // WordPress already has a valid wp-config-sample.php and + // php.run() with the large transformer code hangs. + // Just copy wp-config-sample.php to wp-config.php if needed. + const wpConfigPath = joinPaths( + requestHandler.documentRoot, + 'wp-config.php' + ); + if ( + !php.fileExists(wpConfigPath) && + php.fileExists( + joinPaths(requestHandler.documentRoot, 'wp-config-sample.php') + ) + ) { + await php.writeFile( + wpConfigPath, + await php.readFileAsBuffer( + joinPaths( + requestHandler.documentRoot, + 'wp-config-sample.php' + ) + ) + ); + } + } + if (phpMajor < 7) { + await patchWordPressSourceFiles( + php, + requestHandler.documentRoot, + phpMajor + ); + } + // Run "before database" hooks to mount/copy more files in if (options.hooks?.beforeDatabaseSetup) { await options.hooks.beforeDatabaseSetup(php); @@ -250,8 +296,35 @@ export async function bootWordPress( usesSqlite = true; await preloadSqliteIntegration( php, - await options.sqliteIntegrationPluginZip + await options.sqliteIntegrationPluginZip, + { phpVersion: options.phpVersion } ); + + // Write wp-content/db.php with MySQL function stubs for + // legacy WordPress. WP 4.x checks extension_loaded('mysql') + // and only skips that check if wp-content/db.php exists. + // patchWpSettingsPhp() patches that check away, but only + // runs for legacy PHP. Modern WP doesn't have this check. + if (phpMajor < 7) { + const wpContentDir = joinPaths( + requestHandler.documentRoot, + 'wp-content' + ); + const dbPhpPath = joinPaths(wpContentDir, 'db.php'); + if (php.isDir(wpContentDir) && !php.fileExists(dbPhpPath)) { + await php.writeFile(dbPhpPath, generateDbPhpContent()); + } + } + } + + // Pre-create WP 1.x tables with correct schemas before the + // installer runs (its mysql_* calls can't work through SQLite). + if (phpMajor < 7 && options.sqliteIntegrationPluginZip) { + try { + await preCreateLegacyTables(php); + } catch (error) { + logger.warn('Legacy table pre-creation failed (non-fatal):', error); + } } const installationMode = @@ -269,20 +342,18 @@ export async function bootWordPress( hasCustomDatabasePath, }); // Install WordPress if it's not installed. - try { - await installWordPress(php); - } catch (error) { - // If installation failed, check if it's a database issue - // to provide a more specific error message (but skip if user provided custom DB path) - if (!hasCustomDatabasePath) { - await assertValidDatabaseConnection(requestHandler); - } - // If we get here, the database is valid but installation failed for another reason - throw error; - } - // Validate the database connection after installation (skip if user provided custom DB path) + await installWordPressSafe( + php, + phpMajor, + hasCustomDatabasePath, + requestHandler, + options.phpVersion + ); if (!hasCustomDatabasePath) { - await assertValidDatabaseConnection(requestHandler); + await assertValidDatabaseConnectionSafe( + requestHandler, + options.phpVersion + ); } } else if ('install-from-existing-files-if-needed' === installationMode) { // Check database prerequisites before attempting installation @@ -290,29 +361,59 @@ export async function bootWordPress( usesSqlite, hasCustomDatabasePath, }); - if (!(await isWordPressInstalled(php))) { - // Install WordPress if it's not installed. - try { - await installWordPress(php); - } catch (error) { - // If installation failed, check if it's a database issue - // to provide a more specific error message (but skip if user provided custom DB path) - if (!hasCustomDatabasePath) { - await assertValidDatabaseConnection(requestHandler); - } - // If we get here, the database is valid but installation failed for another reason - throw error; - } + // For legacy PHP (< 7), skip isWordPressInstalled check because + // it crashes the WASM runtime on old WordPress (< 3.0) where the + // SQLite driver initialization chain isn't fully compatible. + const isInstalled = + phpMajor >= 7 ? await isWordPressInstalled(php) : false; + if (!isInstalled) { + await installWordPressSafe( + php, + phpMajor, + hasCustomDatabasePath, + requestHandler, + options.phpVersion + ); } - // Validate the database connection after installation (skip if user provided custom DB path) + // Validate the database connection after installation if (!hasCustomDatabasePath) { - await assertValidDatabaseConnection(requestHandler); + await assertValidDatabaseConnectionSafe( + requestHandler, + options.phpVersion + ); } } return requestHandler; } +/** + * Wrapper around installWordPress that handles errors gracefully + * for legacy PHP versions where installation errors may be non-fatal. + */ +async function installWordPressSafe( + php: PHP, + phpMajor: number, + hasCustomDatabasePath: boolean, + requestHandler: PHPRequestHandler, + phpVersion?: string +): Promise { + try { + await installWordPress(php, phpMajor); + } catch (error) { + if (!hasCustomDatabasePath) { + await assertValidDatabaseConnectionSafe(requestHandler, phpVersion); + } + if (phpMajor >= 7) { + throw error; + } + logger.warn('Legacy PHP WordPress installation error:', error); + } + if (phpMajor < 7) { + await runPostInstallLegacyFixups(php); + } +} + /** * Checks if database prerequisites are in place before attempting WordPress installation. * This performs lightweight checks that don't require WordPress to be installed. @@ -368,6 +469,30 @@ async function assertDatabasePrerequisites( throw new Error('Error connecting to the MySQL database.'); } +/** + * For legacy PHP (< 7), the database validation can fail due to + * compatibility quirks even when the site works fine. In that + * case, log a warning instead of crashing the boot. + */ +async function assertValidDatabaseConnectionSafe( + requestHandler: PHPRequestHandler, + phpVersion?: string +) { + const phpMajor = parseInt(phpVersion ?? '8', 10); + if (phpMajor < 7) { + try { + await assertValidDatabaseConnection(requestHandler); + } catch (e) { + logger.warn( + 'Legacy PHP database validation failed (non-fatal):', + e + ); + } + return; + } + await assertValidDatabaseConnection(requestHandler); +} + async function assertValidDatabaseConnection( requestHandler: PHPRequestHandler ) { @@ -420,9 +545,14 @@ export async function bootRequestHandler(options: BootRequestHandlerOptions) { setPhpIniEntries(php, options.phpIniEntries); } - // Use the new AST-based SQLite driver. + // Use the new AST-based SQLite driver for PHP 7+. + // PHP 5.x can't use the AST driver (it requires PHP 7.2+). // TODO: Remove this once the new driver is the default; when this is closed: // https://github.com/WordPress/sqlite-database-integration/issues/195 + // Enable the AST-based SQLite driver for PHP 7+ and for + // PHP 5.6 (which uses the pre-patched v2.2.22-php56 + // that has the AST driver patched for PHP 5.6). + // The v2.1.16 old translator ignores this constant. php.defineConstant('WP_SQLITE_AST_DRIVER', true); // Define any custom constants provided via CLI or configuration @@ -457,7 +587,9 @@ export async function bootRequestHandler(options: BootRequestHandlerOptions) { !php.isFile('/internal/.boot-files-written') ) { // TODO: There is a race here when multiple workers are calling bootRequestHandler(). Fix it. - await setupPlatformLevelMuPlugins(php); + await setupPlatformLevelMuPlugins(php, { + phpVersion: options.phpVersion, + }); await writeFiles(php, '/', options.createFiles || {}); await preloadPhpInfoRoute( php, @@ -567,13 +699,22 @@ export async function isWordPressInstalled(php: PHP) { * Without them, the installer may take 60 seconds, * 300 seconds, or even more to complete. */ -async function installWordPress(php: PHP) { +async function installWordPress(php: PHP, phpMajor = 8) { + const iniOverrides: Record = { + disable_functions: 'fsockopen', + allow_url_fopen: '0', + }; + if (phpMajor < 7) { + // Suppress E_DEPRECATED (8192) and E_STRICT (2048) at + // the ini level. Old WordPress class declarations trigger + // E_STRICT warnings during compilation (e.g. Walker_Page) + // which PHP may report using the ini error_reporting value + // rather than the runtime error_reporting() call. + iniOverrides['error_reporting'] = String(0x7fff & ~8192 & ~2048); + } const response = await withPHPIniValues( php, - { - disable_functions: 'fsockopen', - allow_url_fopen: '0', - }, + iniOverrides, async () => await php.request({ url: '/wp-admin/install.php?step=2', @@ -593,7 +734,30 @@ async function installWordPress(php: PHP) { }) ); - if (!(await isWordPressInstalled(php))) { + if (phpMajor < 7) { + // Legacy PHP (< 7): isWordPressInstalled() crashes the WASM + // runtime on old WordPress (< 3.0). Use response text heuristic. + let installSucceeded: boolean; + try { + installSucceeded = await isWordPressInstalled(php); + } catch { + installSucceeded = + response.text?.includes('Success') || + response.text?.includes('successful') || + response.text?.includes('Finished') || + response.text?.includes('Already Installed') || + response.text?.includes('already have WordPress installed') || + false; + } + if (!installSucceeded) { + throw new Error( + `Failed to install WordPress – installer responded with "${response.text?.substring( + 0, + 100 + )}"` + ); + } + } else if (!(await isWordPressInstalled(php))) { throw new Error( `Failed to install WordPress – installer responded with "${response.text?.substring( 0, @@ -602,35 +766,84 @@ async function installWordPress(php: PHP) { ); } - const defaultedToPrettyPermalinks = await php.run({ - code: `setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $nice_permalinks = '/%year%/%monthnum%/%day%/%postname%/'; + $stmt = $pdo->prepare( + "UPDATE wp_options SET option_value = :val WHERE option_name = 'permalink_structure'" + ); + $stmt->execute(array(':val' => $nice_permalinks)); + if ($stmt->rowCount() === 0) { + $stmt = $pdo->prepare( + "INSERT INTO wp_options (option_name, option_value, autoload) VALUES ('permalink_structure', :val, 'yes')" + ); + $stmt->execute(array(':val' => $nice_permalinks)); + } + $check = $pdo->query( + "SELECT option_value FROM wp_options WHERE option_name = 'permalink_structure'" + )->fetchColumn(); + echo $check === $nice_permalinks ? '1' : '0'; + `, + env: { DOCUMENT_ROOT: php.documentRoot }, + }); + if (result.text !== '1') { + logger.warn( + 'Failed to default to pretty permalinks after WP install.' + ); } - require $wp_load; - $nice_permalinks = '/%year%/%monthnum%/%day%/%postname%/'; - $option_result = update_option( - 'permalink_structure', - $nice_permalinks + } catch { + logger.warn( + 'Failed to set pretty permalinks after WP install (non-fatal).' ); - ob_clean(); - if ( get_option( 'permalink_structure' ) === $nice_permalinks ) { - echo '1'; - } else { - echo '0'; - } - ob_end_flush(); - `, - env: { - DOCUMENT_ROOT: php.documentRoot, - }, - }); + } + } else { + const defaultedToPrettyPermalinks = await php.run({ + code: `ID, $user->user_login ); + wp_set_auth_cookie( $user->ID ); + do_action( 'wp_login', $user->user_login, $user ); + setcookie('playground_auto_login_already_happened', '1'); + if (headers_sent()) { + _doing_it_wrong('playground_auto_login', 'Headers already sent, the Playground runtime will not auto-login the user', '1.0.0'); + return; + } + $redirect_url = $_SERVER['REQUEST_URI']; + header( "Location: $redirect_url", true, 302 ); + exit; +`; + +/** + * Auto-login body for legacy WordPress (1.0-2.5). + * + * Handles three auth eras: + * - WP 2.5+: wp_set_current_user() + wp_set_auth_cookie() (HMAC cookies) + * - WP 1.5-2.4: USER_COOKIE/PASS_COOKIE constants + wp_setcookie() + * - WP 1.0-1.2: wordpressuser_/wordpresspass_ cookies + global vars + * + * Each era uses different cookie names and hashing. The code detects + * which API is available and uses the appropriate method. + */ +const LEGACY_AUTO_LOGIN_BODY = ` + // WP 2.5+: modern auth API + if (function_exists('is_user_logged_in') && is_user_logged_in()) { + return; + } + if (headers_sent()) { + return; + } + $_pg_skip_redirect = defined('PLAYGROUND_SKIP_AUTO_LOGIN_REDIRECT') + && PLAYGROUND_SKIP_AUTO_LOGIN_REDIRECT; + + // WP 2.5+: use the standard auth API + if (function_exists('wp_set_current_user') && function_exists('wp_set_auth_cookie')) { + $user = function_exists('get_user_by') + ? get_user_by('login', $user_name) + : (function_exists('get_userdatabylogin') + ? get_userdatabylogin($user_name) : null); + if (!$user) return; + + wp_set_current_user($user->ID, $user->user_login); + if ($_pg_skip_redirect) { + // Populate $_COOKIE in-process so auth_redirect() + // and other cookie checks work without sending + // Set-Cookie headers or redirecting. + if (function_exists('wp_generate_auth_cookie')) { + $_pg_exp = time() + 172800; + if (defined('AUTH_COOKIE')) + $_COOKIE[AUTH_COOKIE] = wp_generate_auth_cookie($user->ID, $_pg_exp, 'auth'); + if (defined('SECURE_AUTH_COOKIE')) + $_COOKIE[SECURE_AUTH_COOKIE] = wp_generate_auth_cookie($user->ID, $_pg_exp, 'secure_auth'); + if (defined('LOGGED_IN_COOKIE')) + $_COOKIE[LOGGED_IN_COOKIE] = wp_generate_auth_cookie($user->ID, $_pg_exp, 'logged_in'); + } + } else { + wp_set_auth_cookie($user->ID); + if (function_exists('do_action')) { + do_action('wp_login', $user->user_login, $user); + } + setcookie('playground_auto_login_already_happened', '1'); + if (!headers_sent()) { + header("Location: " . $_SERVER['REQUEST_URI'], true, 302); + exit; + } + } + return; + } + + // WP 1.5-2.4: USER_COOKIE/PASS_COOKIE with double-md5 + if (defined('USER_COOKIE') && defined('PASS_COOKIE')) { + $_COOKIE[USER_COOKIE] = $user_name; + $_COOKIE[PASS_COOKIE] = md5(md5('password')); + if (!$_pg_skip_redirect) { + if (function_exists('wp_setcookie') && !headers_sent()) { + wp_setcookie($user_name, 'password'); + } + } + // Reset cached anonymous user so capability checks work + $GLOBALS['current_user'] = null; + if (function_exists('get_currentuserinfo')) { + get_currentuserinfo(); + } + if (!$_pg_skip_redirect) { + setcookie('playground_auto_login_already_happened', '1'); + if (!headers_sent()) { + header("Location: " . $_SERVER['REQUEST_URI'], true, 302); + exit; + } + } + return; + } + + // WP 1.0-1.2: wordpressuser_/wordpresspass_ cookies + // and global user variables instead of WP_User objects. + $cookiehash = defined('COOKIEHASH') + ? COOKIEHASH + : (isset($GLOBALS['cookiehash']) && $GLOBALS['cookiehash'] + ? $GLOBALS['cookiehash'] + : (function_exists('get_settings') + ? md5(get_settings('siteurl')) + : '')); + if ($cookiehash) { + $_COOKIE['wordpressuser_' . $cookiehash] = $user_name; + $_COOKIE['wordpresspass_' . $cookiehash] = md5(md5('password')); + // Populate global user variables that WP 1.0-1.2 uses + // instead of a WP_User object. + if (function_exists('get_userdatabylogin')) { + $userdata = get_userdatabylogin($user_name); + if ($userdata) { + $GLOBALS['user_login'] = $user_name; + $GLOBALS['userdata'] = $userdata; + $GLOBALS['user_level'] = isset($userdata->user_level) ? (int) $userdata->user_level : 10; + $GLOBALS['user_ID'] = $userdata->ID; + $GLOBALS['user_email'] = isset($userdata->user_email) ? $userdata->user_email : ''; + $GLOBALS['user_url'] = isset($userdata->user_url) ? $userdata->user_url : ''; + $GLOBALS['user_nickname'] = isset($userdata->user_nickname) ? $userdata->user_nickname : $user_name; + $GLOBALS['user_pass_md5'] = md5(isset($userdata->user_pass) ? $userdata->user_pass : ''); + } + } + if (!$_pg_skip_redirect) { + setcookie('playground_auto_login_already_happened', '1'); + if (!headers_sent()) { + header("Location: " . $_SERVER['REQUEST_URI'], true, 302); + exit; + } + } + return; + } +`; + /** * Preloads the platform mu-plugins from /internal/shared/mu-plugins. * This avoids polluting the WordPress installation with mu-plugins @@ -27,11 +180,220 @@ export * from './rewrite-rules'; * * @param php */ -export async function setupPlatformLevelMuPlugins(php: UniversalPHP) { +export async function setupPlatformLevelMuPlugins( + php: UniversalPHP, + options: { phpVersion?: string } = {} +) { + const phpMajor = parseInt(options.phpVersion ?? '8', 10); await php.mkdir('/internal/shared/mu-plugins'); + + if (phpMajor < 7) { + // Overwrite auto_prepend_file.php to add PHP 4 superglobal + // polyfills that WP 1.0-2.5 needs. The default + // auto_prepend_file only loads consts and preload files; + // legacy PHP also needs the superglobals set up first. + await php.writeFile( + '/internal/shared/auto_prepend_file.php', + ` $value) { + if (!defined($const) && is_scalar($value)) { + define($const, $value); + } + } + } +} +foreach (glob('/internal/shared/preload/*.php') as $file) { + require_once $file; +} +` + ); + } + await php.writeFile( '/internal/shared/preload/env.php', - `...,'accepted_args'=>N). +// Returns 'wp10', 'wp12', or 'wp15'. +function _playground_detect_wp_hook_format() { + static $format = null; + if ($format !== null) return $format; + $doc_root = isset($_SERVER['DOCUMENT_ROOT']) + ? $_SERVER['DOCUMENT_ROOT'] : '/wordpress'; + $version_path = $doc_root . '/wp-includes/version.php'; + $wp_version = '1.0'; + if (file_exists($version_path)) { + include $version_path; + } + if (version_compare($wp_version, '1.5', '>=')) { + $format = 'wp15'; + } elseif (version_compare($wp_version, '1.2', '>=')) { + $format = 'wp12'; + } else { + $format = 'wp10'; + } + return $format; +} + +// Allow adding filters/actions prior to loading WordPress. +// $function_to_add MUST be a string. +// Stores the callback in the $wp_filter format that the target +// WordPress version's apply_filters() expects. +function playground_add_filter( $tag, $function_to_add, $priority = 10, $accepted_args = 1 ) { + global $wp_filter; + $fmt = _playground_detect_wp_hook_format(); + if ($fmt === 'wp10') { + $wp_filter[$tag][] = $function_to_add; + } elseif ($fmt === 'wp12') { + $wp_filter[$tag][$priority][] = $function_to_add; + } else { + $wp_filter[$tag][$priority][$function_to_add] = array( + 'function' => $function_to_add, + 'accepted_args' => $accepted_args + ); + } +} +function playground_add_action( $tag, $function_to_add, $priority = 10, $accepted_args = 1 ) { + playground_add_filter( $tag, $function_to_add, $priority, $accepted_args ); +} + +// Fix date function comparisons for the SQLite driver. +// Old WordPress (< 4.0) generates date queries like: +// YEAR(post_date)='2026' AND MONTH(post_date)='4' +// using string literals. The SQLite driver's user-defined +// YEAR/MONTH/DAYOFMONTH/DAY functions return integers, and +// SQLite does not coerce types the way MySQL does (integer +// 4 != text '4' in SQLite). This filter strips quotes around +// numeric values in these comparisons so both sides are integers. +function playground_fix_sqlite_date_comparisons($query) { + if ( + stripos($query, 'YEAR') === false && + stripos($query, 'MONTH') === false && + stripos($query, 'DAY') === false + ) { + return $query; + } + return preg_replace( + '/\\b(YEAR|MONTH|DAYOFMONTH|DAY)\\s*\\(([^)]+)\\)\\s*=\\s*\\'(\\d+)\\'/i', + '$1($2) = $3', + $query + ); +} +playground_add_filter( 'query', 'playground_fix_sqlite_date_comparisons' ); + +// WP < 2.2 doesn't natively override get_option('siteurl') / +// get_option('home') with the WP_SITEURL / WP_HOME constants. +// Modern WP (2.2+) checks these constants in get_option() and +// returns the constant value, bypassing the DB. For WP 1.0-2.1, +// we replicate this behavior via option_siteurl / option_home +// filters so that admin navigation links use the correct +// Playground scoped URL instead of whatever the DB stores. +function playground_override_siteurl($value) { + if (defined('WP_SITEURL')) { + return WP_SITEURL; + } + return $value; +} +function playground_override_home($value) { + if (defined('WP_HOME')) { + return WP_HOME; + } + return $value; +} +playground_add_filter( 'option_siteurl', 'playground_override_siteurl' ); +playground_add_filter( 'option_home', 'playground_override_home' ); + +// Load our mu-plugins after customer mu-plugins. +// NOTE: this means our mu-plugins can't use the muplugins_loaded action! +playground_add_action( 'muplugins_loaded', 'playground_load_mu_plugins', 0 ); +// WP < 2.8 doesn't fire muplugins_loaded, so also hook into init +// as a fallback. The $loaded flag ensures mu-plugins load only once. +playground_add_action( 'init', 'playground_load_mu_plugins', -1000 ); +function playground_load_mu_plugins() { + static $loaded = false; + if ($loaded) return; + $loaded = true; + // Load all PHP files from /internal/shared/mu-plugins sorted by filename + $mu_plugins_dir = '/internal/shared/mu-plugins'; + if(!is_dir($mu_plugins_dir)){ + return; + } + $mu_plugins = glob( $mu_plugins_dir . '/*.php' ); + sort( $mu_plugins ); + global $wp_version; + $is_legacy_wp = isset($wp_version) && version_compare($wp_version, '2.8', '<'); + foreach ( $mu_plugins as $mu_plugin ) { + // sqlite-database-integration.php is loaded separately + // by the preload lazy loader or db.php. + if (strpos($mu_plugin, 'sqlite-database-integration') !== false) { + continue; + } + // Most mu-plugins use closures in add_action/add_filter + // or call functions like site_url() that don't exist in + // very old WordPress. WP < 2.8 crashes on closures in + // hooks; WP < 2.6 lacks site_url(). Only load mu-plugins + // that are explicitly written for legacy WP compatibility. + if ($is_legacy_wp) { + // 1-auto-login.php uses LEGACY_AUTO_LOGIN_BODY which + // handles WP 1.0-2.5 auth APIs with named functions + // only (no closures, no site_url()). + if (strpos($mu_plugin, '1-auto-login.php') === false) { + continue; + } + } + require_once $mu_plugin; + } + // On WP < 2.8, this function runs during init (priority + // -1000). PHP 5.6's foreach iterates over a copy of the + // array, so add_action() calls inside the loaded mu-plugin + // (e.g. add_action('init', 'playground_auto_login', 1)) + // won't fire — the init hook list was already snapshotted. + // Call the functions directly as a workaround. + // + // PLAYGROUND_SKIP_AUTO_LOGIN_REDIRECT tells the auto-login + // function to set cookies in-process without redirecting. + // In Playground's service worker, a redirect+Set-Cookie + // can cause a loop because the cookie isn't applied before + // the redirected request fires. Since the legacy auto-login + // already populates $_COOKIE in-process, the redirect is + // unnecessary — the current request will see the cookies. + if ($is_legacy_wp) { + if (function_exists('playground_auto_login_redirect_target')) { + playground_auto_login_redirect_target(); + } + if (function_exists('playground_auto_login')) { + define('PLAYGROUND_SKIP_AUTO_LOGIN_REDIRECT', true); + playground_auto_login(); + } + } +} +` + : `ID, $user->user_login ); - wp_set_auth_cookie( $user->ID ); - do_action( 'wp_login', $user->user_login, $user ); - - setcookie('playground_auto_login_already_happened', '1'); - - /** - * Confirm that nothing in WordPress, plugins, or filters have finalized - * the headers sending phase. See the comment above for more context. - */ - if (headers_sent()) { - _doing_it_wrong('playground_auto_login', 'Headers already sent, the Playground runtime will not auto-login the user', '1.0.0'); - return; - } - - /** - * Reload page to ensure the user is logged in correctly. - * WordPress uses cookies to determine if the user is logged in, - * so we need to reload the page to ensure the cookies are set. - */ - $redirect_url = $_SERVER['REQUEST_URI']; - - /** - * Intentionally do not use wp_redirect() here. It removes - * %0A and %0D sequences from the URL, which we don't want. - * There are valid use-cases for encoded newlines in the query string, - * for example html-api-debugger accepts markup with newlines - * encoded as %0A via the query string. - */ - header( "Location: $redirect_url", true, 302 ); - exit; + ${phpMajor < 7 ? LEGACY_AUTO_LOGIN_BODY : MODERN_AUTO_LOGIN_BODY} } /** * Autologin users from the wp-login.php page. @@ -249,12 +550,25 @@ export async function setupPlatformLevelMuPlugins(php: UniversalPHP) { * Disable the Site Admin Email Verification Screen for any session started * via autologin. */ - add_filter('admin_email_check_interval', function($interval) { + ${ + phpMajor < 7 + ? `if (function_exists('add_filter')) { + add_filter('admin_email_check_interval', 'playground_disable_admin_email_check'); + } + function playground_disable_admin_email_check($interval) { if(false === playground_get_username_for_auto_login()) { return 0; } return $interval; - }); + }` + : `function playground_disable_admin_email_check($interval) { + if(false === playground_get_username_for_auto_login()) { + return 0; + } + return $interval; + } + add_filter('admin_email_check_interval', 'playground_disable_admin_email_check');` + } ` ); @@ -263,7 +577,7 @@ export async function setupPlatformLevelMuPlugins(php: UniversalPHP) { ` 'sqlite', @@ -284,17 +598,19 @@ export async function setupPlatformLevelMuPlugins(php: UniversalPHP) { if (!file_exists($wp_env_file) || file_get_contents($wp_env_file) !== $wp_env_php ) { file_put_contents($wp_env_file, $wp_env_php); } - }); + } + add_action('wp_loaded', 'playground_save_wp_env_info'); // Needed because gethostbyname( 'wordpress.org' ) returns // a private network IP address for some reason. - add_filter( 'allowed_redirect_hosts', function( $deprecated = '' ) { + function playground_allowed_redirect_hosts( $deprecated = '' ) { return array( 'wordpress.org', 'api.wordpress.org', 'downloads.wordpress.org', ); - } ); + } + add_filter( 'allowed_redirect_hosts', 'playground_allowed_redirect_hosts' ); /** * Prevents wp_http_validate_url() from universally failing. @@ -319,6 +635,48 @@ export async function setupPlatformLevelMuPlugins(php: UniversalPHP) { // Support pretty permalinks add_filter( 'got_url_rewrite', '__return_true' ); + /** + * Flush rewrite rules on the first real WordPress request. + * + * During boot, we set permalink_structure in the database + * but can't flush rewrite rules at that point because WordPress + * isn't fully bootstrapped — post types and taxonomies haven't + * been registered yet, so the generated rules are incomplete. + * + * This hook fires on 'init' at a very late priority, after all + * post types and taxonomies are registered. It checks if the + * rewrite_rules option is empty (meaning rules were never + * flushed) and if permalink_structure is set, then flushes once. + * A flag file prevents repeated flushes on subsequent requests. + */ + function playground_maybe_flush_rewrite_rules() { + $flag = '/internal/shared/.rewrite-rules-flushed'; + if (file_exists($flag)) { + return; + } + if (!function_exists('get_option')) { + return; + } + $structure = get_option('permalink_structure'); + if (empty($structure)) { + return; + } + $rules = get_option('rewrite_rules'); + if (!empty($rules)) { + @file_put_contents($flag, '1'); + return; + } + global $wp_rewrite; + if (!isset($wp_rewrite) && class_exists('WP_Rewrite')) { + $wp_rewrite = new WP_Rewrite(); + } + if (isset($wp_rewrite) && method_exists($wp_rewrite, 'flush_rules')) { + $wp_rewrite->flush_rules(); + } + @file_put_contents($flag, '1'); + } + add_action('init', 'playground_maybe_flush_rewrite_rules', 99999); + // Create the fonts directory if missing if(!file_exists(WP_CONTENT_DIR . '/fonts')) { mkdir(WP_CONTENT_DIR . '/fonts'); @@ -380,7 +738,7 @@ export async function setupPlatformLevelMuPlugins(php: UniversalPHP) { await php.writeFile( '/internal/shared/preload/error-handler.php', `prop = value), which + * was valid in PHP 4 but triggers E_WARNING in PHP 5.6. + * These are benign and cannot be fixed in WP core since + * Playground downloads unmodified WordPress releases. + */ + if (strpos($message, "Creating default object from empty value") !== false) { + return; + } /** * Don't complain about network errors when not connected to the network. */ @@ -427,7 +795,7 @@ export async function setupPlatformLevelMuPlugins(php: UniversalPHP) { } return false; }); - })();` + ${phpMajor < 7 ? '});' : '})();'}` ); } @@ -452,9 +820,14 @@ export async function preloadPhpInfoRoute( ); } +export interface SqliteIntegrationOptions { + phpVersion?: string; +} + export async function preloadSqliteIntegration( php: UniversalPHP, - sqliteZip: File + sqliteZip: File, + options: SqliteIntegrationOptions = {} ) { if (await php.isDir('/tmp/sqlite-database-integration')) { await php.rmdir('/tmp/sqlite-database-integration', { @@ -472,12 +845,19 @@ export async function preloadSqliteIntegration( }`; await php.mv(temporarySqlitePluginFolder, SQLITE_PLUGIN_FOLDER); + // Patch SQLite plugin files for PHP 5.6 compatibility by removing + // PHP 7.0+ syntax (null coalescing, return types, parameter types). + const phpMajor = parseInt(options.phpVersion ?? '8', 10); + if (phpMajor < 7) { + await patchSqlitePluginForLegacyPhp(php, SQLITE_PLUGIN_FOLDER); + } + // Prevents the SQLite integration from trying to call activate_plugin() await php.defineConstant('SQLITE_MAIN_FILE', '1'); const dbCopy = await php.readFileAsText( joinPaths(SQLITE_PLUGIN_FOLDER, 'db.copy') ); - const dbPhp = dbCopy + let dbPhp = dbCopy .replace( "'{SQLITE_IMPLEMENTATION_FOLDER_PATH}'", phpVar(SQLITE_PLUGIN_FOLDER) @@ -486,21 +866,165 @@ export async function preloadSqliteIntegration( "'{SQLITE_PLUGIN}'", phpVar(joinPaths(SQLITE_PLUGIN_FOLDER, 'load.php')) ); + if (phpMajor < 7) { + // Guard add_action call for WordPress < 2.1 compatibility. + // When loaded via the lazy $wpdb loader, WordPress hooks + // may not be available yet. + dbPhp = dbPhp.replace( + /^(add_action\(\s*\n)/m, + 'if (function_exists("add_action")) $1' + ); + } const dbPhpPath = joinPaths(await php.documentRoot, 'wp-content/db.php'); - const stopIfDbPhpExists = ``; - const SQLITE_MUPLUGIN_PATH = - '/internal/shared/mu-plugins/sqlite-database-integration.php'; - await php.writeFile(SQLITE_MUPLUGIN_PATH, stopIfDbPhpExists + dbPhp); + `; + + await php.writeFile(SQLITE_MUPLUGIN_PATH, `` + dbPhp); await php.writeFile( `/internal/shared/preload/0-sqlite.php`, - stopIfDbPhpExists + - ` + +reinitialize_sqlite(); + }` +)} +// These stubs return truthy values because old WordPress (< 3.0) +// calls mysql_connect() directly in wpdb::__construct() and calls +// bail() on a falsy return. +if(!function_exists('mysqli_connect')) { + function mysqli_connect() { return true; } +} +if(!function_exists('mysqli_init')) { + function mysqli_init() { return true; } +} +if(!function_exists('mysql_connect')) { + function mysql_connect() { return true; } +} +if(!function_exists('mysql_select_db')) { + function mysql_select_db() { return true; } +} +${MYSQL_SHIMS_PHP} +if (!function_exists('str_contains')) { + function str_contains($haystack, $needle) { + return $needle === '' || strpos($haystack, $needle) !== false; + } +} +if (!function_exists('str_starts_with')) { + function str_starts_with($haystack, $needle) { + return strncmp($haystack, $needle, strlen($needle)) === 0; + } +} +if (!function_exists('str_ends_with')) { + function str_ends_with($haystack, $needle) { + return $needle === '' || substr($haystack, -strlen($needle)) === $needle; + } +} +if (PHP_MAJOR_VERSION < 7) { + $level = E_ALL & ~E_DEPRECATED & ~E_STRICT; + error_reporting($level); + ini_set('error_reporting', $level); +} +if (!isset($_SERVER['SERVER_PROTOCOL'])) { + $_SERVER['SERVER_PROTOCOL'] = 'HTTP/1.1'; +} +if (!ini_get('date.timezone')) { + date_default_timezone_set('UTC'); +} + + `; +} +/** + * The shared Playground_SQLite_Integration_Loader class definition, + * parameterized by the load_sqlite_integration() body. + */ +function SQLITE_PRELOAD_LOADER_CLASS(loadBody: string): string { + return ` /** * Loads the SQLite integration plugin before WordPress is loaded * and without creating a drop-in "db.php" file. @@ -549,7 +1073,7 @@ class Playground_SQLite_Integration_Loader { $GLOBALS['wpdb']->$name = $value; } protected function load_sqlite_integration() { - require_once ${phpVar(SQLITE_MUPLUGIN_PATH)}; + ${loadBody} } } /** @@ -570,27 +1094,7 @@ $wpdb = $GLOBALS['wpdb'] = new Playground_SQLite_Integration_Loader(); * * What WordPress demands, Playground shall provide. */ -if(!function_exists('mysqli_connect')) { - function mysqli_connect() {} -} - - ` - ); - /** - * Ensure the SQLite integration is loaded and clearly communicate - * if it isn't. This is useful because WordPress database errors - * may be cryptic and won't mention the SQLite integration. - */ - await php.writeFile( - `/internal/shared/mu-plugins/sqlite-test.php`, - `callback )( $args ) → call_user_func( $this->callback, $args ) + result = result.replace( + /\(\s*(\$\w+(?:->\w+)+)\s*\)\(\s*([^)]*)\s*\)/g, + (_, callable, args) => `call_user_func(${callable}, ${args})` + ); + + // Replace isset(self::CONST[$key]) with array_key_exists($key, self::CONST). + // PHP 5.6 doesn't support isset() on class constant array access — + // class constants are expressions, not variables. + result = result.replace( + /isset\(\s*((?:self|static)::\w+)\[\s*([^\]]+?)\s*\]\s*\)/g, + (_, constRef, key) => `array_key_exists(${key.trim()}, ${constRef})` + ); + + // Replace Throwable with Exception in catch blocks and type hints + result = result.replace( + /catch\s*\(\s*\\?Throwable\b/g, + 'catch ( Exception' + ); + // Replace Throwable parameter type hints with Exception + result = result.replace(/([,(]\s*)\\?Throwable\s+(\$)/g, '$1Exception $2'); + + // Replace PHP 7 error classes with Exception + result = result.replace( + /new\s+\\?(?:TypeError|ArgumentCountError|ValueError|Error)\s*\(/g, + 'new Exception(' + ); + + // Replace class hierarchy: `extends Error` → `extends Exception` + result = result.replace( + /extends\s+\\?(?:Error|TypeError|ValueError)\b/g, + 'extends Exception' + ); + + // Replace Closure::call() with bindTo() equivalent (PHP 7.0+) + result = result.replace( + /(\$\w+)->call\(\s*(\$[\w>-]+)\s*,\s*(\$\w+)\s*\)/g, + 'call_user_func($1->bindTo($2, get_class($2)), $3)' + ); + // Closure::call() without arguments + result = result.replace( + /(\$\w+)->call\(\s*(\$[\w>-]+)\s*\)/g, + 'call_user_func($1->bindTo($2, get_class($2)))' + ); + + return result; +} + +/** Safety limit for the iterative ?? replacement loop. */ +const MAX_NULL_COALESCING_ITERATIONS = 500; + +/** + * Replaces PHP 7.0+ null coalescing operators (`??`) with PHP 5.6 + * compatible equivalents. + * + * Handles: + * - `$var ?? $default` → `isset($var) ? $var : $default` + * - `$var->prop[$k] ?? $d` → `isset($var->prop[$k]) ? ... : $d` + * - `self::CONST[$k] ?? $d` → `array_key_exists($k, self::CONST) ? ... : $d` + * (PHP 5.6 doesn't support `isset()` on class constant array access) + * - `$obj->method() ?? $d` → uses temp variable to avoid double evaluation + */ +export function replaceNullCoalescing(content: string): string { + // CRITICAL: Fast path to avoid catastrophic regex backtracking. + if (!content.includes('??')) { + return content; + } + + // Protect nullsafe operators (?->) from being corrupted by ?? processing. + // Replace `?->X` with `->__NS__X` so the `?` doesn't interfere with + // `??` regex patterns while preserving the `->property` chain shape + // that the general-case regex expects. + const NULLSAFE_PREFIX = '__NS__'; + let result = content.replace(/\?->(\w)/g, `->${NULLSAFE_PREFIX}$1`); + + // Fallback value pattern shared by all cases. + const fallbackPat = + "(?:'[^']*'|\"[^\"]*\"|\\$[\\w]+(?:(?:\\['[^']*'\\])|(?:->[\\w]+))*|(?:self|static)::[\\w]+|\\w+\\([^)]*\\)|null|true|false|\\d+|[A-Z_]+|array\\(\\))"; + + // 1. Class constant array access: self::CONST[$key] ?? fallback + // PHP 5.6 doesn't support isset() on class constant subscripts, + // so use array_key_exists() instead. + // The key pattern allows one level of nested brackets to handle + // expressions like `self::MAP[ $info['TYPE'] ]`. + result = result.replace( + new RegExp( + '((?:self|static)::\\w+)\\[\\s*([^\\]]*(?:\\[[^\\]]*\\][^\\]]*)*)\\s*\\]\\s*\\?\\?\\s*(' + + fallbackPat + + ')', + 'g' + ), + (_, constRef, key, fallback) => { + const k = key.trim(); + return `(array_key_exists(${k}, ${constRef}) ? ${constRef}[${k}] : ${fallback})`; + } + ); + + // 1b. Multi-line method chain continuation: ->method(...) ?? fallback + // Handles cases where $var is on a previous line and only + // ->method(...) ?? fallback appears on the current line. + // The negative lookbehind ensures we don't match ->method() + // that's part of a $var->method() chain (handled by #3). + // Excludes word chars, ], and ) preceding the `->`. + result = result.replace( + new RegExp( + '(?[\\w]+\\([^)]*\\))\\s*\\?\\?\\s*(' + + fallbackPat + + ')', + 'g' + ), + (_, call, fallback) => { + return `(($__playground_nc_tmp = ${call}) !== null ? $__playground_nc_tmp : ${fallback})`; + } + ); + + // 2. Function/method call result with array access: + // explode(...)[1] ?? $fallback + result = result.replace( + new RegExp( + '(\\w+)\\(\\s*([^)]+)\\s*\\)\\[(\\d+)]\\s*\\?\\?\\s*(' + + fallbackPat + + ')', + 'g' + ), + (_, fn, args, idx, fallback) => { + return `(($__playground_nc_tmp = ${fn}(${args})) && isset($__playground_nc_tmp[${idx}]) ? $__playground_nc_tmp[${idx}] : ${fallback})`; + } + ); + + // 3. General case: variable/property/array/method access ?? default + // + // The bracket content uses `[^\]]*` (no nested bracket support) + // to avoid catastrophic backtracking. Nested brackets like + // `$a[$b[$c]]` are not needed in the SQLite integration code. + let changed = true; + let iterations = 0; + while (changed) { + if (++iterations > MAX_NULL_COALESCING_ITERATIONS) { + break; + } + changed = false; + const re = new RegExp( + '(\\$[\\w]+(?:(?:\\[[^\\]]*\\])|(?:->[\\w]+(?:\\([^)]*\\))?))*' + + ')\\s*\\?\\?\\s*(' + + fallbackPat + + ')', + 'g' + ); + const replaced = result.replace(re, (_, lhs, rhs) => { + changed = true; + // If the LHS contains a method/function call, use a temp + // variable to avoid calling it twice. The ?? operator + // evaluates its LHS once; isset(x) ? x : y evaluates + // x twice, which breaks side-effectful calls. + if (lhs.includes('(')) { + return `(($__playground_nc_tmp = ${lhs}) !== null ? $__playground_nc_tmp : ${rhs})`; + } + return `isset(${lhs}) ? ${lhs} : ${rhs}`; + }); + result = replaced; + } + result = result.replace(new RegExp(`->${NULLSAFE_PREFIX}`, 'g'), '?->'); + return result; +} diff --git a/packages/playground/wordpress/src/legacy-wp-fixes.ts b/packages/playground/wordpress/src/legacy-wp-fixes.ts new file mode 100644 index 00000000000..3a426f0a1b5 --- /dev/null +++ b/packages/playground/wordpress/src/legacy-wp-fixes.ts @@ -0,0 +1,1928 @@ +/** + * Legacy WordPress version fixes. + * + * Patches WordPress source files at boot time to make WP 1.0 through + * 2.8 work on the SQLite integration layer with the PHP 5.6 WASM + * binary. Also patches the SQLite integration plugin itself for + * PHP 5.6 compatibility. + * + * All functions here are only needed for old WP versions or old PHP. + * Modern WordPress (3.0+) on PHP 7+ doesn't need any of these. + */ +import type { PHP, UniversalPHP } from '@php-wasm/universal'; +import { logger } from '@php-wasm/logger'; +import { joinPaths } from '@php-wasm/util'; +import { MYSQL_SHIMS_PHP } from './mysql-shims'; +import { + replaceNullCoalescing, + replacePhp7ErrorClasses, + stripPhp7TypeDeclarations, +} from './legacy-php-compat'; + +/** + * Patches WordPress source files for legacy version compatibility. + * + * Applies all necessary patches to make old WordPress versions + * (1.0 through 2.8) work with modern PHP and the SQLite integration. + */ +export async function patchWordPressSourceFiles( + php: PHP, + documentRoot: string, + phpMajor: number +) { + await ensureVersionPhp(php, documentRoot); + await ensureWpLoadPhp(php, documentRoot); + await patchWp10DoubleQuotedSqlLiterals(php, documentRoot); + await patchWpSettingsPhp(php, documentRoot, phpMajor); + await patchWpFunctionsPhp(php, documentRoot); + await patchWpInstallPhp(php, documentRoot); + await patchWpDbPhp(php, documentRoot); + await patchWpSchemaPhp(php, documentRoot); + await patchWpAdminRelativePaths(php, documentRoot); + await patchCheckAdminReferer(php, documentRoot); + await patchWpAdminDashboard(php, documentRoot); + if (phpMajor < 7) { + await patchWpLoginDisable1Password(php, documentRoot); + await ensureLegacyAdminAuth(php, documentRoot); + await patchAdminAuthRedirect(php, documentRoot); + await patchAdminAjaxAuth(php, documentRoot); + } +} + +/** + * Returns the PHP content for wp-content/db.php. + * + * This db.php provides MySQL/MySQLi function stubs and, for WP < 3.0, + * loads the SQLite integration directly. Modern WP only needs this file + * to *exist* (to bypass the extension_loaded('mysql') check), but old + * WP actually uses the stubs defined here. + */ +export function generateDbPhpContent(): string { + return `dbh as a boolean stub. +// +// Only do this for old WP: check if wpdb lacks db_connect() +// as a method defined in the class itself (not inherited). +// Modern WP (3.0+) uses the lazy $wpdb loader successfully. +if ( + class_exists('wpdb', false) && + isset($GLOBALS['wpdb']) && + !($GLOBALS['wpdb'] instanceof wpdb) && + !method_exists('wpdb', 'db_connect') && + file_exists('/internal/shared/mu-plugins/sqlite-database-integration.php') +) { + // This block loads SQLite integration for old WP (< 3.0). + require_once '/internal/shared/mu-plugins/sqlite-database-integration.php'; + if ( + isset($GLOBALS['wpdb']) && + $GLOBALS['wpdb'] instanceof wpdb && + method_exists($GLOBALS['wpdb'], 'reinitialize_sqlite') + ) { + $GLOBALS['wpdb']->reinitialize_sqlite(); + } +} +// +// Polyfills for PHP functions used by the SQLite integration +// but missing on older PHP versions. +if (!function_exists('str_contains')) { + function str_contains($haystack, $needle) { + return $needle === '' || strpos($haystack, $needle) !== false; + } +} +if (!function_exists('str_starts_with')) { + function str_starts_with($haystack, $needle) { + return strncmp($haystack, $needle, strlen($needle)) === 0; + } +} +if (!function_exists('str_ends_with')) { + function str_ends_with($haystack, $needle) { + return $needle === '' || substr($haystack, -strlen($needle)) === $needle; + } +} +// Provides MySQL/MySQLi function stubs so WordPress 4.x +// doesn't die on the extension_loaded() check. +// The actual SQLite database is set up by the +// 0-sqlite.php preload via auto_prepend_file. +// +// mysql_connect and mysql_select_db return truthy values because +// WordPress < 3.0 calls mysql_connect() directly in wpdb::__construct +// and dies on false. The return value is never used for real queries. +if (!function_exists('mysql_connect')) { + function mysql_connect() { return true; } +} +if (!function_exists('mysql_select_db')) { + function mysql_select_db() { return true; } +} +if (!function_exists('mysqli_connect')) { + function mysqli_connect() { return true; } +} +if (!function_exists('mysqli_init')) { + function mysqli_init() { return true; } +} +if (!function_exists('mysqli_real_connect')) { + function mysqli_real_connect() { return true; } +} +if (!function_exists('mysqli_error')) { + function mysqli_error() { return ''; } +} +if (!function_exists('mysqli_errno')) { + function mysqli_errno() { return 0; } +} +if (!function_exists('mysqli_query')) { + function mysqli_query() { return false; } +} +if (!function_exists('mysqli_set_charset')) { + function mysqli_set_charset() { return true; } +} +if (!function_exists('mysqli_select_db')) { + function mysqli_select_db() { return true; } +} +if (!function_exists('mysqli_close')) { + function mysqli_close() { return true; } +} +${MYSQL_SHIMS_PHP} +`; +} + +/** + * Pre-creates WP 1.x database tables via PDO before the installer runs. + * + * The WP 1.x installer uses mysql_* calls that the SQLite driver can't + * fully handle. By creating tables with the correct schema first, the + * installer's CREATE TABLE statements become no-ops. + */ +export async function preCreateLegacyTables(php: PHP): Promise { + await php.run({ + code: `setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $p = 'wp_'; + // Only create if this is WP 1.x (has multi-step installer + // with mysql_list_tables, not the dbDelta-based installer). + $install = getenv('DOCUMENT_ROOT') . '/wp-admin/install.php'; + if (file_exists($install) && strpos(file_get_contents($install), 'mysql_list_tables') !== false) { + $pdo->exec("CREATE TABLE IF NOT EXISTS {$p}categories (cat_ID INTEGER PRIMARY KEY AUTOINCREMENT, cat_name TEXT NOT NULL DEFAULT '', category_nicename TEXT NOT NULL DEFAULT '', category_description TEXT NOT NULL DEFAULT '', category_parent INTEGER NOT NULL DEFAULT 0)"); + $pdo->exec("CREATE TABLE IF NOT EXISTS {$p}post2cat (rel_id INTEGER PRIMARY KEY AUTOINCREMENT, post_id INTEGER NOT NULL DEFAULT 0, category_id INTEGER NOT NULL DEFAULT 0)"); + $pdo->exec("CREATE TABLE IF NOT EXISTS {$p}postmeta (meta_id INTEGER PRIMARY KEY AUTOINCREMENT, post_id INTEGER NOT NULL DEFAULT 0, meta_key TEXT NOT NULL DEFAULT '', meta_value TEXT NOT NULL DEFAULT '')"); + // WP 1.0 options table has more columns than WP 1.2 + $pdo->exec("CREATE TABLE IF NOT EXISTS {$p}options (option_id INTEGER PRIMARY KEY AUTOINCREMENT, blog_id INTEGER NOT NULL DEFAULT 0, option_name TEXT NOT NULL DEFAULT '', option_can_override TEXT NOT NULL DEFAULT 'Y', option_type INTEGER NOT NULL DEFAULT 1, option_value TEXT NOT NULL DEFAULT '', option_width INTEGER NOT NULL DEFAULT 20, option_height INTEGER NOT NULL DEFAULT 8, option_description TEXT NOT NULL DEFAULT '', option_admin_level INTEGER NOT NULL DEFAULT 1, autoload TEXT NOT NULL DEFAULT 'yes')"); + // Seed essential options so WordPress can boot + try { + if (!$pdo->query("SELECT COUNT(*) FROM {$p}options WHERE option_name='siteurl'")->fetchColumn()) { + $pdo->exec("INSERT INTO {$p}options (option_name, option_value) VALUES ('siteurl', 'http://localhost')"); + $pdo->exec("INSERT INTO {$p}options (option_name, option_value) VALUES ('blogname', 'My WordPress Website')"); + $pdo->exec("INSERT INTO {$p}options (option_name, option_value) VALUES ('blogdescription', 'Just another WordPress weblog')"); + $pdo->exec("INSERT INTO {$p}options (option_name, option_value) VALUES ('home', 'http://localhost')"); + } + } catch (Exception $e) {} + // Links tables (WP 1.2 install step 1 uses mysql_list_tables) + $pdo->exec("CREATE TABLE IF NOT EXISTS {$p}links (link_id INTEGER PRIMARY KEY AUTOINCREMENT, link_url TEXT NOT NULL DEFAULT '', link_name TEXT NOT NULL DEFAULT '', link_image TEXT NOT NULL DEFAULT '', link_target TEXT NOT NULL DEFAULT '', link_category INTEGER NOT NULL DEFAULT 0, link_description TEXT NOT NULL DEFAULT '', link_visible TEXT NOT NULL DEFAULT 'Y', link_owner INTEGER NOT NULL DEFAULT 1, link_rating INTEGER NOT NULL DEFAULT 0, link_updated TEXT NOT NULL DEFAULT '0000-00-00 00:00:00', link_rel TEXT NOT NULL DEFAULT '', link_notes TEXT NOT NULL DEFAULT '', link_rss TEXT NOT NULL DEFAULT '')"); + $pdo->exec("CREATE TABLE IF NOT EXISTS {$p}linkcategories (cat_id INTEGER PRIMARY KEY AUTOINCREMENT, cat_name TEXT NOT NULL DEFAULT '', auto_toggle TEXT NOT NULL DEFAULT 'N', show_images TEXT NOT NULL DEFAULT 'Y', show_description TEXT NOT NULL DEFAULT 'N', show_rating TEXT NOT NULL DEFAULT 'Y', show_updated TEXT NOT NULL DEFAULT 'Y', sort_order TEXT NOT NULL DEFAULT 'name', sort_desc TEXT NOT NULL DEFAULT 'ASC', text_before_link TEXT NOT NULL DEFAULT '
  • ', text_after_link TEXT NOT NULL DEFAULT '
  • ', text_after_all TEXT NOT NULL DEFAULT '', list_limit INTEGER NOT NULL DEFAULT -1)"); + $pdo->exec("CREATE TABLE IF NOT EXISTS {$p}comments (comment_ID INTEGER PRIMARY KEY AUTOINCREMENT, comment_post_ID INTEGER NOT NULL DEFAULT 0, comment_author TEXT NOT NULL DEFAULT '', comment_author_email TEXT NOT NULL DEFAULT '', comment_author_url TEXT NOT NULL DEFAULT '', comment_author_IP TEXT NOT NULL DEFAULT '', comment_date TEXT NOT NULL DEFAULT '0000-00-00 00:00:00', comment_date_gmt TEXT NOT NULL DEFAULT '0000-00-00 00:00:00', comment_content TEXT NOT NULL DEFAULT '', comment_karma INTEGER NOT NULL DEFAULT 0, comment_approved TEXT NOT NULL DEFAULT '1', comment_agent TEXT NOT NULL DEFAULT '', comment_type TEXT NOT NULL DEFAULT '', comment_parent INTEGER NOT NULL DEFAULT 0, user_id INTEGER NOT NULL DEFAULT 0)"); + // Seed Hello world post + $pdo->exec("CREATE TABLE IF NOT EXISTS {$p}posts (ID INTEGER PRIMARY KEY AUTOINCREMENT, post_author INTEGER NOT NULL DEFAULT 0, post_date TEXT NOT NULL DEFAULT '0000-00-00 00:00:00', post_date_gmt TEXT NOT NULL DEFAULT '0000-00-00 00:00:00', post_content TEXT NOT NULL DEFAULT '', post_title TEXT NOT NULL DEFAULT '', post_category INTEGER NOT NULL DEFAULT 0, post_excerpt TEXT NOT NULL DEFAULT '', post_status TEXT NOT NULL DEFAULT 'publish', comment_status TEXT NOT NULL DEFAULT 'open', ping_status TEXT NOT NULL DEFAULT 'open', post_password TEXT NOT NULL DEFAULT '', post_name TEXT NOT NULL DEFAULT '', to_ping TEXT NOT NULL DEFAULT '', pinged TEXT NOT NULL DEFAULT '', post_modified TEXT NOT NULL DEFAULT '0000-00-00 00:00:00', post_modified_gmt TEXT NOT NULL DEFAULT '0000-00-00 00:00:00', post_content_filtered TEXT NOT NULL DEFAULT '')"); + try { + if (!$pdo->query("SELECT COUNT(*) FROM {$p}posts")->fetchColumn()) { + $now = date('Y-m-d H:i:s'); + $pdo->exec("INSERT INTO {$p}posts (ID, post_author, post_date, post_date_gmt, post_content, post_title, post_status, post_name, post_modified, post_modified_gmt) VALUES (1, 1, '{$now}', '{$now}', 'Welcome to WordPress. This is your first post. Edit or delete it, then start blogging!', 'Hello world!', 'publish', 'hello-world', '{$now}', '{$now}')"); + } + } catch (Exception $e) {} + } + `, + env: { DOCUMENT_ROOT: php.documentRoot }, + }); +} + +/** + * Runs post-install fixups for old WordPress versions. + * + * Two-stage approach: + * 1. Load WordPress and fix data via $wpdb (admin password, seed content) + * 2. PDO fallback that directly creates tables and seeds data when the + * WordPress-based fixup fails (WP 1.x where loading WP may crash) + */ +export async function runPostInstallLegacyFixups(php: PHP): Promise { + // Derive siteUrl from the PHP request handler if available. + const siteUrl = (php as any).requestHandler?.absoluteUrl?.toString() || ''; + // Stage 1: wpdb-based fixups (loads WordPress) + try { + const result = await php.run({ + code: `options) ? $wpdb->options : $GLOBALS['table_prefix'] . 'options'; + try { + $_pg_url = getenv('PLAYGROUND_SITE_URL'); + if ($_pg_url) { + $_pg_current = $wpdb->get_var("SELECT option_value FROM {$_pg_opts} WHERE option_name = 'siteurl'"); + if ($_pg_current !== $_pg_url) { + $wpdb->query("UPDATE {$_pg_opts} SET option_value = '{$_pg_url}' WHERE option_name = 'siteurl'"); + $wpdb->query("UPDATE {$_pg_opts} SET option_value = '{$_pg_url}' WHERE option_name = 'home'"); + } + } + } catch (Exception $e) {} + + // Fix admin password for WP < 2.5. + // Use $wpdb->users if available (WP 1.5+), + // fall back to $table_prefix . 'users' (WP 1.2). + $users_table = !empty($wpdb->users) ? $wpdb->users : $GLOBALS['table_prefix'] . 'users'; + + // WP 1.2/1.0: the installer may fail to create the + // users table or the admin user. Create both if missing. + $wpdb->query("CREATE TABLE IF NOT EXISTS {$users_table} ( + ID int(10) unsigned NOT NULL auto_increment, + user_login varchar(20) NOT NULL default '', + user_pass varchar(64) NOT NULL default '', + user_firstname varchar(50) NOT NULL default '', + user_lastname varchar(50) NOT NULL default '', + user_nickname varchar(50) NOT NULL default '', + user_icq int(10) unsigned NOT NULL default '0', + user_email varchar(100) NOT NULL default '', + user_url varchar(100) NOT NULL default '', + user_ip varchar(15) NOT NULL default '', + user_domain varchar(200) NOT NULL default '', + user_browser varchar(200) NOT NULL default '', + dateYMDhour datetime NOT NULL default '0000-00-00 00:00:00', + user_level int(2) unsigned NOT NULL default '0', + user_aim varchar(50) NOT NULL default '', + user_msn varchar(100) NOT NULL default '', + user_yim varchar(50) NOT NULL default '', + user_idmode varchar(20) NOT NULL default '', + PRIMARY KEY (ID), + UNIQUE KEY user_login (user_login) + )"); + if (!$wpdb->get_var("SELECT COUNT(*) FROM {$users_table}")) { + $now = date('Y-m-d H:i:s'); + $wpdb->query( + "INSERT INTO {$users_table} (ID, user_login, user_pass, user_email, user_level, dateYMDhour, user_nickname) " . + "VALUES (1, 'admin', MD5('password'), 'admin@localhost.com', 10, '{$now}', 'admin')" + ); + } + $wpdb->query( + "UPDATE {$users_table} SET user_pass = MD5('password') WHERE user_login = 'admin'" + ); + + // Ensure WordPress roles exist and the admin user has + // admin capabilities. The installer calls populate_roles() + // but it may fail on SQLite. Set up roles and user caps + // directly via database queries as a fallback. + $p = $GLOBALS['table_prefix']; + $roles_key = $p . 'user_roles'; + $_diag = array('prefix' => $p, 'roles_key' => $roles_key); + try { + $has_roles = $wpdb->get_var( + "SELECT COUNT(*) FROM {$p}options WHERE option_name = '{$roles_key}'" + ); + $_diag['has_roles'] = $has_roles; + } catch (Exception $e) { + $has_roles = 0; + $_diag['roles_error'] = $e->getMessage(); + } + if (!$has_roles) { + // Minimal administrator role with essential capabilities. + $roles = array('administrator' => array( + 'name' => 'Administrator', + 'capabilities' => array( + 'switch_themes'=>true, 'edit_themes'=>true, + 'activate_plugins'=>true, 'edit_plugins'=>true, + 'edit_users'=>true, 'edit_files'=>true, + 'manage_options'=>true, 'moderate_comments'=>true, + 'manage_categories'=>true, 'manage_links'=>true, + 'upload_files'=>true, 'import'=>true, + 'unfiltered_html'=>true, 'edit_posts'=>true, + 'edit_others_posts'=>true, 'edit_published_posts'=>true, + 'publish_posts'=>true, 'edit_pages'=>true, + 'read'=>true, 'level_10'=>true, 'level_9'=>true, + 'level_8'=>true, 'level_7'=>true, 'level_6'=>true, + 'level_5'=>true, 'level_4'=>true, 'level_3'=>true, + 'level_2'=>true, 'level_1'=>true, 'level_0'=>true, + 'edit_others_pages'=>true, 'edit_published_pages'=>true, + 'publish_pages'=>true, 'delete_pages'=>true, + 'delete_others_pages'=>true, 'delete_published_pages'=>true, + 'delete_posts'=>true, 'delete_others_posts'=>true, + 'delete_published_posts'=>true, 'delete_private_posts'=>true, + 'edit_private_posts'=>true, 'read_private_posts'=>true, + 'delete_private_pages'=>true, 'edit_private_pages'=>true, + 'read_private_pages'=>true, + ) + )); + $wpdb->query("INSERT INTO {$p}options (option_name, option_value, autoload) VALUES ('{$roles_key}', '" . addslashes(serialize($roles)) . "', 'yes')"); + } + // Set admin user capabilities and level in usermeta. + $um = isset($wpdb->usermeta) ? $wpdb->usermeta : $p . 'usermeta'; + try { + $has_cap = $wpdb->get_var("SELECT COUNT(*) FROM {$um} WHERE user_id=1 AND meta_key='{$p}capabilities'"); + if (!$has_cap) { + $cap_val = addslashes(serialize(array('administrator' => true))); + $wpdb->query("INSERT INTO {$um} (user_id, meta_key, meta_value) VALUES (1, '{$p}capabilities', '{$cap_val}')"); + } + $has_level = $wpdb->get_var("SELECT COUNT(*) FROM {$um} WHERE user_id=1 AND meta_key='{$p}user_level'"); + if (!$has_level) { + $wpdb->query("INSERT INTO {$um} (user_id, meta_key, meta_value) VALUES (1, '{$p}user_level', '10')"); + } + } catch (Exception $e) { + $_diag['usermeta_error'] = $e->getMessage(); + } + // Also check usermeta + try { + $_diag['all_usermeta'] = $wpdb->get_results( + "SELECT meta_key, meta_value FROM {$um} WHERE user_id=1", ARRAY_A + ); + } catch (Exception $e) { + $_diag['usermeta_read_error'] = $e->getMessage(); + } + echo 'ROLES_DIAG:' . json_encode($_diag); + + // Seed default content when the posts table is empty. + // Covers both old WP 1.5 (SQLite NOT NULL fix) and + // WP 2.5+ where the install may have failed to seed + // data due to SQLite compatibility issues. + $posts_table = !empty($wpdb->posts) ? $wpdb->posts : $GLOBALS['table_prefix'] . 'posts'; + $has_posts = false; + try { $has_posts = (bool)$wpdb->get_var("SELECT COUNT(*) FROM {$posts_table}"); } catch (Exception $e) {} + if (!$has_posts) { + $now = date('Y-m-d H:i:s'); + $now_gmt = gmdate('Y-m-d H:i:s'); + + // Default category + if (isset($wpdb->categories)) { + $wpdb->query("INSERT INTO {$wpdb->categories} (cat_ID, cat_name, category_nicename, category_description, category_parent) VALUES (1, 'Uncategorized', 'uncategorized', '', 0)"); + } + + // Default post — use only basic columns that exist + // in all WP versions (1.0+). + $wpdb->query("INSERT INTO {$posts_table} (ID, post_author, post_date, post_date_gmt, post_content, post_title, post_excerpt, post_status, comment_status, ping_status, post_password, post_name, to_ping, pinged, post_modified, post_modified_gmt, post_content_filtered) VALUES (1, 1, '{$now}', '{$now_gmt}', 'Welcome to WordPress. This is your first post. Edit or delete it, then start blogging!', 'Hello world!', '', 'publish', 'open', 'open', '', 'hello-world', '', '', '{$now}', '{$now_gmt}', '')"); + + // Default comment + if (isset($wpdb->comments)) { + $wpdb->query("INSERT INTO {$wpdb->comments} (comment_post_ID, comment_author, comment_author_email, comment_author_url, comment_author_IP, comment_date, comment_date_gmt, comment_content, comment_karma, comment_approved, comment_agent, comment_type, comment_parent, user_id) VALUES (1, 'Mr WordPress', '', 'http://wordpress.org', '127.0.0.1', '{$now}', '{$now_gmt}', 'Hi, this is a comment. To delete a comment, just log in and view the post comments. There you will have the option to edit or delete them.', 0, '1', '', '', 0, 0)"); + } + + // Link post to category + if (isset($wpdb->post2cat)) { + $wpdb->query("INSERT INTO {$wpdb->post2cat} (rel_id, post_id, category_id) VALUES (1, 1, 1)"); + } + } + `, + env: { + DOCUMENT_ROOT: php.documentRoot, + PLAYGROUND_SITE_URL: siteUrl || '', + }, + }); + // TEMPORARY: Log diagnostic output + if (result.text.includes('ROLES_DIAG:')) { + const diagStr = result.text.split('ROLES_DIAG:')[1]?.split('\n')[0]; + logger.warn('Legacy roles diagnostic:', diagStr); + } + } catch (error) { + // Non-fatal: post-install fixups may fail on some WP versions + logger.warn('Legacy WP post-install fixups failed (non-fatal):', error); + } + + // Stage 2: PDO fallback for WP 1.x where loading WordPress crashes + try { + await php.run({ + code: `setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + // Check if admin user exists + $prefix = 'wp_'; + $table = $prefix . 'users'; + try { + $count = $pdo->query("SELECT COUNT(*) FROM {$table}")->fetchColumn(); + } catch (Exception $e) { + // Table might not exist — create it + $pdo->exec("CREATE TABLE IF NOT EXISTS {$table} ( + ID INTEGER PRIMARY KEY AUTOINCREMENT, + user_login TEXT NOT NULL DEFAULT '', + user_pass TEXT NOT NULL DEFAULT '', + user_nickname TEXT NOT NULL DEFAULT '', + user_email TEXT NOT NULL DEFAULT '', + user_url TEXT NOT NULL DEFAULT '', + user_ip TEXT NOT NULL DEFAULT '', + user_domain TEXT NOT NULL DEFAULT '', + user_browser TEXT NOT NULL DEFAULT '', + dateYMDhour TEXT NOT NULL DEFAULT '0000-00-00 00:00:00', + user_level INTEGER NOT NULL DEFAULT 0, + user_idmode TEXT NOT NULL DEFAULT '', + user_firstname TEXT NOT NULL DEFAULT '', + user_lastname TEXT NOT NULL DEFAULT '', + user_icq INTEGER NOT NULL DEFAULT 0, + user_aim TEXT NOT NULL DEFAULT '', + user_msn TEXT NOT NULL DEFAULT '', + user_yim TEXT NOT NULL DEFAULT '' + )"); + $count = 0; + } + if ($count == 0) { + $now = date('Y-m-d H:i:s'); + $pass = md5('password'); + try { + // Build INSERT with defaults for ALL columns + $col_info = $pdo->query("PRAGMA table_info({$table})")->fetchAll(PDO::FETCH_ASSOC); + $known = array( + 'ID' => '1', 'user_login' => "'admin'", + 'user_pass' => "'{$pass}'", 'user_email' => "'admin@localhost.com'", + 'user_level' => '10', 'dateYMDhour' => "'{$now}'", + 'user_nickname' => "'admin'", 'user_nicename' => "'admin'", + 'user_registered' => "'{$now}'", 'user_status' => '0', + ); + $ins_cols = array(); $ins_vals = array(); + foreach ($col_info as $ci) { + $cn = $ci['name']; + $ins_cols[] = $cn; + if (isset($known[$cn])) { + $ins_vals[] = $known[$cn]; + } elseif ($ci['dflt_value'] !== null) { + $ins_vals[] = $ci['dflt_value']; + } elseif (stripos($ci['type'], 'int') !== false) { + $ins_vals[] = '0'; + } else { + $ins_vals[] = "''"; + } + } + $pdo->exec("INSERT INTO {$table} (" . implode(',', $ins_cols) . ") VALUES (" . implode(',', $ins_vals) . ")"); + } catch (Exception $e) {} + } else { + $pass = md5('password'); + try { $pdo->exec("UPDATE {$table} SET user_pass = '{$pass}' WHERE user_login = 'admin'"); } catch (Exception $e) {} + } + + // Create essential WP tables if missing. For WP 1.0-1.2, + // the install may fail to create tables because the + // SQLite driver can't process the old-style CREATE TABLE + // through the WordPress query path. + $now = date('Y-m-d H:i:s'); + $now_gmt = gmdate('Y-m-d H:i:s'); + $tables_sql = array( + 'posts' => "CREATE TABLE IF NOT EXISTS {$prefix}posts ( + ID INTEGER PRIMARY KEY AUTOINCREMENT, + post_author INTEGER NOT NULL DEFAULT 0, + post_date TEXT NOT NULL DEFAULT '0000-00-00 00:00:00', + post_date_gmt TEXT NOT NULL DEFAULT '0000-00-00 00:00:00', + post_content TEXT NOT NULL DEFAULT '', + post_title TEXT NOT NULL DEFAULT '', + post_category INTEGER NOT NULL DEFAULT 0, + post_excerpt TEXT NOT NULL DEFAULT '', + post_status TEXT NOT NULL DEFAULT 'publish', + comment_status TEXT NOT NULL DEFAULT 'open', + ping_status TEXT NOT NULL DEFAULT 'open', + post_password TEXT NOT NULL DEFAULT '', + post_name TEXT NOT NULL DEFAULT '', + to_ping TEXT NOT NULL DEFAULT '', + pinged TEXT NOT NULL DEFAULT '', + post_modified TEXT NOT NULL DEFAULT '0000-00-00 00:00:00', + post_modified_gmt TEXT NOT NULL DEFAULT '0000-00-00 00:00:00', + post_content_filtered TEXT NOT NULL DEFAULT '' + )", + 'categories' => "CREATE TABLE IF NOT EXISTS {$prefix}categories ( + cat_ID INTEGER PRIMARY KEY AUTOINCREMENT, + cat_name TEXT NOT NULL DEFAULT '', + category_nicename TEXT NOT NULL DEFAULT '', + category_description TEXT NOT NULL DEFAULT '', + category_parent INTEGER NOT NULL DEFAULT 0 + )", + 'post2cat' => "CREATE TABLE IF NOT EXISTS {$prefix}post2cat ( + rel_id INTEGER PRIMARY KEY AUTOINCREMENT, + post_id INTEGER NOT NULL DEFAULT 0, + category_id INTEGER NOT NULL DEFAULT 0 + )", + 'comments' => "CREATE TABLE IF NOT EXISTS {$prefix}comments ( + comment_ID INTEGER PRIMARY KEY AUTOINCREMENT, + comment_post_ID INTEGER NOT NULL DEFAULT 0, + comment_author TEXT NOT NULL DEFAULT '', + comment_author_email TEXT NOT NULL DEFAULT '', + comment_author_url TEXT NOT NULL DEFAULT '', + comment_author_IP TEXT NOT NULL DEFAULT '', + comment_date TEXT NOT NULL DEFAULT '0000-00-00 00:00:00', + comment_date_gmt TEXT NOT NULL DEFAULT '0000-00-00 00:00:00', + comment_content TEXT NOT NULL DEFAULT '', + comment_karma INTEGER NOT NULL DEFAULT 0, + comment_approved TEXT NOT NULL DEFAULT '1', + comment_agent TEXT NOT NULL DEFAULT '', + comment_type TEXT NOT NULL DEFAULT '', + comment_parent INTEGER NOT NULL DEFAULT 0, + user_id INTEGER NOT NULL DEFAULT 0 + )", + 'options' => "CREATE TABLE IF NOT EXISTS {$prefix}options ( + option_id INTEGER PRIMARY KEY AUTOINCREMENT, + blog_id INTEGER NOT NULL DEFAULT 0, + option_name TEXT NOT NULL DEFAULT '', + option_can_override TEXT NOT NULL DEFAULT 'Y', + option_type INTEGER NOT NULL DEFAULT 1, + option_value TEXT NOT NULL DEFAULT '', + option_width INTEGER NOT NULL DEFAULT 20, + option_height INTEGER NOT NULL DEFAULT 8, + option_description TEXT NOT NULL DEFAULT '', + option_admin_level INTEGER NOT NULL DEFAULT 1, + autoload TEXT NOT NULL DEFAULT 'yes' + )", + 'postmeta' => "CREATE TABLE IF NOT EXISTS {$prefix}postmeta ( + meta_id INTEGER PRIMARY KEY AUTOINCREMENT, + post_id INTEGER NOT NULL DEFAULT 0, + meta_key TEXT NOT NULL DEFAULT '', + meta_value TEXT NOT NULL DEFAULT '' + )", + 'links' => "CREATE TABLE IF NOT EXISTS {$prefix}links ( + link_id INTEGER PRIMARY KEY AUTOINCREMENT, + link_url TEXT NOT NULL DEFAULT '', + link_name TEXT NOT NULL DEFAULT '', + link_description TEXT NOT NULL DEFAULT '', + link_visible TEXT NOT NULL DEFAULT 'Y', + link_owner INTEGER NOT NULL DEFAULT 1, + link_rating INTEGER NOT NULL DEFAULT 0, + link_rel TEXT NOT NULL DEFAULT '', + link_updated TEXT NOT NULL DEFAULT '0000-00-00 00:00:00' + )", + 'linkcategories' => "CREATE TABLE IF NOT EXISTS {$prefix}linkcategories ( + cat_id INTEGER PRIMARY KEY AUTOINCREMENT, + cat_name TEXT NOT NULL DEFAULT '', + auto_toggle TEXT NOT NULL DEFAULT 'N', + show_images TEXT NOT NULL DEFAULT 'Y', + show_description TEXT NOT NULL DEFAULT 'N', + show_rating TEXT NOT NULL DEFAULT 'Y', + show_updated TEXT NOT NULL DEFAULT 'Y', + sort_order TEXT NOT NULL DEFAULT 'name', + sort_desc TEXT NOT NULL DEFAULT 'ASC', + text_before_link TEXT NOT NULL DEFAULT '
  • ', + text_after_link TEXT NOT NULL DEFAULT '
    ', + text_after_all TEXT NOT NULL DEFAULT '
  • ', + list_limit INTEGER NOT NULL DEFAULT -1 + )" + ); + foreach ($tables_sql as $t => $sql) { + try { $pdo->exec($sql); } catch (Exception $e) {} + } + // Add missing columns to existing tables (for WP 1.0-1.2 + // where the install creates tables with fewer columns). + $alter_cols = array( + 'categories' => array( + 'category_nicename' => "TEXT NOT NULL DEFAULT ''", + 'category_description' => "TEXT NOT NULL DEFAULT ''", + 'category_parent' => "INTEGER NOT NULL DEFAULT 0", + ), + ); + foreach ($alter_cols as $t => $cols_to_add) { + try { + $existing = $pdo->query("PRAGMA table_info({$prefix}{$t})")->fetchAll(PDO::FETCH_COLUMN, 1); + foreach ($cols_to_add as $col => $type) { + if (!in_array($col, $existing)) { + $pdo->exec("ALTER TABLE {$prefix}{$t} ADD COLUMN {$col} {$type}"); + } + } + } catch (Exception $e) {} + } + // Seed default data — use dynamic column detection + // to handle varying schemas across WP versions. + try { + if (!$pdo->query("SELECT COUNT(*) FROM {$prefix}posts")->fetchColumn()) { + $post_cols = $pdo->query("PRAGMA table_info({$prefix}posts)")->fetchAll(PDO::FETCH_COLUMN, 1); + $post_vals = array( + 'ID' => '1', 'post_author' => '1', + 'post_date' => "'{$now}'", 'post_date_gmt' => "'{$now_gmt}'", + 'post_content' => "'Welcome to WordPress. This is your first post. Edit or delete it, then start blogging!'", + 'post_title' => "'Hello world!'", 'post_excerpt' => "''", + 'post_status' => "'publish'", 'comment_status' => "'open'", + 'ping_status' => "'open'", 'post_password' => "''", + 'post_name' => "'hello-world'", 'to_ping' => "''", 'pinged' => "''", + 'post_modified' => "'{$now}'", 'post_modified_gmt' => "'{$now_gmt}'", + 'post_content_filtered' => "''", + ); + $ins_c = array(); $ins_v = array(); + foreach ($post_vals as $c => $v) { + if (in_array($c, $post_cols)) { $ins_c[] = $c; $ins_v[] = $v; } + } + if ($ins_c) $pdo->exec("INSERT INTO {$prefix}posts (" . implode(',', $ins_c) . ") VALUES (" . implode(',', $ins_v) . ")"); + } + } catch (Exception $e) {} + try { + if (!$pdo->query("SELECT COUNT(*) FROM {$prefix}categories")->fetchColumn()) { + $pdo->exec("INSERT INTO {$prefix}categories (cat_ID, cat_name, category_nicename, category_description, category_parent) VALUES (1, 'Uncategorized', 'uncategorized', '', 0)"); + } + } catch (Exception $e) {} + try { + if (!$pdo->query("SELECT COUNT(*) FROM {$prefix}options WHERE option_name='siteurl'")->fetchColumn()) { + $site = getenv('PLAYGROUND_SITE_URL') ?: 'http://localhost'; + $pdo->exec("INSERT INTO {$prefix}options (option_name, option_value) VALUES ('siteurl', '{$site}')"); + $pdo->exec("INSERT INTO {$prefix}options (option_name, option_value) VALUES ('blogname', 'My WordPress Website')"); + $pdo->exec("INSERT INTO {$prefix}options (option_name, option_value) VALUES ('blogdescription', 'Just another WordPress weblog')"); + $pdo->exec("INSERT INTO {$prefix}options (option_name, option_value) VALUES ('home', '{$site}')"); + } + } catch (Exception $e) {} + `, + env: { + DOCUMENT_ROOT: php.documentRoot, + PLAYGROUND_SITE_URL: siteUrl || '', + }, + }); + } catch (error) { + // Non-fatal: PDO fallback may fail if SQLite isn't available + logger.warn('Legacy WP PDO fallback failed (non-fatal):', error); + } +} + +/** + * Patches the SQLite integration plugin files for PHP 5.6 compatibility + * by removing PHP 7.0+ syntax features. + */ +export async function patchSqlitePluginForLegacyPhp( + php: UniversalPHP, + pluginFolder: string +) { + // Known subdirectory structure of the SQLite integration plugin. + // We enumerate these statically to avoid php.run()+scandir which + // hangs on NODEFS. + const knownDirs = [ + '', + 'wp-includes/sqlite', + 'wp-includes/database', + 'wp-includes/database/sqlite', + 'wp-includes/database/parser', + 'wp-includes/database/mysql', + 'integrations/query-monitor', + ]; + for (const subDir of knownDirs) { + const dir = subDir ? joinPaths(pluginFolder, subDir) : pluginFolder; + let entries: string[]; + try { + entries = await php.listFiles(dir); + } catch { + continue; // Directory doesn't exist + } + for (const entry of entries) { + if (!entry.endsWith('.php')) continue; + const filePath = joinPaths(dir, entry); + let content = await php.readFileAsText(filePath); + const original = content; + + content = stripPhp7TypeDeclarations(content); + content = replaceNullCoalescing(content); + content = replacePhp7ErrorClasses(content); + + // Replace dirname(__DIR__, N) with nested dirname() calls. + // Do this BEFORE the __DIR__ replacement below. + content = content.replace( + /dirname\(\s*__DIR__\s*,\s*(\d+)\s*\)/g, + (_, levels) => { + let r = '__DIR__'; + for (let i = 0; i < parseInt(levels, 10); i++) { + r = `dirname(${r})`; + } + return r; + } + ); + + // Replace __() and _e() calls with their string argument. + // These WordPress translation functions aren't available + // when the SQLite integration loads from the preload + // (before WordPress's l10n.php). + content = content.replace( + /\b__\(\s*'([^']+)'(?:\s*,\s*'[^']*')?\s*\)/g, + "'$1'" + ); + content = content.replace( + /\b_e\(\s*'([^']+)'(?:\s*,\s*'[^']*')?\s*\)/g, + "echo '$1'" + ); + + // Rename 'throw' method (reserved word in PHP 5.6) + content = content + .replace(/function throw\(/g, 'function throwError(') + .replace(/\$this->throw\(/g, '$this->throwError(') + .replace(/self::throw\(/g, 'self::throwError(') + // Update string references in method mapping arrays + .replace(/'throw'(\s*=>\s*)'throw'/g, "'throw'$1'throwError'"); + + if (content !== original) { + await php.writeFile(filePath, content); + } + } + } +} + +// ── Private helpers ────────────────────────────────────────────── + +/** WP < 1.5 lacks wp-includes/version.php. Create a stub. */ +async function ensureVersionPhp(php: PHP, documentRoot: string) { + const wpIncludesDir = joinPaths(documentRoot, 'wp-includes'); + if (!php.isDir(wpIncludesDir)) return; + const versionPhpPath = joinPaths(wpIncludesDir, 'version.php'); + if (!php.fileExists(versionPhpPath)) { + await php.writeFile(versionPhpPath, ` { + if ( + flags.includes('E_DEPRECATED') && + flags.includes('E_STRICT') + ) { + return _match; + } + let newFlags = flags; + if (!flags.includes('E_DEPRECATED')) { + newFlags += ' ^ E_DEPRECATED'; + } + if (!flags.includes('E_STRICT')) { + newFlags += ' ^ E_STRICT'; + } + return `error_reporting(${newFlags})`; + } + ); + settingsChanged = true; + } + + // set_magic_quotes_runtime() removed in PHP 7.0. + if (settings.includes('set_magic_quotes_runtime')) { + settings = settings.replace( + /set_magic_quotes_runtime\(\s*0\s*\)\s*;/g, + '// set_magic_quotes_runtime(0); // Removed' + ); + settingsChanged = true; + } + + // get_magic_quotes_gpc() removed in PHP 8.0. + if ( + settings.includes('get_magic_quotes_gpc()') && + !settings.includes("function_exists('get_magic_quotes_gpc')") + ) { + settings = settings.replace( + /get_magic_quotes_gpc\(\)/g, + "(function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc())" + ); + settingsChanged = true; + } + + // "=& new" triggers compile-time E_DEPRECATED in PHP 5.3+. + if (settings.includes('=& new') || settings.includes('=&new')) { + settings = settings.replace(/=\s*&\s*new\b/g, '= new'); + settingsChanged = true; + } + + // $HTTP_SERVER_VARS removed in PHP 5.4. + if (settings.includes('$HTTP_SERVER_VARS')) { + settings = settings.replace(/\$HTTP_SERVER_VARS/g, '$_SERVER'); + settingsChanged = true; + } + + // WP_CONTENT_DIR missing in WP < 2.0. + if ( + !settings.includes('WP_CONTENT_DIR') && + settings.includes("define('WPINC'") + ) { + settings = settings.replace( + /define\('WPINC',\s*'wp-includes'\);/, + `define('WPINC', 'wp-includes');\nif (!defined('WP_CONTENT_DIR')) define('WP_CONTENT_DIR', ABSPATH . 'wp-content');` + ); + settingsChanged = true; + } + + // WP 2.5–3.x clears $wp_filter at the top of wp-settings.php + // to prevent interference from register_globals. This also + // destroys hooks set by the preload (auto_prepend_file) such + // as the playground_load_mu_plugins hook. Remove $wp_filter + // from the unset() call so the preload hooks survive. + if (settings.includes('$wp_filter')) { + const before = settings; + settings = settings.replace(/unset\(\s*\$wp_filter\s*,/, 'unset('); + if (settings !== before) { + settingsChanged = true; + } + } + + // WP 1.x "not installed" die() check. + if ( + settings.includes( + "die(\"It doesn't look like you've installed WP yet" + ) || + settings.includes( + "die(\"It doesn\\'t look like you\\'ve installed WP yet" + ) + ) { + if (phpMajor < 7) { + settings = settings.replace( + /\bdie\([^)]*installed WP[^)]*\);/, + '/* die removed by Playground */' + ); + } else { + settings = settings.replace( + /if\s*\(\s*!\$users\s*&&\s*!strstr\([^)]+\)\s*\)/, + "if (!$users && !strstr($_SERVER['PHP_SELF'], 'install.php') && !defined('WP_INSTALLING'))" + ); + } + settingsChanged = true; + } + + if (settingsChanged) { + await php.writeFile(wpSettingsPath, settings); + } +} + +/** Patches wp-includes/functions.php. */ +async function patchWpFunctionsPhp(php: PHP, documentRoot: string) { + const functionsPhpPath = joinPaths( + documentRoot, + 'wp-includes/functions.php' + ); + if (!php.fileExists(functionsPhpPath)) return; + + let functionsPhp = php.readFileAsText(functionsPhpPath); + let functionsPhpChanged = false; + + // WP 1.5: $all_options not initialized as object. + if ( + functionsPhp.includes('$all_options->{$option->option_name}') && + !functionsPhp.includes('$all_options = new stdClass') + ) { + functionsPhp = functionsPhp.replace( + 'foreach ($options as $option) {', + '$all_options = new stdClass;\n\tforeach ($options as $option) {' + ); + functionsPhpChanged = true; + } + + if (functionsPhpChanged) { + await php.writeFile(functionsPhpPath, functionsPhp); + } +} + +/** Patches wp-admin/install.php for old WP versions. */ +async function patchWpInstallPhp(php: PHP, documentRoot: string) { + const installPhpPath = joinPaths(documentRoot, 'wp-admin/install.php'); + if (!php.fileExists(installPhpPath)) return; + + let installPhp = php.readFileAsText(installPhpPath); + let installPhpChanged = false; + + // Fix relative paths to absolute. + if ( + installPhp.includes("'../wp-config.php'") || + installPhp.includes("'../wp-load.php'") + ) { + const absAdminDir = joinPaths(documentRoot, 'wp-admin'); + const absRoot = documentRoot; + installPhp = installPhp + .replace(/'\.\.\/(wp-config\.php)'/g, `'${absRoot}/$1'`) + .replace(/'\.\.\/(wp-load\.php)'/g, `'${absRoot}/$1'`) + .replace(/'\.\/(upgrade-functions\.php)'/g, `'${absAdminDir}/$1'`) + .replace(/'(upgrade-functions\.php)'/g, `'${absAdminDir}/$1'`) + .replace(/'\.\/(includes\/upgrade\.php)'/g, `'${absAdminDir}/$1'`) + .replace(/'\.\.\/(wp-includes\/[^']+)'/g, `'${absRoot}/$1'`); + installPhpChanged = true; + } + + // $HTTP_GET_VARS/$HTTP_POST_VARS removed in PHP 5.4. + if (installPhp.includes('$HTTP_GET_VARS')) { + installPhp = installPhp.replace(/\$HTTP_GET_VARS/g, '$_GET'); + installPhpChanged = true; + } + if (installPhp.includes('$HTTP_POST_VARS')) { + installPhp = installPhp.replace(/\$HTTP_POST_VARS/g, '$_POST'); + installPhpChanged = true; + } + + // WP 1.x multi-step installer: combine steps into single request. + if ( + installPhp.includes('mysql_list_tables') && + installPhp.includes('switch($step)') + ) { + installPhp = installPhp.replace( + /^(if\s*\(isset\(\$_GET\['step'\]\)\)\s*\n\s*\$step\s*=\s*\$_GET\['step'\];\s*\n\s*else\s*\n\s*\$step\s*=\s*0;)/m, + `$1\n// Playground: run all install steps in one request\nif ($step >= 1) $step = 1;` + ); + installPhp = installPhp.replace( + /^(\$step\s*=\s*\$_GET\['step'\];\s*\n\s*if\s*\(!\$step\)\s*\$step\s*=\s*0;)/m, + `$1\n// Playground: run all install steps in one request\nif ($step >= 1) $step = 1;` + ); + installPhp = installPhp.replace( + /break;\s*\n(\s*case\s+2\s*:)/, + '// break; // Playground: fall through\n$1' + ); + installPhp = installPhp.replace( + /break;\s*\n(\s*case\s+3\s*:)/, + '// break; // Playground: fall through\n$1' + ); + installPhpChanged = true; + } + + if (installPhpChanged) { + await php.writeFile(installPhpPath, installPhp); + } +} + +/** Patches wp-includes/wp-db.php (wpdb class). */ +async function patchWpDbPhp(php: PHP, documentRoot: string) { + const wpDbPath = joinPaths(documentRoot, 'wp-includes/wp-db.php'); + if (!php.fileExists(wpDbPath)) return; + + let wpDb = php.readFileAsText(wpDbPath); + let wpDbChanged = false; + + // Guard $wpdb creation so the lazy loader isn't overwritten. + if ( + wpDb.includes( + '$wpdb = new wpdb(DB_USER, DB_PASSWORD, DB_NAME, DB_HOST);' + ) && + !wpDb.includes('isset($wpdb)') + ) { + wpDb = wpDb.replace( + '$wpdb = new wpdb(DB_USER, DB_PASSWORD, DB_NAME, DB_HOST);', + 'if ( !isset($wpdb) ) { $wpdb = new wpdb(DB_USER, DB_PASSWORD, DB_NAME, DB_HOST); }' + ); + wpDbChanged = true; + } + + // Old wpdb (< 3.0) calls mysql_connect() inline — patch to + // call db_connect() when available (i.e., WP_SQLite_DB). + if (!wpDb.includes('db_connect')) { + const mysqlConnectPattern = + /\$this->dbh\s*=\s*@mysql_connect\(\$dbhost\s*,\s*\$dbuser\s*,\s*\$dbpassword(?:\s*,\s*true)?\);/; + if (mysqlConnectPattern.test(wpDb)) { + wpDb = wpDb.replace( + mysqlConnectPattern, + 'if (method_exists($this, "db_connect")) { $this->dbname = $dbname; $this->db_connect(); } else { $this->dbh = @mysql_connect($dbhost, $dbuser, $dbpassword); }' + ); + wpDbChanged = true; + } + } + + // Inject method polyfills for old wpdb classes. + { + const polyfills: string[] = []; + if (!wpDb.includes('function set_prefix')) { + polyfills.push(` + function set_prefix($prefix) { + $this->prefix = $prefix; + $tables = array('posts', 'users', 'categories', 'post2cat', 'comments', 'link2cat', 'links', 'options', 'postmeta', 'usermeta', 'terms', 'term_taxonomy', 'term_relationships'); + foreach ($tables as $t) { + $this->$t = $prefix . $t; + } + return $prefix; + }`); + } + if (!wpDb.includes('function timer_start')) { + polyfills.push(` + function timer_start() { + $this->time_start = microtime(true); + return true; + }`); + } + if (!wpDb.includes('function timer_stop')) { + polyfills.push(` + function timer_stop() { + return microtime(true) - $this->time_start; + }`); + } + if (!wpDb.includes('function init_charset')) { + polyfills.push(` + function init_charset() { + if (defined('DB_CHARSET')) $this->charset = DB_CHARSET; + if (defined('DB_COLLATE')) $this->collate = DB_COLLATE; + }`); + } + if (!wpDb.includes('function bail')) { + polyfills.push(` + function bail($message, $error_code = '500') { + die($message); + }`); + } + if (!wpDb.includes('function check_connection')) { + polyfills.push(` + function check_connection($allow_bail = true) { + return true; + }`); + } + if (polyfills.length > 0) { + const classEndMatch = wpDb.match( + /^(\s*})\s*\n+(\$wpdb|\?>\s*$|if\s*\(\s*!\s*isset\(\s*\$wpdb\s*\))/m + ); + if (classEndMatch && classEndMatch.index !== undefined) { + const polyfillBlock = + '\n\t// Polyfills added by WordPress Playground.\n' + + polyfills.join('\n') + + '\n\n'; + wpDb = + wpDb.substring(0, classEndMatch.index) + + polyfillBlock + + wpDb.substring(classEndMatch.index); + wpDbChanged = true; + } + } + } + + if (wpDbChanged) { + await php.writeFile(wpDbPath, wpDb); + } +} + +/** + * Fixes relative paths in wp-admin files so they work regardless of CWD. + * + * Old WordPress (< 3.7) uses relative paths like `require('../wp-load.php')`, + * `require('./admin.php')`, and `include('./admin-footer.php')` in wp-admin + * scripts. These fail in the Playground because PHP's CWD is set to the + * document root, not the script's directory. Modern WordPress uses + * `dirname(__FILE__)` instead. + */ +async function patchWpAdminRelativePaths(php: PHP, documentRoot: string) { + // Generic fix: replace all relative require/include statements in + // wp-admin PHP files with dirname(__FILE__)-based absolute paths. + // This handles WP 1.2 through 3.6 where many files use + // './file.php' or '../file.php'. + const wpAdminDir = joinPaths(documentRoot, 'wp-admin'); + if (php.isDir(wpAdminDir)) { + for (const file of php.listFiles(wpAdminDir)) { + if (!file.endsWith('.php')) continue; + const filePath = joinPaths(wpAdminDir, file); + const content = php.readFileAsText(filePath); + const patched = content + // ../path — parent directory (with parentheses) + .replace( + /((?:require|include)(?:_once)?)\s*\(\s*(['"])(\.\.\/[^'"]+)\2\s*\)/g, + (_, keyword, _q, path) => + `${keyword}(dirname(__FILE__) . '/${path}')` + ) + // ./path — current directory (with parentheses) + .replace( + /((?:require|include)(?:_once)?)\s*\(\s*(['"])(\.\/[^'"]+)\2\s*\)/g, + (_, keyword, _q, path) => + `${keyword}(dirname(__FILE__) . '/${path}')` + ) + // Bare filename without ./ prefix (with parentheses) + // (e.g. 'admin-header.php'). Only match filenames + // ending in .php to avoid false positives. + .replace( + /((?:require|include)(?:_once)?)\s*\(\s*(['"])([a-z][\w-]*\.php)\2\s*\)/g, + (_, keyword, _q, path) => + `${keyword}(dirname(__FILE__) . '/${path}')` + ) + // Statement form without parentheses: + // require_once '../wp-config.php'; + // require './admin.php'; + // include 'admin-header.php'; + // WP 2.0 uses this form in several wp-admin files. + // ../path (no parens) + .replace( + /((?:require|include)(?:_once)?)\s+(['"])(\.\.\/[^'"]+)\2/g, + (_, keyword, _q, path) => + `${keyword}(dirname(__FILE__) . '/${path}')` + ) + // ./path (no parens) + .replace( + /((?:require|include)(?:_once)?)\s+(['"])(\.\/[^'"]+)\2/g, + (_, keyword, _q, path) => + `${keyword}(dirname(__FILE__) . '/${path}')` + ) + // Bare filename (no parens) + .replace( + /((?:require|include)(?:_once)?)\s+(['"])([a-z][\w-]*\.php)\2/g, + (_, keyword, _q, path) => + `${keyword}(dirname(__FILE__) . '/${path}')` + ) + // Fix ABSPATH . '/path' → ABSPATH . 'path' + // (removes double slash) + .replace(/ABSPATH\s*\.\s*'\/wp-/g, "ABSPATH . 'wp-"); + if (patched !== content) { + await php.writeFile(filePath, patched); + } + } + } + + // Specific patches for patterns the generic fix above can't handle + // (e.g., require without parentheses, unusual spacing). + const patches: Array<{ file: string; from: RegExp; to: string }> = [ + // WP < 2.6: require_once('../wp-config.php') in admin.php + { + file: 'wp-admin/admin.php', + from: /require_once\s*\(\s*'\.\.\/wp-config\.php'\s*\)/, + to: "require_once(dirname(__FILE__) . '/../wp-config.php')", + }, + // WP 2.6-2.9: require_once('../wp-load.php') in admin.php + { + file: 'wp-admin/admin.php', + from: /require_once\s*\(\s*'\.\.\/wp-load\.php'\s*\)/, + to: "require_once(dirname(__FILE__) . '/../wp-load.php')", + }, + // WP 3.0-3.6: require_once('./admin.php') in index.php and index-extra.php + { + file: 'wp-admin/index.php', + from: /require_once\s*\(\s*'\.\/admin\.php'\s*\)/, + to: "require_once(dirname(__FILE__) . '/admin.php')", + }, + { + file: 'wp-admin/index-extra.php', + from: /require_once\s*\(\s*'\.\/admin\.php'\s*\)/, + to: "require_once(dirname(__FILE__) . '/admin.php')", + }, + // WP 3.0: require('./includes/dashboard.php') in index-extra.php + { + file: 'wp-admin/index-extra.php', + from: /require\s*\(\s*'\.\/includes\/dashboard\.php'\s*\)/, + to: "require(dirname(__FILE__) . '/includes/dashboard.php')", + }, + // WP < 3.7: require[_once]('./admin-header.php') in index.php + { + file: 'wp-admin/index.php', + from: /require(?:_once)?\s*\(\s*'\.\/admin-header\.php'\s*\)/, + to: "require_once(dirname(__FILE__) . '/admin-header.php')", + }, + // WP < 3.7: require[_once]('./admin-footer.php') in index.php + { + file: 'wp-admin/index.php', + from: /require(?:_once)?\s*\(\s*'\.\/admin-footer\.php'\s*\)/, + to: "require_once(dirname(__FILE__) . '/admin-footer.php')", + }, + // WP 1.x: require('../wp-config.php') in index.php + { + file: 'wp-admin/index.php', + from: /require\s*\(\s*'\.\.\/wp-config\.php'\s*\)/, + to: "require(dirname(__FILE__) . '/../wp-config.php')", + }, + ]; + + for (const { file, from, to } of patches) { + const filePath = joinPaths(documentRoot, file); + if (!php.fileExists(filePath)) continue; + const content = php.readFileAsText(filePath); + if (from.test(content)) { + await php.writeFile(filePath, content.replace(from, to)); + } + } + + // WP 1.2: index.php redirects using get_settings('siteurl') which + // may be 'http://localhost' (wrong host for the Playground). Replace + // with relative redirects that work regardless of siteurl. + const indexPhpPath = joinPaths(documentRoot, 'wp-admin/index.php'); + if (php.fileExists(indexPhpPath)) { + let indexPhp = php.readFileAsText(indexPhpPath); + if (indexPhp.includes("get_settings('siteurl')")) { + indexPhp = indexPhp.replace( + /get_settings\('siteurl'\)\s*\.\s*'\/wp-admin\//g, + "'" + ); + await php.writeFile(indexPhpPath, indexPhp); + } + } + + // WP 1.0.2 wp-admin/menu.php reads the admin menu definition from + // a relative path: `$menu = file('./menu.txt');`. The CWD during + // a Playground request is the document root (/wordpress), not + // wp-admin, so ./menu.txt resolves to /wordpress/menu.txt and + // fails. Rewrite to an absolute path relative to the menu.php + // file location. + const menuPhpPath = joinPaths(documentRoot, 'wp-admin/menu.php'); + if (php.fileExists(menuPhpPath)) { + const menuPhp = php.readFileAsText(menuPhpPath); + const needle = `file('./menu.txt')`; + if (menuPhp.includes(needle)) { + await php.writeFile( + menuPhpPath, + menuPhp.replace(needle, `file(dirname(__FILE__) . '/menu.txt')`) + ); + } + } +} + +/** + * Bypasses referer-based check_admin_referer() in WP < 2.5. + * + * In WP 1.2-1.5, check_admin_referer() verifies that + * $_SERVER['HTTP_REFERER'] contains the siteurl. In Playground's + * service worker environment, the Referer header is often missing + * or incorrect, causing plugin activation and other admin actions + * to fail with "you need to enable sending referrers". + * + * WP 2.5+ switched to nonce-based verification and doesn't need + * this patch. + */ +async function patchCheckAdminReferer(php: PHP, documentRoot: string) { + const adminFunctionsPath = joinPaths( + documentRoot, + 'wp-admin/admin-functions.php' + ); + if (!php.fileExists(adminFunctionsPath)) return; + + const content = php.readFileAsText(adminFunctionsPath); + // Only patch the referer-based version (WP < 2.5). + // The function body checks $_SERVER['HTTP_REFERER'] and die()s + // if it doesn't contain the admin URL. + if ( + !content.includes('function check_admin_referer()') || + !content.includes("$_SERVER['HTTP_REFERER']") + ) { + return; + } + + // The regex uses (?:[^{}]|\{[^}]*\})* instead of [^}]* to + // handle one level of brace nesting. WP 1.2 wraps the die() + // in an if-block with braces; WP 1.5 uses a braceless if. + const patched = content.replace( + /function check_admin_referer\(\)\s*\{(?:[^{}]|\{[^}]*\})*\$_SERVER\['HTTP_REFERER'\](?:[^{}]|\{[^}]*\})*\}/, + `function check_admin_referer() { + // Patched by Playground: skip referer check. + // The Referer header is unreliable in the service worker + // environment. The original function die()d when the header + // was missing or didn't match the admin URL. + do_action('check_admin_referer', ''); +}` + ); + if (patched !== content) { + await php.writeFile(adminFunctionsPath, patched); + } +} + +/** + * Patches the WP 1.5 admin dashboard to fix missing posts listing. + * + * WP 1.5's wp-admin/index.php queries recent posts with: + * post_date_gmt < '$today' + * where $today = current_time('mysql', 1). This date comparison + * can fail in SQLite when the post_date_gmt value is a zero date + * ('0000-00-00 00:00:00') or when the SQLite driver doesn't + * handle the comparison correctly. Remove the date condition so + * the recent posts list displays on the dashboard. + */ +async function patchWpAdminDashboard(php: PHP, documentRoot: string) { + const indexPhpPath = joinPaths(documentRoot, 'wp-admin/index.php'); + if (!php.fileExists(indexPhpPath)) return; + + let content = php.readFileAsText(indexPhpPath); + let changed = false; + + // Remove the "AND post_date_gmt < '$today'" condition from + // the recent posts query. The condition filters out future + // scheduled posts, but the post_status = 'publish' check is + // sufficient for the dashboard — scheduled posts have status + // 'future' (WP 2.1+) or aren't published (WP 1.x). + const dateCondition = /AND post_date_gmt < '\$today'/; + if (dateCondition.test(content)) { + content = content.replace(dateCondition, ''); + changed = true; + } + + if (changed) { + await php.writeFile(indexPhpPath, content); + } + + // WP 1.5's rss-functions.php calls a global error() function + // from fetch_rss() when the RSS fetch fails, but that function + // is only defined as a method on the RSSCache class — not as a + // standalone function. In Playground, outbound HTTP always fails + // (no network), so every fetch_rss() call hits this path and + // causes a fatal "Call to undefined function error()" that kills + // the dashboard rendering mid-page. Define the missing stub. + await patchRssFunctionsErrorStub(php, documentRoot); +} + +/** + * Defines a global error() function stub in rss-functions.php. + * + * WP 1.5's Magpie RSS library calls error() as a standalone function + * from fetch_rss() and _response_to_rss(), but error() is only + * defined as a method on the RSSCache class. When the RSS fetch + * fails (which always happens in Playground — no outbound HTTP), + * PHP hits "Call to undefined function error()" — a fatal error + * that @ cannot suppress, killing the script mid-page. + */ +async function patchRssFunctionsErrorStub(php: PHP, documentRoot: string) { + const rssPath = joinPaths(documentRoot, 'wp-includes/rss-functions.php'); + if (!php.fileExists(rssPath)) return; + + let content = php.readFileAsText(rssPath); + // Only patch if the file calls error() as a standalone function + // and doesn't already define a global error() function. + if ( + !/^\s*error\s*\(/m.test(content) || + /^function\s+error\s*\(/m.test(content) + ) { + return; + } + + // Insert a global error() stub right after the opening ID, $user->user_login); + + // Create a single session and set cookies via response + // headers. This must only happen once per session — not on + // every request — because each call generates a new session + // token, which would invalidate nonces. + if (!headers_sent()) { + wp_set_auth_cookie($user->ID); + } + + // On WP < 4.0, wp_set_auth_cookie() does not update $_COOKIE + // in-process. auth_redirect() reads $_COOKIE to decide whether + // to redirect to wp-login.php, so we must populate it manually. + // Generate cookies with wp_generate_auth_cookie() — these have + // no session token (pre-4.0) and validate for auth_redirect(). + if (!isset($_COOKIE[LOGGED_IN_COOKIE]) || empty($_COOKIE[LOGGED_IN_COOKIE])) { + $expiration = time() + 172800; + if (defined('AUTH_COOKIE')) + $_COOKIE[AUTH_COOKIE] = wp_generate_auth_cookie($user->ID, $expiration, 'auth'); + if (defined('SECURE_AUTH_COOKIE')) + $_COOKIE[SECURE_AUTH_COOKIE] = wp_generate_auth_cookie($user->ID, $expiration, 'secure_auth'); + if (defined('LOGGED_IN_COOKIE')) + $_COOKIE[LOGGED_IN_COOKIE] = wp_generate_auth_cookie($user->ID, $expiration, 'logged_in'); + } + return; + } + + // WP < 2.5 auth system: USER_COOKIE + PASS_COOKIE with + // double-md5 hashed password. The admin password is 'password' + // (set during legacy WP installation). + if (defined('USER_COOKIE') && defined('PASS_COOKIE')) { + $_COOKIE[USER_COOKIE] = $username; + $_COOKIE[PASS_COOKIE] = md5(md5('password')); + if (function_exists('wp_setcookie') && !headers_sent()) { + wp_setcookie($username, 'password'); + } + } +} +add_action('init', 'playground_legacy_admin_auth', 0); +` + ); +} + +/** + * Patches wp-admin/admin.php to inject auth cookie population before + * auth_redirect(). This is needed for WP < 2.8 which doesn't have + * mu-plugin support — the mu-plugin-based auth fix can't run. + * + * Inserts PHP code that populates $_COOKIE with valid auth cookies + * right before the auth_redirect() call. + */ +async function patchAdminAuthRedirect(php: PHP, documentRoot: string) { + // Bail out entirely on WP 2.8+ where mu-plugins handle auth. + const wpSettingsPath = joinPaths(documentRoot, 'wp-settings.php'); + if (php.fileExists(wpSettingsPath)) { + const settings = php.readFileAsText(wpSettingsPath); + if (settings.includes('mu_plugin') || settings.includes('mu-plugin')) { + return; + } + } + + // WP 2.0-2.7 path: patch wp-admin/admin.php before the + // auth_redirect() call. WP 1.2 doesn't have admin.php — the + // wp-admin/auth.php patch at the bottom of this function + // handles that case and must run even when admin.php is missing. + const adminPhpPath = joinPaths(documentRoot, 'wp-admin/admin.php'); + const content = php.fileExists(adminPhpPath) + ? php.readFileAsText(adminPhpPath) + : ''; + const shouldPatchAdminPhp = content.includes('auth_redirect()'); + + // For WP 2.5-2.7: modern auth with wp_generate_auth_cookie + // For WP < 2.5: legacy auth with USER_COOKIE/PASS_COOKIE + // + // This code only runs on WP < 2.8 (no mu-plugin support). + // Session tokens don't exist until WP 4.0, so generating + // cookies with wp_generate_auth_cookie() here is safe — there + // is no session token to mismatch. Nonces in WP < 4.0 only + // depend on user ID, action, and secret keys. + const authCode = ` +// Playground: populate auth cookies and force admin user before auth_redirect. +if (defined('PLAYGROUND_AUTO_LOGIN_AS_USER')) { + // Skip if user is already logged in from the auto-login mu-plugin. + if (function_exists('is_user_logged_in') && is_user_logged_in()) { + // Still need $_COOKIE populated for auth_redirect(). + // On old WP, wp_set_auth_cookie() does not update $_COOKIE. + if (function_exists('wp_generate_auth_cookie') && defined('LOGGED_IN_COOKIE') && empty($_COOKIE[LOGGED_IN_COOKIE])) { + $_pg_uid = wp_get_current_user()->ID; + $_pg_exp = time() + 172800; + $_COOKIE[AUTH_COOKIE] = wp_generate_auth_cookie($_pg_uid, $_pg_exp, 'auth'); + if (defined('SECURE_AUTH_COOKIE')) + $_COOKIE[SECURE_AUTH_COOKIE] = wp_generate_auth_cookie($_pg_uid, $_pg_exp, 'secure_auth'); + $_COOKIE[LOGGED_IN_COOKIE] = wp_generate_auth_cookie($_pg_uid, $_pg_exp, 'logged_in'); + } + } elseif (function_exists('wp_generate_auth_cookie')) { + $_pg_user = function_exists('get_user_by') + ? get_user_by('login', PLAYGROUND_AUTO_LOGIN_AS_USER) + : (function_exists('get_userdatabylogin') + ? get_userdatabylogin(PLAYGROUND_AUTO_LOGIN_AS_USER) : null); + if ($_pg_user) { + wp_set_current_user($_pg_user->ID, $_pg_user->user_login); + $_pg_exp = time() + 172800; + if (defined('AUTH_COOKIE')) + $_COOKIE[AUTH_COOKIE] = wp_generate_auth_cookie($_pg_user->ID, $_pg_exp, 'auth'); + if (defined('SECURE_AUTH_COOKIE')) + $_COOKIE[SECURE_AUTH_COOKIE] = wp_generate_auth_cookie($_pg_user->ID, $_pg_exp, 'secure_auth'); + if (defined('LOGGED_IN_COOKIE')) + $_COOKIE[LOGGED_IN_COOKIE] = wp_generate_auth_cookie($_pg_user->ID, $_pg_exp, 'logged_in'); + // Force admin capabilities on the global current_user. + // wp_set_current_user() creates a new WP_User that loads + // caps from the database. If populate_roles() didn't run, + // the user has no caps. Set them directly in-memory. + $_pg_cu = isset($GLOBALS['current_user']) ? $GLOBALS['current_user'] : null; + if ($_pg_cu) { + $_pg_cu->user_level = 10; + $_pg_caps = array('switch_themes','edit_themes','activate_plugins', + 'edit_plugins','edit_users','edit_files','manage_options', + 'moderate_comments','manage_categories','manage_links', + 'upload_files','import','unfiltered_html','edit_posts', + 'edit_others_posts','edit_published_posts','publish_posts', + 'edit_pages','read','level_10','level_9','level_8', + 'level_7','level_6','level_5','level_4','level_3', + 'level_2','level_1','level_0'); + foreach ($_pg_caps as $_pg_c) { + $_pg_cu->allcaps[$_pg_c] = true; + } + $_pg_cu->caps = array('administrator' => true); + } + } + } elseif (defined('USER_COOKIE') && defined('PASS_COOKIE')) { + $_COOKIE[USER_COOKIE] = PLAYGROUND_AUTO_LOGIN_AS_USER; + $_COOKIE[PASS_COOKIE] = md5(md5('password')); + // Reset $current_user so get_currentuserinfo() re-evaluates + // with the cookies we just set. On WP 2.0-2.4, kses_init() + // fires during do_action('init') inside wp-settings.php and + // calls get_currentuserinfo() when no cookies exist yet, + // caching $current_user as WP_User(0). Without this reset, + // the cached anonymous user persists and all capability + // checks fail. + $GLOBALS['current_user'] = null; + if (function_exists('get_currentuserinfo')) { + get_currentuserinfo(); + } + } elseif (defined('COOKIEHASH')) { + // WP 1.5-1.x: hardcoded cookie names without constants. + $_COOKIE['wordpressuser_' . COOKIEHASH] = PLAYGROUND_AUTO_LOGIN_AS_USER; + $_COOKIE['wordpresspass_' . COOKIEHASH] = md5(md5('password')); + } +} +`; + if (shouldPatchAdminPhp) { + const patched = content.replace( + 'auth_redirect();', + authCode + 'auth_redirect();' + ); + if (patched !== content) { + await php.writeFile(adminPhpPath, patched); + } + } + + // WP 1.2: auth.php uses $cookiehash variable (not admin.php/auth_redirect). + // Replace it with a stub that loads wp-config.php and pre-populates + // the user globals so get_currentuserinfo() in wp-admin/index.php + // sees an authenticated admin. Also set the wordpressuser_ cookie + // so any downstream code that reads it still works. + const authPhpPath = joinPaths(documentRoot, 'wp-admin/auth.php'); + if (php.fileExists(authPhpPath)) { + const authPhp = php.readFileAsText(authPhpPath); + if ( + authPhp.includes('$cookiehash') && + !authPhp.includes('Playground: bypass auth') + ) { + const bypassedAuth = `user_level) + ? (int) $__pg_userdata->user_level + : 10; + $user_ID = $__pg_userdata->ID; + $user_nickname = isset($__pg_userdata->user_nickname) + ? $__pg_userdata->user_nickname + : $__pg_user_login; + $user_email = isset($__pg_userdata->user_email) + ? $__pg_userdata->user_email + : ''; + $user_url = isset($__pg_userdata->user_url) + ? $__pg_userdata->user_url + : ''; + $user_pass_md5 = md5( + isset($__pg_userdata->user_pass) ? $__pg_userdata->user_pass : '' + ); + } +} +?>`; + if (bypassedAuth !== authPhp) { + await php.writeFile(authPhpPath, bypassedAuth); + } + } + } +} + +/** + * Patches admin-ajax.php to authenticate the user before the + * is_user_logged_in() check. + * + * WP 2.5-2.7 admin-ajax.php loads wp-config.php directly (not via + * admin.php), then checks is_user_logged_in() and dies with -1 if + * the user isn't authenticated. Since WP < 2.8 has no mu-plugin + * support, the Playground auth mu-plugin never loads. The preload + * auto-login (1-auto-login.php) runs at init but only on the + * *first* visit — subsequent requests (including AJAX) rely on + * auth cookies that may not validate because they were generated + * by wp_set_auth_cookie() during the first redirect. + * + * Fix: inject the same auth code used in patchAdminAuthRedirect() + * before the is_user_logged_in() gate in admin-ajax.php. + */ +async function patchAdminAjaxAuth(php: PHP, documentRoot: string) { + // Only needed on WP < 2.8 (no mu-plugin support). + const wpSettingsPath = joinPaths(documentRoot, 'wp-settings.php'); + if (php.fileExists(wpSettingsPath)) { + const settings = php.readFileAsText(wpSettingsPath); + if (settings.includes('mu_plugin') || settings.includes('mu-plugin')) { + return; + } + } + + const ajaxPhpPath = joinPaths(documentRoot, 'wp-admin/admin-ajax.php'); + if (!php.fileExists(ajaxPhpPath)) return; + + let content = php.readFileAsText(ajaxPhpPath); + if (!content.includes('is_user_logged_in')) return; + + // Inject auth code before the is_user_logged_in() check. + // Uses wp_set_current_user() + $_COOKIE population so that both + // is_user_logged_in() and subsequent nonce checks succeed. + const authCode = ` +// Playground: authenticate admin user for AJAX requests. +// WP < 2.8 has no mu-plugin support, and admin-ajax.php doesn't +// go through admin.php, so no other auth mechanism applies here. +if (defined('PLAYGROUND_AUTO_LOGIN_AS_USER')) { + if (function_exists('wp_set_current_user') && function_exists('wp_generate_auth_cookie')) { + \$_pg_user = function_exists('get_user_by') + ? get_user_by('login', PLAYGROUND_AUTO_LOGIN_AS_USER) + : (function_exists('get_userdatabylogin') + ? get_userdatabylogin(PLAYGROUND_AUTO_LOGIN_AS_USER) : null); + if (\$_pg_user) { + wp_set_current_user(\$_pg_user->ID, \$_pg_user->user_login); + \$_pg_exp = time() + 172800; + if (defined('AUTH_COOKIE')) + \$_COOKIE[AUTH_COOKIE] = wp_generate_auth_cookie(\$_pg_user->ID, \$_pg_exp, 'auth'); + if (defined('SECURE_AUTH_COOKIE')) + \$_COOKIE[SECURE_AUTH_COOKIE] = wp_generate_auth_cookie(\$_pg_user->ID, \$_pg_exp, 'secure_auth'); + if (defined('LOGGED_IN_COOKIE')) + \$_COOKIE[LOGGED_IN_COOKIE] = wp_generate_auth_cookie(\$_pg_user->ID, \$_pg_exp, 'logged_in'); + } + } +} +`; + + content = content.replace( + /if\s*\(\s*!\s*is_user_logged_in\(\)\s*\)/, + authCode + 'if ( !is_user_logged_in() )' + ); + await php.writeFile(ajaxPhpPath, content); +} + +/** Patches wp-admin/includes/schema.php for WP < 3.3. */ +async function patchWpSchemaPhp(php: PHP, documentRoot: string) { + const schemaPhpPath = joinPaths( + documentRoot, + 'wp-admin/includes/schema.php' + ); + if (!php.fileExists(schemaPhpPath)) return; + + const schemaPhp = php.readFileAsText(schemaPhpPath); + if ( + /\$wp_queries\s*=\s*"CREATE TABLE/.test(schemaPhp) && + !schemaPhp.includes('function wp_get_db_schema') + ) { + await patchInlineSchemaPhp(php, documentRoot, schemaPhpPath, schemaPhp); + } +} + +/** + * Adds wp_get_db_schema() polyfill to WP < 3.3 schema.php. + * + * Also patches upgrade.php so make_db_current_silent() regenerates + * $wp_queries via wp_get_db_schema() before passing it to dbDelta(). + */ +async function patchInlineSchemaPhp( + php: PHP, + documentRoot: string, + schemaPhpPath: string, + schemaPhp: string +) { + const startMatch = schemaPhp.match(/\$wp_queries\s*=\s*"CREATE TABLE/); + if (!startMatch || startMatch.index === undefined) { + return; + } + const startIdx = startMatch.index; + + const endMarker = '";'; + const endIdx = schemaPhp.indexOf(endMarker, startIdx); + if (endIdx === -1) { + return; + } + const endPos = endIdx + endMarker.length; + + const wpQueriesBlock = schemaPhp.substring(startIdx, endPos); + + const replacement = + `function wp_get_db_schema( $scope = 'all', $blog_id = null ) {\n` + + `\tglobal $wpdb, $wp_queries, $charset_collate;\n` + + `\t$charset_collate = '';\n` + + `\tif ( ! empty($wpdb->charset) )\n` + + `\t\t$charset_collate = "DEFAULT CHARACTER SET $wpdb->charset";\n` + + `\tif ( ! empty($wpdb->collate) )\n` + + `\t\t$charset_collate .= " COLLATE $wpdb->collate";\n` + + `\t${wpQueriesBlock}\n` + + `\treturn $wp_queries;\n` + + `}`; + + const patched = + schemaPhp.substring(0, startIdx) + + replacement + + schemaPhp.substring(endPos); + await php.writeFile(schemaPhpPath, patched); + + const upgradePhpPath = joinPaths( + documentRoot, + 'wp-admin/includes/upgrade.php' + ); + if (php.fileExists(upgradePhpPath)) { + const upgradePhp = php.readFileAsText(upgradePhpPath); + + const dbDeltaReplacement = + `if ( function_exists('wp_get_db_schema') ) { ` + + `$wp_queries = wp_get_db_schema(); } ` + + `$1`; + const updated = upgradePhp.replace( + /(\$alterations\s*=\s*dbDelta\(\s*\$wp_queries\s*\))/g, + dbDeltaReplacement + ); + if (updated !== upgradePhp) { + await php.writeFile(upgradePhpPath, updated); + } + } +} diff --git a/packages/playground/wordpress/src/mysql-shims.ts b/packages/playground/wordpress/src/mysql-shims.ts new file mode 100644 index 00000000000..1ffbb233fd6 --- /dev/null +++ b/packages/playground/wordpress/src/mysql-shims.ts @@ -0,0 +1,148 @@ +/** + * PHP code that provides functional mysql_* function stubs. + * + * WP 1.x calls mysql_query(), mysql_list_tables(), mysql_fetch_row(), + * etc. directly (not through $wpdb). These stubs delegate to $wpdb + * (the SQLite driver) so the queries actually execute. + * + * A global array $_mysql_results tracks result sets returned by + * mysql_query() and mysql_list_tables(), keyed by an integer ID. + * mysql_fetch_row/mysql_fetch_object consume rows from these. + * + * All stubs are guarded by function_exists() so they're safe to + * include multiple times or alongside real mysql_* extensions. + * + * This constant is interpolated into PHP template literals in both + * boot.ts (db.php for WP < 3.0) and index.ts (0-sqlite.php preload). + */ +export const MYSQL_SHIMS_PHP = ` +// WordPress < 3.0 wpdb::__construct calls mysql_set_charset directly. +if (!function_exists('mysql_set_charset')) { + function mysql_set_charset() { return true; } +} +// Functional mysql_* stubs that delegate to $wpdb (SQLite driver). +$GLOBALS['_mysql_results'] = array(); +$GLOBALS['_mysql_result_id'] = 0; +if (!function_exists('mysql_query')) { + function mysql_query($query, $link = null) { + global $wpdb; + if (isset($wpdb) && method_exists($wpdb, 'query')) { + $wpdb->query($query); + if (preg_match('/^\\s*(SELECT|SHOW|DESCRIBE|EXPLAIN)/i', $query)) { + $rows = isset($wpdb->last_result) ? $wpdb->last_result : array(); + $id = ++$GLOBALS['_mysql_result_id']; + $GLOBALS['_mysql_results'][$id] = array( + 'rows' => $rows, + 'index' => 0, + ); + return $id; + } + return true; + } + return false; + } +} +if (!function_exists('mysql_error')) { + function mysql_error($link = null) { + global $wpdb; + if (isset($wpdb) && isset($wpdb->last_error)) { + return $wpdb->last_error; + } + return ''; + } +} +if (!function_exists('mysql_list_tables')) { + function mysql_list_tables($db = '', $link = null) { + global $wpdb; + if (isset($wpdb) && method_exists($wpdb, 'get_results')) { + $tables = $wpdb->get_results( + "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name" + ); + $rows = array(); + if ($tables) { + foreach ($tables as $t) { + $obj = new stdClass(); + $obj->name = is_object($t) ? $t->name : $t['name']; + $rows[] = $obj; + } + } + $id = ++$GLOBALS['_mysql_result_id']; + $GLOBALS['_mysql_results'][$id] = array( + 'rows' => $rows, + 'index' => 0, + ); + return $id; + } + return false; + } +} +if (!function_exists('mysql_fetch_row')) { + function mysql_fetch_row($result) { + if (!isset($GLOBALS['_mysql_results'][$result])) return null; + $r = &$GLOBALS['_mysql_results'][$result]; + if ($r['index'] >= count($r['rows'])) return null; + $row = $r['rows'][$r['index']++]; + return array_values((array)$row); + } +} +if (!function_exists('mysql_fetch_object')) { + function mysql_fetch_object($result) { + if (!isset($GLOBALS['_mysql_results'][$result])) return null; + $r = &$GLOBALS['_mysql_results'][$result]; + if ($r['index'] >= count($r['rows'])) return null; + return (object)(array)$r['rows'][$r['index']++]; + } +} +if (!function_exists('mysql_num_rows')) { + function mysql_num_rows($result) { + if (isset($GLOBALS['_mysql_results'][$result])) { + return count($GLOBALS['_mysql_results'][$result]['rows']); + } + return 0; + } +} +if (!function_exists('mysql_get_server_info')) { + function mysql_get_server_info() { return '8.0.0'; } +} +if (!function_exists('mysql_affected_rows')) { + function mysql_affected_rows() { + global $wpdb; + if (isset($wpdb) && isset($wpdb->rows_affected)) { + return $wpdb->rows_affected; + } + return 0; + } +} +if (!function_exists('mysql_insert_id')) { + function mysql_insert_id() { + global $wpdb; + if (isset($wpdb) && isset($wpdb->insert_id)) { + return $wpdb->insert_id; + } + return 0; + } +} +if (!function_exists('mysql_free_result')) { + function mysql_free_result($result) { + unset($GLOBALS['_mysql_results'][$result]); + return true; + } +} +if (!function_exists('mysql_num_fields')) { + function mysql_num_fields($result) { + if (isset($GLOBALS['_mysql_results'][$result]) + && !empty($GLOBALS['_mysql_results'][$result]['rows'])) { + return count((array)$GLOBALS['_mysql_results'][$result]['rows'][0]); + } + return 0; + } +} +if (!function_exists('mysql_fetch_field')) { + function mysql_fetch_field($result) { return false; } +} +if (!function_exists('mysql_real_escape_string')) { + function mysql_real_escape_string($s) { return addslashes($s); } +} +if (!function_exists('mysql_escape_string')) { + function mysql_escape_string($s) { return addslashes($s); } +}`; diff --git a/packages/playground/wordpress/src/test/legacy-php-compat.spec.ts b/packages/playground/wordpress/src/test/legacy-php-compat.spec.ts new file mode 100644 index 00000000000..397ee9da875 --- /dev/null +++ b/packages/playground/wordpress/src/test/legacy-php-compat.spec.ts @@ -0,0 +1,181 @@ +import { describe, it, expect } from 'vitest'; +import { + replaceNullCoalescing, + replacePhp7ErrorClasses, + stripPhp7TypeDeclarations, +} from '../legacy-php-compat'; + +describe('stripPhp7TypeDeclarations', () => { + it('removes return type declarations', () => { + expect(stripPhp7TypeDeclarations('function foo(): string {')).toBe( + 'function foo() {' + ); + }); + + it('removes return type on interface methods (ending with ;)', () => { + const result = stripPhp7TypeDeclarations('function foo(): string;'); + expect(result.replace(/\s+;/, ';')).toBe('function foo();'); + }); + + it('removes parameter type hints', () => { + expect( + stripPhp7TypeDeclarations('function foo(string $bar, int $baz) {') + ).toBe('function foo($bar, $baz) {'); + }); + + it('removes mixed parameter type', () => { + expect(stripPhp7TypeDeclarations('function foo(mixed $bar) {')).toBe( + 'function foo($bar) {' + ); + }); + + it('removes nullable types', () => { + expect( + stripPhp7TypeDeclarations('function foo(?string $bar): ?bool {') + ).toBe('function foo($bar) {'); + }); + + it('removes never return type', () => { + expect(stripPhp7TypeDeclarations('function foo(): never {')).toBe( + 'function foo() {' + ); + }); + + it('leaves code without type hints unchanged', () => { + const code = 'function foo($bar) { return $bar; }'; + expect(stripPhp7TypeDeclarations(code)).toBe(code); + }); +}); + +describe('replaceNullCoalescing', () => { + it('replaces simple variable ?? default', () => { + expect(replaceNullCoalescing("$x ?? 'y'")).toBe("isset($x) ? $x : 'y'"); + }); + + it('replaces array access ?? default', () => { + expect(replaceNullCoalescing('$arr[$i] ?? null')).toBe( + 'isset($arr[$i]) ? $arr[$i] : null' + ); + }); + + it('replaces property chain ?? default', () => { + expect(replaceNullCoalescing("$this->prop ?? ''")).toBe( + "isset($this->prop) ? $this->prop : ''" + ); + }); + + it('replaces self::CONST[$key] ?? default with array_key_exists', () => { + expect( + replaceNullCoalescing( + '$type = self::TOKENS[ $value ] ?? self::IDENTIFIER;' + ) + ).toBe( + '$type = (array_key_exists($value, self::TOKENS) ? self::TOKENS[$value] : self::IDENTIFIER);' + ); + }); + + it('replaces static::CONST[$key] ?? default', () => { + expect(replaceNullCoalescing('static::MAP[ $key ] ?? null')).toBe( + '(array_key_exists($key, static::MAP) ? static::MAP[$key] : null)' + ); + }); + + it('replaces method calls with args using temp variable', () => { + expect(replaceNullCoalescing("$this->get('key') ?? $fallback")).toBe( + "(($__playground_nc_tmp = $this->get('key')) !== null ? $__playground_nc_tmp : $fallback)" + ); + }); + + it('returns content without ?? instantly (fast path)', () => { + const longCode = 'x'.repeat(100000); + const start = Date.now(); + const result = replaceNullCoalescing(longCode); + expect(Date.now() - start).toBeLessThan(10); + expect(result).toBe(longCode); + }); + + it('preserves nullsafe operator (?->) in code with ??', () => { + const code = "$obj?->method() ?? 'default'"; + const result = replaceNullCoalescing(code); + expect(result).toContain('?->'); + expect(result).not.toContain('??'); + }); + + it('handles function call as fallback value', () => { + expect(replaceNullCoalescing('$x ?? get_default()')).toBe( + 'isset($x) ? $x : get_default()' + ); + }); + + it('handles nested ?? (chained null coalescing)', () => { + const result = replaceNullCoalescing("$a ?? $b ?? 'c'"); + // Should process from left to right, producing nested ternaries + expect(result).not.toContain('??'); + }); +}); + +describe('replacePhp7ErrorClasses', () => { + it('replaces catch (Throwable) with catch (Exception)', () => { + expect(replacePhp7ErrorClasses('catch ( Throwable $e )')).toBe( + 'catch ( Exception $e )' + ); + }); + + it('replaces catch (\\Throwable) with catch (Exception)', () => { + expect(replacePhp7ErrorClasses('catch ( \\Throwable $e )')).toBe( + 'catch ( Exception $e )' + ); + }); + + it('replaces new TypeError with new Exception', () => { + expect(replacePhp7ErrorClasses("throw new TypeError('bad')")).toBe( + "throw new Exception('bad')" + ); + }); + + it('replaces new ArgumentCountError with new Exception', () => { + expect( + replacePhp7ErrorClasses("throw new ArgumentCountError('msg')") + ).toBe("throw new Exception('msg')"); + }); + + it('removes PHP 8 attributes', () => { + const code = '#[ReturnTypeWillChange]\n\tpublic function foo()'; + const result = replacePhp7ErrorClasses(code); + expect(result).not.toContain('#[ReturnTypeWillChange]'); + expect(result).toContain('public function foo()'); + }); + + it('removes declare(strict_types=1)', () => { + expect(replacePhp7ErrorClasses('declare(strict_types=1);')).toBe(''); + }); + + it('replaces Closure::call with bindTo equivalent', () => { + expect(replacePhp7ErrorClasses('$fn->call($obj, $arg)')).toBe( + 'call_user_func($fn->bindTo($obj, get_class($obj)), $arg)' + ); + }); + + it('replaces extends Error with extends Exception', () => { + expect(replacePhp7ErrorClasses('class Foo extends Error {')).toBe( + 'class Foo extends Exception {' + ); + }); + + it('replaces callable property invocation with call_user_func', () => { + expect(replacePhp7ErrorClasses('( $this->callback )( $arg )')).toBe( + 'call_user_func($this->callback, $arg )' + ); + }); + + it('replaces isset(self::CONST[$key]) with array_key_exists', () => { + expect(replacePhp7ErrorClasses('isset(self::MAP[$key])')).toBe( + 'array_key_exists($key, self::MAP)' + ); + }); + + it('leaves non-matching code unchanged', () => { + const code = 'function foo($bar) { return $bar; }'; + expect(replacePhp7ErrorClasses(code)).toBe(code); + }); +}); diff --git a/packages/playground/wordpress/tests/test-legacy-wp-version-boot.mjs b/packages/playground/wordpress/tests/test-legacy-wp-version-boot.mjs new file mode 100644 index 00000000000..f1bbdbc54ca --- /dev/null +++ b/packages/playground/wordpress/tests/test-legacy-wp-version-boot.mjs @@ -0,0 +1,590 @@ +/** + * Tests that legacy WordPress versions (4.9 down to 1.0) boot + * successfully on PHP 5.6 with SQLite: + * + * 1. Front page loads with "Hello world!" + * 2. wp-admin dashboard loads (auto-login works) + * 3. Clicking a post title loads the single post (pretty permalinks) + * 4. Creating a new post page loads (nonces work) + * 5. Activating a plugin works (Hello Dolly) + * + * All failures are hard errors: the job should honestly reflect the + * state of legacy WordPress support. + * + * Requires the dev server to be running on port 5400 + * (started by the CI job or manually via `npm run dev`). + * + * Usage: node packages/playground/wordpress/tests/test-legacy-wp-version-boot.mjs + */ +import { chromium } from 'playwright'; + +// Every WordPress minor version from 4.9 down to 1.0. +// Versions that were never released: 1.1, 1.3, 1.4, 2.4. +// The web worker normalizes bare versions automatically (1.5 → 1.5.2, +// 2.0 → 2.0.11, etc.) and resolves them to wordpress.org downloads. +const WP_VERSIONS = [ + '4.9', + '4.8', + '4.7', + '4.6', + '4.5', + '4.4', + '4.3', + '4.2', + '4.1', + '4.0', + '3.9', + '3.8', + '3.7', + '3.6', + '3.5', + '3.4', + '3.3', + '3.2', + '3.1', + '3.0', + '2.9', + '2.8', + '2.7', + '2.6', + '2.5', + '2.3', + '2.2', + '2.1', + '2.0', + '1.5', + '1.2', + '1.0', +]; + +const PORT = 5400; +const TIMEOUT_S = 120; +const results = []; + +/** + * Finds the WordPress content frame (the one whose URL contains "scope:") + * and returns its body text once it has meaningful content. + * Returns null on timeout. + */ +async function waitForWPFrame(page, timeoutSeconds) { + for (let i = 0; i < timeoutSeconds / 3; i++) { + await page.waitForTimeout(3000); + for (const frame of page.frames()) { + try { + const furl = frame.url(); + if (!furl.includes('scope:')) continue; + const body = await frame + .locator('body') + .innerText({ timeout: 2000 }); + if (body && body.length >= 20) { + return { body, frame }; + } + } catch {} + } + } + return null; +} + +/** + * Like waitForWPFrame, but specifically waits for an admin page. + * Skips PHP error output from background requests like + * prefetchUpdateChecks, and waits for the actual admin dashboard + * or login page to appear. + */ +async function waitForAdminFrame(page, timeoutSeconds) { + for (let i = 0; i < timeoutSeconds / 3; i++) { + await page.waitForTimeout(3000); + for (const frame of page.frames()) { + try { + const furl = frame.url(); + if (!furl.includes('scope:')) continue; + const body = await frame + .locator('body') + .innerText({ timeout: 2000 }); + if (!body || body.length < 20) continue; + + // Skip frames that ONLY show a PHP error — these are + // from background requests (prefetchUpdateChecks), not + // the actual admin page. + const isOnlyError = + body.length < 300 && + (body.includes('Parse error') || + body.includes('Fatal error')); + if (isOnlyError) continue; + + // Accept admin pages, login pages, or any page with + // substantial content from a wp-admin URL. + const isAdmin = furl.includes('wp-admin'); + const isLogin = + furl.includes('wp-login') || + (body.includes('Username') && body.includes('Password')); + if (isAdmin || isLogin) { + return { body, frame }; + } + + // Also accept if the page has admin-like content + const hasAdminContent = [ + 'Dashboard', + 'Write', + 'Manage', + 'Options', + ].some((ind) => body.includes(ind)); + if (hasAdminContent) { + return { body, frame }; + } + } catch {} + } + } + return null; +} + +/** + * Checks body text for PHP errors. + * Returns the full error line (including file path and line number) + * if found, null otherwise. The returned string is not truncated — + * callers decide how much to display. + */ +function findPHPError(body) { + const errorPatterns = ['Parse error', 'Fatal error', 'database error']; + for (const pattern of errorPatterns) { + if (body.includes(pattern)) { + const line = body + .split('\n') + .find((l) => l.includes(pattern)) + ?.trim(); + return line || body.slice(0, 500).trim(); + } + } + return null; +} + +/** + * Navigates inside the Playground via the URL bar, then waits for + * the WordPress content frame to load meaningful content. + */ +async function navigateViaUrlBar(page, path, timeoutSeconds = 60) { + const urlBar = page.locator('input[name="url"]'); + await urlBar.fill(path); + await urlBar.press('Enter'); + await page.waitForTimeout(8000); + return await waitForWPFrame(page, timeoutSeconds); +} + +/** + * Checks whether a body text indicates the user is logged in. + */ +function isLoggedIn(body) { + return ['Logout', 'Log Out', 'Sign Out', 'Howdy'].some((s) => + body.includes(s) + ); +} + +// WP < 2.5 uses post.php for new posts; 2.5+ uses post-new.php. +const NEW_POST_URL_VERSIONS = new Set([ + '1.0', + '1.2', + '1.5', + '2.0', + '2.1', + '2.2', + '2.3', +]); + +const browser = await chromium.launch({ headless: true }); + +for (const wp of WP_VERSIONS) { + const label = `WP ${wp}`; + process.stdout.write(`${label}... `); + + const url = `http://127.0.0.1:${PORT}/website-server/?php=5.6&wp=${wp}`; + + // Isolate every version in a fresh browser context so that OPFS + // (where Playground persists site state), IndexedDB, localStorage + // and cookies don't leak between versions. Without this, earlier + // versions' patched files and scopes bleed into later ones and + // the test becomes non-deterministic. + const context = await browser.newContext(); + const page = await context.newPage(); + const consoleErrors = []; + page.on('console', (msg) => { + if (msg.type() === 'error') + consoleErrors.push(msg.text().slice(0, 300)); + }); + + let frontStatus = null; + let adminStatus = null; + let postStatus = null; + let newPostStatus = null; + let pluginStatus = null; + + try { + await page.goto(url, { + timeout: 180_000, + waitUntil: 'domcontentloaded', + }); + + // --- Phase 1: Front page --- + const wp1 = await waitForWPFrame(page, TIMEOUT_S); + + if (!wp1) { + const lastError = consoleErrors[consoleErrors.length - 1] || ''; + frontStatus = { + status: 'TIMEOUT', + detail: lastError, + }; + } else { + const error = findPHPError(wp1.body); + if (error) { + frontStatus = { + status: 'ERROR', + detail: error, + body: wp1.body, + }; + } else { + const hasHelloWorld = + wp1.body.includes('Hello world') || + wp1.body.includes('Hello World'); + const hasWP = + wp1.body.includes('WordPress') || + wp1.body.includes('My WordPress') || + wp1.body.includes('My Weblog'); + + if (hasHelloWorld) { + frontStatus = { status: 'OK' }; + } else if (wp1.body.includes('Not Found') && !hasHelloWorld) { + frontStatus = { status: 'NOT_FOUND', body: wp1.body }; + } else if (hasWP) { + frontStatus = { + status: 'PARTIAL', + detail: wp1.body.slice(0, 120).replace(/\n/g, ' '), + }; + } else { + frontStatus = { + status: 'UNKNOWN', + detail: wp1.body.slice(0, 120).replace(/\n/g, ' '), + body: wp1.body, + }; + } + } + } + + // --- Phase 2: View single post (click "Hello world!") --- + if ( + wp1 && + (frontStatus.status === 'OK' || frontStatus.status === 'PARTIAL') + ) { + try { + const link = wp1.frame + .getByRole('link', { + name: 'Hello world!', + exact: true, + }) + .first(); + if ((await link.count()) > 0) { + await link.click({ timeout: 5000 }); + await page.waitForTimeout(8000); + const wp1b = await waitForWPFrame(page, 30); + if (!wp1b) { + postStatus = { status: 'TIMEOUT' }; + } else { + const hasContent = + (wp1b.body.includes('Welcome to WordPress') || + wp1b.body.includes('Hello world')) && + !wp1b.body.includes('Not Found') && + !wp1b.body.includes("can't find"); + postStatus = hasContent + ? { status: 'OK' } + : { + status: 'NOT_FOUND', + detail: wp1b.body + .slice(0, 120) + .replace(/\n/g, ' '), + }; + } + } else { + postStatus = { status: 'SKIP', detail: 'no link found' }; + } + } catch (e) { + postStatus = { status: 'CRASH', detail: e.message }; + } + } else { + postStatus = { status: 'SKIP', detail: 'front page failed' }; + } + + // --- Phase 3: Admin dashboard (auto-login) --- + if (frontStatus.status === 'OK' || frontStatus.status === 'PARTIAL') { + try { + const wp2 = await navigateViaUrlBar( + page, + '/wp-admin/', + TIMEOUT_S + ); + if (!wp2) { + adminStatus = { status: 'TIMEOUT' }; + } else { + const error = findPHPError(wp2.body); + if (error) { + adminStatus = { + status: 'ERROR', + detail: error, + body: wp2.body, + }; + } else { + const adminIndicators = [ + 'Dashboard', + 'Write', + 'Manage', + 'Options', + 'Log Out', + 'Logout', + 'Settings', + 'Posts', + 'Plugins', + 'Create New Post', + 'My Profile', + ]; + const hasAdmin = adminIndicators.some((ind) => + wp2.body.includes(ind) + ); + const loggedIn = isLoggedIn(wp2.body); + if (hasAdmin && loggedIn) { + adminStatus = { status: 'OK' }; + } else if (hasAdmin) { + adminStatus = { + status: 'OK', + detail: 'admin loaded but login state unclear', + }; + } else { + adminStatus = { + status: 'UNKNOWN', + detail: wp2.body + .slice(0, 120) + .replace(/\n/g, ' '), + body: wp2.body, + }; + } + } + } + } catch (e) { + adminStatus = { + status: 'CRASH', + detail: e.message, + }; + } + } else { + adminStatus = { status: 'SKIP', detail: 'front page failed' }; + } + + // --- Phase 4: New post page (nonce check) --- + if (adminStatus && adminStatus.status === 'OK') { + try { + const newPostPath = NEW_POST_URL_VERSIONS.has(wp) + ? '/wp-admin/post.php' + : '/wp-admin/post-new.php'; + const wp3 = await navigateViaUrlBar(page, newPostPath, 30); + if (!wp3) { + newPostStatus = { status: 'TIMEOUT' }; + } else { + // Check both innerText and innerHTML for PHP + // errors — some errors land inside hidden elements + // (e.g. WP 3.3's contextual-help sidebar) and + // don't appear in innerText. + let html = ''; + try { + html = await wp3.frame + .locator('body') + .innerHTML({ timeout: 2000 }); + } catch {} + const error = findPHPError(wp3.body) || findPHPError(html); + + const bad = + wp3.body.includes('Are you sure') || + wp3.body.includes('not allowed') || + wp3.body.includes('sufficient permissions'); + const hasEditor = + wp3.body.includes('Title') || + wp3.body.includes('title') || + wp3.body.includes('Write Post') || + wp3.body.includes('Add New Post') || + wp3.body.includes('Create New Post'); + if (error) { + newPostStatus = { + status: 'ERROR', + detail: error, + }; + } else if (bad) { + newPostStatus = { + status: 'NONCE_FAIL', + detail: wp3.body.includes('Are you sure') + ? 'nonce verification failed' + : 'permission denied', + }; + } else if (hasEditor) { + newPostStatus = { status: 'OK' }; + } else { + newPostStatus = { + status: 'UNKNOWN', + detail: wp3.body.slice(0, 120).replace(/\n/g, ' '), + }; + } + } + } catch (e) { + newPostStatus = { status: 'CRASH', detail: e.message }; + } + } else { + newPostStatus = { status: 'SKIP', detail: 'admin failed' }; + } + + // --- Phase 5: Plugin activation --- + if (adminStatus && adminStatus.status === 'OK') { + try { + const wp4 = await navigateViaUrlBar( + page, + '/wp-admin/plugins.php', + 30 + ); + if (!wp4) { + pluginStatus = { status: 'TIMEOUT' }; + } else { + const activateLink = wp4.frame + .locator('a') + .filter({ hasText: 'Activate' }) + .first(); + if ((await activateLink.count()) > 0) { + await activateLink.click({ timeout: 5000 }); + await page.waitForTimeout(8000); + const wp4b = await waitForWPFrame(page, 20); + if (!wp4b) { + pluginStatus = { status: 'TIMEOUT' }; + } else { + const ok = + wp4b.body.includes('Plugin activated') || + wp4b.body.includes('Deactivate'); + const bad = wp4b.body.includes('Are you sure'); + pluginStatus = ok + ? { status: 'OK' } + : { + status: bad ? 'NONCE_FAIL' : 'UNKNOWN', + detail: wp4b.body + .slice(0, 120) + .replace(/\n/g, ' '), + }; + } + } else { + pluginStatus = { + status: 'SKIP', + detail: 'no activate link found', + }; + } + } + } catch (e) { + pluginStatus = { status: 'CRASH', detail: e.message }; + } + } else { + pluginStatus = { status: 'SKIP', detail: 'admin failed' }; + } + } catch (e) { + frontStatus = { + status: 'CRASH', + detail: e.message, + }; + adminStatus = { status: 'SKIP', detail: 'boot crashed' }; + postStatus = { status: 'SKIP', detail: 'boot crashed' }; + newPostStatus = { status: 'SKIP', detail: 'boot crashed' }; + pluginStatus = { status: 'SKIP', detail: 'boot crashed' }; + } + + const icon = (s) => + s.status === 'OK' ? '✓' : s.status === 'SKIP' ? '-' : '✗'; + const parts = [ + `front:${icon(frontStatus)}`, + `post:${icon(postStatus)}`, + `admin:${icon(adminStatus)}`, + `newpost:${icon(newPostStatus)}`, + `plugin:${icon(pluginStatus)}`, + ]; + console.log(parts.join(' ')); + + results.push({ + wp, + front: frontStatus, + post: postStatus, + admin: adminStatus, + newPost: newPostStatus, + plugin: pluginStatus, + }); + await page.close(); + await context.close(); +} + +await browser.close(); + +const PHASES = ['front', 'post', 'admin', 'newPost', 'plugin']; + +function isPass(status) { + return status.status === 'OK' || status.status === 'PARTIAL'; +} +function isSkip(status) { + return status.status === 'SKIP'; +} + +console.log(`\n${'='.repeat(70)}`); +console.log('RESULTS SUMMARY:'); +console.log(`${'='.repeat(70)}`); +for (const r of results) { + const cols = PHASES.map((p) => { + const s = r[p]; + if (!s) return '-'; + if (isPass(s)) return 'PASS'; + if (isSkip(s)) return 'skip'; + return 'FAIL'; + }); + console.log( + ` WP ${r.wp.padEnd(5)} ${cols.map((c, i) => `${PHASES[i]}:${c}`).join(' ')}` + ); +} + +const counts = {}; +for (const p of PHASES) { + const tested = results.filter((r) => r[p] && !isSkip(r[p])); + const passed = tested.filter((r) => isPass(r[p])); + counts[p] = { tested: tested.length, passed: passed.length }; +} +console.log(''); +for (const p of PHASES) { + console.log(` ${p.padEnd(8)}: ${counts[p].passed}/${counts[p].tested} OK`); +} + +// Dump per-failure diagnostic bodies. +const failures = results.filter((r) => + PHASES.some((p) => r[p] && !isPass(r[p]) && !isSkip(r[p])) +); +if (failures.length > 0) { + console.log(`\n${'='.repeat(70)}`); + console.log('FAILURE DETAILS:'); + console.log(`${'='.repeat(70)}`); + for (const r of failures) { + console.log(`\n--- WP ${r.wp} ---`); + for (const p of PHASES) { + const s = r[p]; + if (!s || isPass(s) || isSkip(s)) continue; + console.log(` ${p} [${s.status}]: ${s.detail || ''}`); + if (s.body) { + console.log( + ` body:\n${s.body.slice(0, 1000).replace(/^/gm, ' ')}` + ); + } + } + } +} + +// All non-skip failures are hard errors. +const totalFailures = results.reduce( + (n, r) => + n + PHASES.filter((p) => r[p] && !isPass(r[p]) && !isSkip(r[p])).length, + 0 +); +if (totalFailures > 0) { + console.error(`\n${totalFailures} failure(s) across all phases.`); + process.exit(1); +} diff --git a/scripts/patch-sqlite-for-php56.mjs b/scripts/patch-sqlite-for-php56.mjs new file mode 100644 index 00000000000..89df3a61d98 --- /dev/null +++ b/scripts/patch-sqlite-for-php56.mjs @@ -0,0 +1,391 @@ +/** + * Offline patcher: applies PHP 5.6 compatibility transformations to the + * SQLite integration plugin. Produces a pre-patched zip that can be used + * without runtime patching. + * + * Usage: node scripts/patch-sqlite-for-php56.mjs + */ +import fs from 'fs'; +import os from 'os'; +import path from 'path'; +import { execSync } from 'child_process'; +import { + stripPhp7TypeDeclarations, + replaceNullCoalescing, + replacePhp7ErrorClasses, +} from '../packages/playground/wordpress/src/legacy-php-compat.ts'; + +const SRC_ZIP = path.resolve( + 'packages/playground/wordpress-builds/src/sqlite-database-integration/sqlite-database-integration-v2.2.22.zip' +); +const OUT_ZIP = path.resolve( + 'packages/playground/wordpress-builds/src/sqlite-database-integration/sqlite-database-integration-v2.2.22-php56.zip' +); +const TMP_DIR = fs.mkdtempSync(path.join(os.tmpdir(), 'sqlite-php56-patch-')); +try { + execSync(`unzip -q "${SRC_ZIP}" -d "${TMP_DIR}"`); + + // Find all PHP files + function findPhpFiles(dir) { + const results = []; + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const full = path.join(dir, entry.name); + if (entry.isDirectory()) { + results.push(...findPhpFiles(full)); + } else if (entry.name.endsWith('.php')) { + results.push(full); + } + } + return results; + } + + const phpFiles = findPhpFiles(TMP_DIR); + let patchedCount = 0; + + for (const filePath of phpFiles) { + let content = fs.readFileSync(filePath, 'utf-8'); + const original = content; + + content = stripPhp7TypeDeclarations(content); + + // Handle multi-line chains BEFORE general ?? replacement, because + // the general regex can't see across lines and produces invalid PHP. + // + // Pattern A: $var = $obj\n ->m1()\n ->m2() ?? $fallback; + // Match a method chain that starts with $obj and has newline-separated + // ->method() calls, ending with ?? $fallback. + content = content.replace( + /(\$\w+)\s*=\s*(\$\w+(?:\s*\n\s*->\w+\([^)]*\))+)\s*\?\?\s*(\$\w+(?:->\w+\([^)]*\))?)\s*;/g, + (_, varName, chain, fallback) => { + return `$_nc_chain = ${chain};\n\t\t\t${varName} = $_nc_chain !== null ? $_nc_chain : ${fallback};`; + } + ); + // Pattern B: ($expr\n ?? $expr) — paren-wrapped multi-line ?? + content = content.replace( + /\(\s*(\$\w+(?:->\w+\([^)]*\))*)\s*\n\s*\?\?\s*(\$\w+(?:->\w+\([^)]*\))*)\s*\)/g, + (_, lhs, rhs) => { + return `(($_nc_tmp = ${lhs}) !== null ? $_nc_tmp : ${rhs})`; + } + ); + + content = replaceNullCoalescing(content); + content = replacePhp7ErrorClasses(content); + + // Replace dirname(__DIR__, N) with nested dirname() calls + content = content.replace( + /dirname\(\s*__DIR__\s*,\s*(\d+)\s*\)/g, + (_, levels) => { + let r = '__DIR__'; + for (let i = 0; i < parseInt(levels, 10); i++) { + r = `dirname(${r})`; + } + return r; + } + ); + + // Rename 'throw' method (reserved word in PHP 5.6) + content = content + .replace(/function throw\(/g, 'function throwError(') + .replace(/\$this->throw\(/g, '$this->throwError(') + .replace(/self::throw\(/g, 'self::throwError(') + // Update string references in method mapping arrays + .replace(/'throw'(\s*=>\s*)'throw'/g, "'throw'$1'throwError'"); + + // Make allow_unsafe_unquoted_parameters public so old WordPress + // versions can access it without triggering Undefined property notices. + content = content.replace( + 'private $allow_unsafe_unquoted_parameters = true;', + 'public $allow_unsafe_unquoted_parameters = true;' + ); + + // Guard calls to WP functions that may not exist in old WordPress. + if (filePath.endsWith('class-wp-sqlite-db.php')) { + // Guard apply_filters in query() (WP < 2.1) + content = content.replace( + "$query = apply_filters( 'query', $query );", + "if ( function_exists( 'apply_filters' ) ) { $query = apply_filters( 'query', $query ); }" + ); + // Guard apply_filters in set_sql_mode() + content = content.replace( + "$incompatible_modes = (array) apply_filters( 'incompatible_sql_modes', $this->incompatible_modes );", + "$_modes = isset( $this->incompatible_modes ) ? $this->incompatible_modes : array();\n\t\t$incompatible_modes = function_exists( 'apply_filters' ) ? (array) apply_filters( 'incompatible_sql_modes', $_modes ) : (array) $_modes;" + ); + // Guard wp_load_translations_early() in print_error() (WP < 3.4) + content = content.replace( + 'wp_load_translations_early();', + "if ( function_exists( 'wp_load_translations_early' ) ) { wp_load_translations_early(); }" + ); + } + + // Guard is_multisite() and is_admin() calls (WP < 2.0 may lack them). + content = content.replace( + /if \( is_multisite\(\) \)/g, + "if ( function_exists('is_multisite') && is_multisite() )" + ); + content = content.replace( + /if \( is_admin\(\) \)/g, + "if ( function_exists('is_admin') && is_admin() )" + ); + + // Guard set_prefix call in information schema reconstructor (WP < 2.0). + if ( + filePath.includes( + 'class-wp-sqlite-information-schema-reconstructor.php' + ) + ) { + content = content.replace( + '$wpdb->set_prefix( $table_prefix );', + "if ( method_exists( $wpdb, 'set_prefix' ) ) { $wpdb->set_prefix( $table_prefix ); }" + ); + // Work around the PHP 5.6 WASM parser bug for schema.php. + // Try require_once first (works for WP 4.9 etc.). If it + // fails (WASM bug for WP 2.5-2.8), fall back to eval. + content = content.replace( + "require_once ABSPATH . 'wp-admin/includes/schema.php';", + "@require_once ABSPATH . 'wp-admin/includes/schema.php'; " + + "if (!function_exists('wp_get_db_schema') && !isset($GLOBALS['wp_queries'])) " + + "{ eval('?>' . file_get_contents(ABSPATH . 'wp-admin/includes/schema.php')); }" + ); + } + + // Make information schema parse failures non-fatal for old WP schemas. + if ( + filePath.includes( + 'class-wp-sqlite-information-schema-reconstructor.php' + ) + ) { + content = content.replace( + /throw new WP_SQLite_Driver_Exception\( \$this->driver, 'Failed to parse the MySQL query\.' \);/g, + 'return; // Non-fatal: old WP schema may not parse cleanly' + ); + } + + // Replace wp_get_db_schema() calls with inline fallback for WP < 3.3. + // Can't add a polyfill function because schema.php defines the real + // one later, causing "Cannot redeclare". + if (filePath.includes('install-functions.php')) { + content = content.replace( + '$table_schemas = wp_get_db_schema();', + '$table_schemas = function_exists("wp_get_db_schema") ? wp_get_db_schema() : (isset($GLOBALS["wp_queries"]) ? $GLOBALS["wp_queries"] : "");' + ); + // Skip the SQLite wp_install() override for old WordPress. + // The SQLite version uses functions like update_user_meta() + // that don't exist in WP < 3.0. Old WP's own wp_install() + // works fine with the SQLite driver (CREATE TABLE queries + // are translated by the AST driver). + content = content.replace( + "if ( ! function_exists( 'wp_install' ) ) {", + "if ( ! function_exists( 'wp_install' ) && function_exists( 'update_user_meta' ) ) {" + ); + } + if ( + filePath.includes( + 'class-wp-sqlite-information-schema-reconstructor.php' + ) + ) { + // Replace all wp_get_db_schema() calls (with or without args) + // with an inline fallback that uses $wp_queries on old WP. + // Replace specific known call patterns + const fallback = + '(isset($GLOBALS["wp_queries"]) ? $GLOBALS["wp_queries"] : "")'; + content = content + .replace( + "wp_get_db_schema( 'global' )", + `(function_exists("wp_get_db_schema") ? wp_get_db_schema( 'global' ) : ${fallback})` + ) + .replace( + /wp_get_db_schema\( 'blog', \(int\) \$blog_id \)/g, + `(function_exists("wp_get_db_schema") ? wp_get_db_schema( 'blog', (int) $blog_id ) : ${fallback})` + ) + .replace( + "wp_get_db_schema( 'blog' )", + `(function_exists("wp_get_db_schema") ? wp_get_db_schema( 'blog' ) : ${fallback})` + ); + // Remove the "function was not defined" exception + content = content.replace( + /if \( ! function_exists\( 'wp_get_db_schema' \) \) \{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}/s, + '// wp_get_db_schema polyfill handled inline' + ); + } + + // Add placeholder_escape polyfills to WP_SQLite_DB for WordPress < 4.8.3. + // The parent wpdb class only gained these methods in WP 4.8.3, but + // WP_SQLite_DB::_real_escape() calls add_placeholder_escape(). + if ( + filePath.endsWith('class-wp-sqlite-db.php') && + !content.includes('function add_placeholder_escape') + ) { + const polyfill = ` + + /** + * Polyfill for wpdb::placeholder_escape() (added in WP 4.8.3). + * + * Generates a unique hash placeholder and registers a 'query' filter + * to restore '%' characters before the query is executed. This matches + * the behavior of wpdb::placeholder_escape() in WordPress >= 4.8.3. + */ + public function placeholder_escape() { + static $placeholder; + if ( ! $placeholder ) { + $algo = function_exists( 'hash' ) ? 'sha256' : 'sha1'; + $salt = defined( 'AUTH_SALT' ) && AUTH_SALT ? AUTH_SALT : (string) rand(); + $placeholder = '{' . hash_hmac( $algo, uniqid( $salt, true ), $salt ) . '}'; + } + + /* + * Register a filter to remove the placeholder escape before query + * execution. Uses priority 0 so that other 'query' filter callbacks + * receive the query with real '%' characters restored. + */ + if ( function_exists( 'add_filter' ) + && function_exists( 'has_filter' ) + && false === has_filter( 'query', array( $this, 'remove_placeholder_escape' ) ) + ) { + add_filter( 'query', array( $this, 'remove_placeholder_escape' ), 0 ); + } + + return $placeholder; + } + + /** + * Polyfill for wpdb::add_placeholder_escape() (added in WP 4.8.3). + */ + public function add_placeholder_escape( $query ) { + return str_replace( '%', $this->placeholder_escape(), $query ); + } + + /** + * Polyfill for wpdb::remove_placeholder_escape() (added in WP 4.8.3). + */ + public function remove_placeholder_escape( $query ) { + return str_replace( $this->placeholder_escape(), '%', $query ); + } + + + // Polyfills for wpdb methods missing in WordPress < 2.0. + // These are no-op stubs that prevent fatal errors. + public function get_caller() { + if ( method_exists( get_parent_class( $this ), 'get_caller' ) ) { + return parent::get_caller(); + } + return ''; + } + public function log_query( $query, $elapsed, $caller, $start = 0.0, $data = array() ) { + if ( method_exists( get_parent_class( $this ), 'log_query' ) ) { + return parent::log_query( $query, $elapsed, $caller, $start, $data ); + } + if ( !isset( $this->queries ) ) { $this->queries = array(); } + $this->queries[] = array( $query, $elapsed, $caller ); + } + + // Properties that old wpdb (< 3.0) doesn't declare as class members. + // Declaring them here prevents "Undefined property" notices. + public $insert_id = 0; + public $num_rows = 0; + public $last_result = array(); + public $last_error = ''; + public $last_query = null; + public $rows_affected = 0; + public $col_info = null; + public $result = null; + public $incompatible_modes = array(); + public $dbname = null; + + /** + * Re-initialize the SQLite driver for old WordPress (< 3.0). + * + * Old wpdb::__construct() calls mysql_connect() inline instead + * of db_connect(), leaving $this->dbh as a boolean stub. + * This method resets $this->dbh and initializes the driver. + */ + public function reinitialize_sqlite() { + if ( $this->dbh instanceof WP_SQLite_Driver || $this->dbh instanceof WP_SQLite_Translator ) { + return; + } + if ( empty( $this->dbname ) && defined( 'DB_NAME' ) ) { + $this->dbname = DB_NAME; + } + if ( !isset( $this->last_result ) ) { + $this->last_result = array(); + } + // Ensure table prefix is set before db_connect(), because + // the information schema reconstructor uses $wpdb->tables + // properties that are only set after set_prefix(). + global $table_prefix; + if ( isset( $table_prefix ) && empty( $this->prefix ) && method_exists( $this, 'set_prefix' ) ) { + $this->set_prefix( $table_prefix ); + } + // Set an empty wp_queries so the information schema + // reconstructor doesn't crash on old WP during db_connect(). + // NOTE: db_connect() triggers require_once schema.php which + // sets $wp_queries to the real schema. Don't unset it after. + if ( !isset( $GLOBALS['wp_queries'] ) ) { + $GLOBALS['wp_queries'] = ''; + } + $this->dbh = null; + $this->db_connect(); + } + + /** + * Polyfill for wpdb::init_charset() (added in WP 4.2). + */ + public function init_charset() { + if ( method_exists( get_parent_class( $this ), 'init_charset' ) ) { + parent::init_charset(); + } elseif ( defined( 'DB_CHARSET' ) ) { + $this->charset = DB_CHARSET; + } + } +`; + // Insert before the closing brace of the class + const lastBrace = content.lastIndexOf('}'); + content = + content.slice(0, lastBrace) + + polyfill + + content.slice(lastBrace); + } + + // Fix WP_SQLite_DB::prepare() — it uses __get() and ReflectionProperty + // on wpdb::$allow_unsafe_unquoted_parameters which doesn't exist in + // old WordPress (added in WP 6.2). Also, __get() doesn't exist in + // WP < 4.8.3, and calling an undefined method is a fatal error in + // PHP 5.6 (can't be caught with try-catch). + content = content.replace( + /\$wpdb_allow_unsafe_unquoted_parameters = \$this->__get\( 'allow_unsafe_unquoted_parameters' \);\s*\n\s*if \( \$wpdb_allow_unsafe_unquoted_parameters !== \$this->allow_unsafe_unquoted_parameters \) \{\s*\n\s*\$property = new ReflectionProperty\([^}]+\}/s, + "if ( method_exists( $this, '__get' ) ) {\n\t\t\ttry {\n\t\t\t\t$wpdb_allow_unsafe_unquoted_parameters = $this->__get( 'allow_unsafe_unquoted_parameters' );\n\t\t\t\tif ( $wpdb_allow_unsafe_unquoted_parameters !== $this->allow_unsafe_unquoted_parameters ) {\n\t\t\t\t\t$property = new ReflectionProperty( 'wpdb', 'allow_unsafe_unquoted_parameters' );\n\t\t\t\t\t$property->setAccessible( true );\n\t\t\t\t\t$property->setValue( $this, $this->allow_unsafe_unquoted_parameters );\n\t\t\t\t\t$property->setAccessible( false );\n\t\t\t\t}\n\t\t\t} catch (Exception $e) { /* Old WP lacks this property */ }\n\t\t\t}" + ); + + // Fix specific complex expressions that regex can't handle. + // (ternary)['key'] ?? '' — array access on ternary result + null coalescing + content = content.replace( + /\(isset\(\$meta\['sqlite:decl_type'\]\) \? \$meta\['sqlite:decl_type'\] : \$meta\)\['native_type'\] \?\? ''/g, + "(isset($meta['sqlite:decl_type']) ? (isset($meta['sqlite:decl_type']['native_type']) ? $meta['sqlite:decl_type']['native_type'] : '') : (isset($meta['native_type']) ? $meta['native_type'] : ''))" + ); + + if (content !== original) { + fs.writeFileSync(filePath, content); + patchedCount++; + console.log(` Patched: ${path.relative(TMP_DIR, filePath)}`); + } + } + + console.log(`\nPatched ${patchedCount}/${phpFiles.length} files`); + + // Re-zip + if (fs.existsSync(OUT_ZIP)) fs.unlinkSync(OUT_ZIP); + execSync(`cd "${TMP_DIR}" && zip -r -q "${OUT_ZIP}" .`); + console.log(`\nCreated: ${OUT_ZIP}`); + + // Verify: check for remaining ?? + const remaining = execSync( + `grep -rn '??' "${TMP_DIR}" --include='*.php' || true` + ).toString(); + if (remaining.trim()) { + console.log('\nWARNING: Remaining ?? operators:'); + console.log(remaining); + } else { + console.log('\nNo remaining ?? operators — all patched.'); + } +} finally { + fs.rmSync(TMP_DIR, { recursive: true, force: true }); +} diff --git a/tsconfig.base.json b/tsconfig.base.json index 164447419b7..3fc72decf05 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -24,6 +24,9 @@ ], "@php-wasm/logger": ["packages/php-wasm/logger/src/index.ts"], "@php-wasm/node": ["packages/php-wasm/node/src/index.ts"], + "@php-wasm/node-5-6": [ + "packages/php-wasm/node-builds/5-6/src/index.ts" + ], "@php-wasm/node-7-2": [ "packages/php-wasm/node-builds/7-2/src/index.ts" ], @@ -66,6 +69,9 @@ ], "@php-wasm/util": ["packages/php-wasm/util/src/index.ts"], "@php-wasm/web": ["packages/php-wasm/web/src/index.ts"], + "@php-wasm/web-5-6": [ + "packages/php-wasm/web-builds/5-6/src/index.ts" + ], "@php-wasm/web-7-2": [ "packages/php-wasm/web-builds/7-2/src/index.ts" ],