sandboxedjs 0.1.92 → 0.1.94
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent.d.cts +2 -2
- package/dist/agent.d.ts +2 -2
- package/dist/{container-BGmhPbSD.d.cts → container-C3cgcnXZ.d.cts} +1 -1
- package/dist/{container-DCwYX_Cx.d.ts → container-WEYRUg6Q.d.ts} +1 -1
- package/dist/{contracts-BkWBTH8E.d.cts → contracts-B6SHFjma.d.cts} +109 -21
- package/dist/{contracts-BkWBTH8E.d.ts → contracts-B6SHFjma.d.ts} +109 -21
- package/dist/index.cjs +1398 -48
- package/dist/index.d.cts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +1398 -48
- package/dist/{memory-volume-DCm-DiXH.d.cts → memory-volume-CRhXUyCN.d.cts} +1 -1
- package/dist/{memory-volume-DJA-vFYW.d.ts → memory-volume-CwiXvRfs.d.ts} +1 -1
- package/dist/python/python.data +461 -303
- package/dist/python/python.js +1 -1
- package/dist/python/python.wasm +0 -0
- package/dist/python/runtime.json +3 -3
- package/dist/python-abi.cjs +56 -10
- package/dist/python-abi.d.cts +4 -3
- package/dist/python-abi.d.ts +4 -3
- package/dist/python-abi.js +56 -10
- package/dist/python-worker.js +1 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -19804,6 +19804,7 @@ var Op = {
|
|
|
19804
19804
|
setsockopt: 1802,
|
|
19805
19805
|
getsockopt: 1803,
|
|
19806
19806
|
socket_close: 1804,
|
|
19807
|
+
resolve: 1805,
|
|
19807
19808
|
spawn: 2048,
|
|
19808
19809
|
waitpid: 2049,
|
|
19809
19810
|
kill: 2050
|
|
@@ -20040,6 +20041,28 @@ var wheels_default = {
|
|
|
20040
20041
|
patches: []
|
|
20041
20042
|
}
|
|
20042
20043
|
},
|
|
20044
|
+
{
|
|
20045
|
+
name: "asyncpg",
|
|
20046
|
+
version: "0.31.0",
|
|
20047
|
+
filename: "asyncpg-0.31.0-cp313-cp313-emscripten_5_0_6_wasm32.whl",
|
|
20048
|
+
sha256: "3eaf965cbd78d2314e8f12b9384e5642725323063aaff2708a8a4b3c01ef10ba",
|
|
20049
|
+
abiId: "sbxabi1-c2637d04695ad927",
|
|
20050
|
+
requires: [
|
|
20051
|
+
'async_timeout>=4.0.3; python_version < "3.11.0"',
|
|
20052
|
+
'gssapi; platform_system != "Windows" and extra == "gssauth"',
|
|
20053
|
+
'sspilib; platform_system == "Windows" and extra == "gssauth"'
|
|
20054
|
+
],
|
|
20055
|
+
provenance: {
|
|
20056
|
+
sourceTreeDigest: "a9ab0177da5c0c2d0ba635d1f5568687efbf8aefc08cd51cd086277184eba76f",
|
|
20057
|
+
recipeSha256: "fb9cf1a9fd35d8d63ac69dcf6a1671ceeebcb23f381a100b412058d75ff2eedc",
|
|
20058
|
+
buildTools: [
|
|
20059
|
+
"setuptools==84.0.0",
|
|
20060
|
+
"Cython==3.1.4"
|
|
20061
|
+
],
|
|
20062
|
+
nativeDependencies: [],
|
|
20063
|
+
patches: []
|
|
20064
|
+
}
|
|
20065
|
+
},
|
|
20043
20066
|
{
|
|
20044
20067
|
name: "bcrypt",
|
|
20045
20068
|
version: "5.0.0",
|
|
@@ -20057,6 +20080,26 @@ var wheels_default = {
|
|
|
20057
20080
|
patches: []
|
|
20058
20081
|
}
|
|
20059
20082
|
},
|
|
20083
|
+
{
|
|
20084
|
+
name: "brotli",
|
|
20085
|
+
version: "1.2.0",
|
|
20086
|
+
filename: "brotli-1.2.0-cp313-cp313-emscripten_5_0_6_wasm32.whl",
|
|
20087
|
+
sha256: "d8b06cc04770a048b41956c9cc767e7332c49f0229e69744f4b1c2892b888b27",
|
|
20088
|
+
abiId: "sbxabi1-c2637d04695ad927",
|
|
20089
|
+
requires: [],
|
|
20090
|
+
provenance: {
|
|
20091
|
+
sourceTreeDigest: "7b563fa2e7f20edac204cc80004cae35c844b36a83758e5957184383332d1209",
|
|
20092
|
+
recipeSha256: "1326c898e999feba536ff15f080fed9ac4b9cd14e0dbfee39a950decf19607ef",
|
|
20093
|
+
buildTools: [
|
|
20094
|
+
"setuptools==84.0.0",
|
|
20095
|
+
"pkgconfig==1.6.0"
|
|
20096
|
+
],
|
|
20097
|
+
nativeDependencies: [],
|
|
20098
|
+
patches: [
|
|
20099
|
+
"0001-meth-noargs-signatures.patch"
|
|
20100
|
+
]
|
|
20101
|
+
}
|
|
20102
|
+
},
|
|
20060
20103
|
{
|
|
20061
20104
|
name: "cffi",
|
|
20062
20105
|
version: "2.1.1",
|
|
@@ -20082,14 +20125,14 @@ var wheels_default = {
|
|
|
20082
20125
|
name: "cryptography",
|
|
20083
20126
|
version: "50.0.1",
|
|
20084
20127
|
filename: "cryptography-50.0.1-cp313-cp313-emscripten_5_0_6_wasm32.whl",
|
|
20085
|
-
sha256: "
|
|
20128
|
+
sha256: "10bb6568bf4368a593ea3a990ea870e57d4eeb1d3278af89bf24a8713473238e",
|
|
20086
20129
|
abiId: "sbxabi1-c2637d04695ad927",
|
|
20087
20130
|
requires: [
|
|
20088
20131
|
'cffi>=2.0.0; platform_python_implementation != "PyPy"'
|
|
20089
20132
|
],
|
|
20090
20133
|
provenance: {
|
|
20091
20134
|
sourceTreeDigest: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
20092
|
-
recipeSha256: "
|
|
20135
|
+
recipeSha256: "082169d1d317ba001e37ccfb7a40a0dafd8d3d34e42034da8590f0207ae73ea5",
|
|
20093
20136
|
buildTools: [
|
|
20094
20137
|
"setuptools==84.0.0",
|
|
20095
20138
|
"cffi==2.1.1"
|
|
@@ -20115,6 +20158,25 @@ var wheels_default = {
|
|
|
20115
20158
|
patches: []
|
|
20116
20159
|
}
|
|
20117
20160
|
},
|
|
20161
|
+
{
|
|
20162
|
+
name: "google-crc32c",
|
|
20163
|
+
version: "1.8.0",
|
|
20164
|
+
filename: "google_crc32c-1.8.0-cp313-cp313-emscripten_5_0_6_wasm32.whl",
|
|
20165
|
+
sha256: "e64c21841598ea21121d7b7bb203e124d58c035db49ffce03631c55216c942cd",
|
|
20166
|
+
abiId: "sbxabi1-c2637d04695ad927",
|
|
20167
|
+
requires: [],
|
|
20168
|
+
provenance: {
|
|
20169
|
+
sourceTreeDigest: "fa277032a78adfccf6adcda32cadc4f6958a324979040c231e01f0963821cb75",
|
|
20170
|
+
recipeSha256: "843dea57727061bdfc7bcd27ac380d16a767effa538cbab6257e106939689f43",
|
|
20171
|
+
buildTools: [
|
|
20172
|
+
"setuptools==84.0.0"
|
|
20173
|
+
],
|
|
20174
|
+
nativeDependencies: [
|
|
20175
|
+
"crc32c==1.1.2"
|
|
20176
|
+
],
|
|
20177
|
+
patches: []
|
|
20178
|
+
}
|
|
20179
|
+
},
|
|
20118
20180
|
{
|
|
20119
20181
|
name: "httptools",
|
|
20120
20182
|
version: "0.8.0",
|
|
@@ -20151,6 +20213,58 @@ var wheels_default = {
|
|
|
20151
20213
|
patches: []
|
|
20152
20214
|
}
|
|
20153
20215
|
},
|
|
20216
|
+
{
|
|
20217
|
+
name: "lxml",
|
|
20218
|
+
version: "6.1.3",
|
|
20219
|
+
filename: "lxml-6.1.3-cp313-cp313-emscripten_5_0_6_wasm32.whl",
|
|
20220
|
+
sha256: "dc76d45314b806934bdaf35f9e4ae61e86e741e07bac53fae7e94f9d3dab9d4e",
|
|
20221
|
+
abiId: "sbxabi1-c2637d04695ad927",
|
|
20222
|
+
requires: [
|
|
20223
|
+
'cssselect>=0.7; extra == "cssselect"',
|
|
20224
|
+
'html5lib; extra == "html5"',
|
|
20225
|
+
'BeautifulSoup4; extra == "htmlsoup"',
|
|
20226
|
+
'lxml_html_clean; extra == "html-clean"'
|
|
20227
|
+
],
|
|
20228
|
+
provenance: {
|
|
20229
|
+
sourceTreeDigest: "aedea680c1fe58ae9a124d81ea28f2316765e444e3809691854ddbf173c22bf5",
|
|
20230
|
+
recipeSha256: "aa62da97bb13eb5fa0a047f7c61ed93554f60142be9fcae64e3053e2a5673e9f",
|
|
20231
|
+
buildTools: [
|
|
20232
|
+
"setuptools==84.0.0",
|
|
20233
|
+
"Cython==3.1.4"
|
|
20234
|
+
],
|
|
20235
|
+
nativeDependencies: [
|
|
20236
|
+
"xml2==2.13.8",
|
|
20237
|
+
"xslt==1.1.43"
|
|
20238
|
+
],
|
|
20239
|
+
patches: []
|
|
20240
|
+
}
|
|
20241
|
+
},
|
|
20242
|
+
{
|
|
20243
|
+
name: "lz4",
|
|
20244
|
+
version: "4.4.5",
|
|
20245
|
+
filename: "lz4-4.4.5-cp313-cp313-emscripten_5_0_6_wasm32.whl",
|
|
20246
|
+
sha256: "f94c0ef7d84c9bf700cc86f69a28382699262e442e29d9763736afdb61141ffe",
|
|
20247
|
+
abiId: "sbxabi1-c2637d04695ad927",
|
|
20248
|
+
requires: [
|
|
20249
|
+
'pytest!=3.3.0; extra == "tests"',
|
|
20250
|
+
'psutil; extra == "tests"',
|
|
20251
|
+
'pytest-cov; extra == "tests"',
|
|
20252
|
+
'sphinx>=1.6.0; extra == "docs"',
|
|
20253
|
+
'sphinx_bootstrap_theme; extra == "docs"',
|
|
20254
|
+
'flake8; extra == "flake8"'
|
|
20255
|
+
],
|
|
20256
|
+
provenance: {
|
|
20257
|
+
sourceTreeDigest: "5f7bda9bfb026d0a30bb5e3383a4b25ae6b7f44fd6dc74950653d57f84c9d88e",
|
|
20258
|
+
recipeSha256: "80250de0ae159cd9f977bdfad059de0d192f28555225b084b42b5a180f684e0c",
|
|
20259
|
+
buildTools: [
|
|
20260
|
+
"setuptools==84.0.0",
|
|
20261
|
+
"pkgconfig==1.6.0",
|
|
20262
|
+
"setuptools-scm==10.2.3"
|
|
20263
|
+
],
|
|
20264
|
+
nativeDependencies: [],
|
|
20265
|
+
patches: []
|
|
20266
|
+
}
|
|
20267
|
+
},
|
|
20154
20268
|
{
|
|
20155
20269
|
name: "msgpack",
|
|
20156
20270
|
version: "1.1.0",
|
|
@@ -20211,16 +20325,172 @@ var wheels_default = {
|
|
|
20211
20325
|
name: "ormsgpack",
|
|
20212
20326
|
version: "1.12.2",
|
|
20213
20327
|
filename: "ormsgpack-1.12.2-cp313-cp313-emscripten_5_0_6_wasm32.whl",
|
|
20214
|
-
sha256: "
|
|
20328
|
+
sha256: "7fa4463a69aad2fe057c111642cce71c8f72af8465c22769d839415dbc15ac79",
|
|
20215
20329
|
abiId: "sbxabi1-c2637d04695ad927",
|
|
20216
20330
|
requires: [],
|
|
20217
20331
|
provenance: {
|
|
20218
|
-
sourceTreeDigest: "
|
|
20219
|
-
recipeSha256: "
|
|
20332
|
+
sourceTreeDigest: "eff3a7be3bc6de2a15e479c0e3e94905c35203fd83369124073f2df5cce46409",
|
|
20333
|
+
recipeSha256: "bd398762801fce51d35b33439f5e57037e1d4d274f25bd795683e2ad17d34b62",
|
|
20220
20334
|
buildTools: [
|
|
20221
20335
|
"setuptools==84.0.0"
|
|
20222
20336
|
],
|
|
20223
20337
|
nativeDependencies: [],
|
|
20338
|
+
patches: [
|
|
20339
|
+
"0001-pylong-asbytearray-313-signature.patch"
|
|
20340
|
+
]
|
|
20341
|
+
}
|
|
20342
|
+
},
|
|
20343
|
+
{
|
|
20344
|
+
name: "pandas",
|
|
20345
|
+
version: "3.0.5",
|
|
20346
|
+
filename: "pandas-3.0.5-cp313-cp313-emscripten_5_0_6_wasm32.whl",
|
|
20347
|
+
sha256: "ae36ad9b403b6fc28cb2bb79f54a38e1897ab43635c596fff2bce87f8148ac48",
|
|
20348
|
+
abiId: "sbxabi1-c2637d04695ad927",
|
|
20349
|
+
requires: [
|
|
20350
|
+
'numpy>=1.26.0; python_version < "3.14"',
|
|
20351
|
+
'numpy>=2.3.3; python_version >= "3.14"',
|
|
20352
|
+
"python-dateutil>=2.8.2",
|
|
20353
|
+
'tzdata; sys_platform == "win32"',
|
|
20354
|
+
'tzdata; sys_platform == "emscripten"',
|
|
20355
|
+
'hypothesis>=6.116.0; extra == "test"',
|
|
20356
|
+
'pytest<9.1,>=8.3.4; extra == "test"',
|
|
20357
|
+
'pytest-xdist>=3.6.1; extra == "test"',
|
|
20358
|
+
'pyarrow>=13.0.0; extra == "pyarrow"',
|
|
20359
|
+
'bottleneck>=1.4.2; extra == "performance"',
|
|
20360
|
+
'numba>=0.60.0; extra == "performance"',
|
|
20361
|
+
'numexpr>=2.10.2; extra == "performance"',
|
|
20362
|
+
'scipy>=1.14.1; extra == "computation"',
|
|
20363
|
+
'xarray>=2024.10.0; extra == "computation"',
|
|
20364
|
+
'fsspec>=2024.10.0; extra == "fss"',
|
|
20365
|
+
's3fs>=2024.10.0; extra == "aws"',
|
|
20366
|
+
'gcsfs>=2024.10.0; extra == "gcp"',
|
|
20367
|
+
'odfpy>=1.4.1; extra == "excel"',
|
|
20368
|
+
'openpyxl>=3.1.5; extra == "excel"',
|
|
20369
|
+
'python-calamine>=0.3.0; extra == "excel"',
|
|
20370
|
+
'pyxlsb>=1.0.10; extra == "excel"',
|
|
20371
|
+
'xlrd>=2.0.1; extra == "excel"',
|
|
20372
|
+
'xlsxwriter>=3.2.0; extra == "excel"',
|
|
20373
|
+
'pyarrow>=13.0.0; extra == "parquet"',
|
|
20374
|
+
'pyarrow>=13.0.0; extra == "feather"',
|
|
20375
|
+
'pyiceberg>=0.8.1; extra == "iceberg"',
|
|
20376
|
+
'tables>=3.10.1; extra == "hdf5"',
|
|
20377
|
+
'pyreadstat>=1.2.8; extra == "spss"',
|
|
20378
|
+
'SQLAlchemy>=2.0.36; extra == "postgresql"',
|
|
20379
|
+
'psycopg2>=2.9.10; extra == "postgresql"',
|
|
20380
|
+
'adbc-driver-postgresql>=1.2.0; extra == "postgresql"',
|
|
20381
|
+
'SQLAlchemy>=2.0.36; extra == "mysql"',
|
|
20382
|
+
'pymysql>=1.1.1; extra == "mysql"',
|
|
20383
|
+
'SQLAlchemy>=2.0.36; extra == "sql-other"',
|
|
20384
|
+
'adbc-driver-postgresql>=1.2.0; extra == "sql-other"',
|
|
20385
|
+
'adbc-driver-sqlite>=1.2.0; extra == "sql-other"',
|
|
20386
|
+
'beautifulsoup4>=4.12.3; extra == "html"',
|
|
20387
|
+
'html5lib>=1.1; extra == "html"',
|
|
20388
|
+
'lxml>=5.3.0; extra == "html"',
|
|
20389
|
+
'lxml>=5.3.0; extra == "xml"',
|
|
20390
|
+
'matplotlib>=3.9.3; extra == "plot"',
|
|
20391
|
+
'jinja2>=3.1.5; extra == "output-formatting"',
|
|
20392
|
+
'tabulate>=0.9.0; extra == "output-formatting"',
|
|
20393
|
+
'PyQt5>=5.15.9; extra == "clipboard"',
|
|
20394
|
+
'qtpy>=2.4.2; extra == "clipboard"',
|
|
20395
|
+
'zstandard>=0.23.0; extra == "compression"',
|
|
20396
|
+
'pytz>=2020.1; extra == "timezone"',
|
|
20397
|
+
'adbc-driver-postgresql>=1.2.0; extra == "all"',
|
|
20398
|
+
'adbc-driver-sqlite>=1.2.0; extra == "all"',
|
|
20399
|
+
'beautifulsoup4>=4.12.3; extra == "all"',
|
|
20400
|
+
'bottleneck>=1.4.2; extra == "all"',
|
|
20401
|
+
'fastparquet>=2024.11.0; extra == "all"',
|
|
20402
|
+
'fsspec>=2024.10.0; extra == "all"',
|
|
20403
|
+
'gcsfs>=2024.10.0; extra == "all"',
|
|
20404
|
+
'html5lib>=1.1; extra == "all"',
|
|
20405
|
+
'hypothesis>=6.116.0; extra == "all"',
|
|
20406
|
+
'jinja2>=3.1.5; extra == "all"',
|
|
20407
|
+
'lxml>=5.3.0; extra == "all"',
|
|
20408
|
+
'matplotlib>=3.9.3; extra == "all"',
|
|
20409
|
+
'numba>=0.60.0; extra == "all"',
|
|
20410
|
+
'numexpr>=2.10.2; extra == "all"',
|
|
20411
|
+
'odfpy>=1.4.1; extra == "all"',
|
|
20412
|
+
'openpyxl>=3.1.5; extra == "all"',
|
|
20413
|
+
'psycopg2>=2.9.10; extra == "all"',
|
|
20414
|
+
'pyarrow>=13.0.0; extra == "all"',
|
|
20415
|
+
'pyiceberg>=0.8.1; extra == "all"',
|
|
20416
|
+
'pymysql>=1.1.1; extra == "all"',
|
|
20417
|
+
'PyQt5>=5.15.9; extra == "all"',
|
|
20418
|
+
'pyreadstat>=1.2.8; extra == "all"',
|
|
20419
|
+
'pytest>=8.3.4; extra == "all"',
|
|
20420
|
+
'pytest-xdist>=3.6.1; extra == "all"',
|
|
20421
|
+
'python-calamine>=0.3.0; extra == "all"',
|
|
20422
|
+
'pytz>=2020.1; extra == "all"',
|
|
20423
|
+
'pyxlsb>=1.0.10; extra == "all"',
|
|
20424
|
+
'qtpy>=2.4.2; extra == "all"',
|
|
20425
|
+
'scipy>=1.14.1; extra == "all"',
|
|
20426
|
+
's3fs>=2024.10.0; extra == "all"',
|
|
20427
|
+
'SQLAlchemy>=2.0.36; extra == "all"',
|
|
20428
|
+
'tables>=3.10.1; extra == "all"',
|
|
20429
|
+
'tabulate>=0.9.0; extra == "all"',
|
|
20430
|
+
'xarray>=2024.10.0; extra == "all"',
|
|
20431
|
+
'xlrd>=2.0.1; extra == "all"',
|
|
20432
|
+
'xlsxwriter>=3.2.0; extra == "all"',
|
|
20433
|
+
'zstandard>=0.23.0; extra == "all"'
|
|
20434
|
+
],
|
|
20435
|
+
provenance: {
|
|
20436
|
+
sourceTreeDigest: "373f08151b5a209a84dd35b086316ef11a0d4030150457c2bfdab38305903d54",
|
|
20437
|
+
recipeSha256: "56976ed58b17053971334a62b503d5397b8ff739911fb596a116df23aeccabb0",
|
|
20438
|
+
buildTools: [
|
|
20439
|
+
"setuptools==84.0.0",
|
|
20440
|
+
"Cython==3.1.4",
|
|
20441
|
+
"meson-python==0.21.0",
|
|
20442
|
+
"meson==1.12.0",
|
|
20443
|
+
"ninja==1.13.2",
|
|
20444
|
+
"versioneer==0.29"
|
|
20445
|
+
],
|
|
20446
|
+
nativeDependencies: [],
|
|
20447
|
+
patches: []
|
|
20448
|
+
}
|
|
20449
|
+
},
|
|
20450
|
+
{
|
|
20451
|
+
name: "pillow",
|
|
20452
|
+
version: "12.3.0",
|
|
20453
|
+
filename: "pillow-12.3.0-cp313-cp313-emscripten_5_0_6_wasm32.whl",
|
|
20454
|
+
sha256: "7e585c71bf943029d8201283308a1b43f2be5f166d9beb36697d1f6bfba865e9",
|
|
20455
|
+
abiId: "sbxabi1-c2637d04695ad927",
|
|
20456
|
+
requires: [
|
|
20457
|
+
'furo; extra == "docs"',
|
|
20458
|
+
'olefile; extra == "docs"',
|
|
20459
|
+
'sphinx>=8.2; extra == "docs"',
|
|
20460
|
+
'sphinx-autobuild; extra == "docs"',
|
|
20461
|
+
'sphinx-copybutton; extra == "docs"',
|
|
20462
|
+
'sphinx-inline-tabs; extra == "docs"',
|
|
20463
|
+
'sphinxext-opengraph; extra == "docs"',
|
|
20464
|
+
'olefile; extra == "fpx"',
|
|
20465
|
+
'olefile; extra == "mic"',
|
|
20466
|
+
'arro3-compute; extra == "test-arrow"',
|
|
20467
|
+
'arro3-core; extra == "test-arrow"',
|
|
20468
|
+
'nanoarrow; extra == "test-arrow"',
|
|
20469
|
+
'pyarrow; extra == "test-arrow"',
|
|
20470
|
+
'coverage>=7.4.2; extra == "tests"',
|
|
20471
|
+
'defusedxml; extra == "tests"',
|
|
20472
|
+
'markdown2; extra == "tests"',
|
|
20473
|
+
'olefile; extra == "tests"',
|
|
20474
|
+
'packaging; extra == "tests"',
|
|
20475
|
+
'pytest; extra == "tests"',
|
|
20476
|
+
'pytest-cov; extra == "tests"',
|
|
20477
|
+
'pytest-timeout; extra == "tests"',
|
|
20478
|
+
'pytest-xdist; extra == "tests"',
|
|
20479
|
+
'setuptools; extra == "tests"',
|
|
20480
|
+
'trove-classifiers>=2024.10.12; extra == "tests"',
|
|
20481
|
+
'defusedxml; extra == "xmp"'
|
|
20482
|
+
],
|
|
20483
|
+
provenance: {
|
|
20484
|
+
sourceTreeDigest: "5427eff55a6a0e5f667d70527c2c3749f2c0786598903d7bbe7aaf16c4e95e31",
|
|
20485
|
+
recipeSha256: "77418b7e139e4779f2a422a06abbea27c941174b1f86597a2f3c4483bb236b7f",
|
|
20486
|
+
buildTools: [
|
|
20487
|
+
"setuptools==84.0.0",
|
|
20488
|
+
"pybind11==3.1.0"
|
|
20489
|
+
],
|
|
20490
|
+
nativeDependencies: [
|
|
20491
|
+
"z==1.3.1",
|
|
20492
|
+
"jpeg==9f"
|
|
20493
|
+
],
|
|
20224
20494
|
patches: []
|
|
20225
20495
|
}
|
|
20226
20496
|
},
|
|
@@ -20281,6 +20551,31 @@ var wheels_default = {
|
|
|
20281
20551
|
patches: []
|
|
20282
20552
|
}
|
|
20283
20553
|
},
|
|
20554
|
+
{
|
|
20555
|
+
name: "rapidfuzz",
|
|
20556
|
+
version: "3.14.6",
|
|
20557
|
+
filename: "rapidfuzz-3.14.6-cp313-cp313-emscripten_5_0_6_wasm32.whl",
|
|
20558
|
+
sha256: "b9ed2ea058350925beda765364c0f382bf9a7c08b3bec33840795178de021d32",
|
|
20559
|
+
abiId: "sbxabi1-c2637d04695ad927",
|
|
20560
|
+
requires: [
|
|
20561
|
+
'numpy; extra == "all"'
|
|
20562
|
+
],
|
|
20563
|
+
provenance: {
|
|
20564
|
+
sourceTreeDigest: "ae2b2bb95f7aba2babbbe668b68d48b5347ca923e6508b76d24228cd13ac7cd3",
|
|
20565
|
+
recipeSha256: "46954814226fff070aa6771d2f09d32a96b718ce8ef7a9f9404375850f7d1f77",
|
|
20566
|
+
buildTools: [
|
|
20567
|
+
"setuptools==84.0.0",
|
|
20568
|
+
"scikit-build-core==1.0.3",
|
|
20569
|
+
"cmake==4.4.3",
|
|
20570
|
+
"ninja==1.13.2",
|
|
20571
|
+
"Cython==3.1.4",
|
|
20572
|
+
"pathspec==1.1.1",
|
|
20573
|
+
"packaging==26.3"
|
|
20574
|
+
],
|
|
20575
|
+
nativeDependencies: [],
|
|
20576
|
+
patches: []
|
|
20577
|
+
}
|
|
20578
|
+
},
|
|
20284
20579
|
{
|
|
20285
20580
|
name: "regex",
|
|
20286
20581
|
version: "2026.9.10",
|
|
@@ -20486,6 +20781,23 @@ var wheels_default = {
|
|
|
20486
20781
|
patches: []
|
|
20487
20782
|
}
|
|
20488
20783
|
},
|
|
20784
|
+
{
|
|
20785
|
+
name: "simplejson",
|
|
20786
|
+
version: "4.1.2",
|
|
20787
|
+
filename: "simplejson-4.1.2-cp313-cp313-emscripten_5_0_6_wasm32.whl",
|
|
20788
|
+
sha256: "80671c2b02beb0d429108df8cefb6f27857b34e032bb5a5f1e0f957e7bf3cab6",
|
|
20789
|
+
abiId: "sbxabi1-c2637d04695ad927",
|
|
20790
|
+
requires: [],
|
|
20791
|
+
provenance: {
|
|
20792
|
+
sourceTreeDigest: "055af71755ff30d71076fdebc6f605e66f5876e6f01350a7d6075e8679006111",
|
|
20793
|
+
recipeSha256: "e163a1f2e78f32265c3ffdbf743dab27bc9e0829f21c553cabf0bafafd7d69fd",
|
|
20794
|
+
buildTools: [
|
|
20795
|
+
"setuptools==84.0.0"
|
|
20796
|
+
],
|
|
20797
|
+
nativeDependencies: [],
|
|
20798
|
+
patches: []
|
|
20799
|
+
}
|
|
20800
|
+
},
|
|
20489
20801
|
{
|
|
20490
20802
|
name: "siphash24",
|
|
20491
20803
|
version: "1.9",
|
|
@@ -20529,6 +20841,24 @@ var wheels_default = {
|
|
|
20529
20841
|
patches: []
|
|
20530
20842
|
}
|
|
20531
20843
|
},
|
|
20844
|
+
{
|
|
20845
|
+
name: "ujson",
|
|
20846
|
+
version: "6.0.0",
|
|
20847
|
+
filename: "ujson-6.0.0-cp313-cp313-emscripten_5_0_6_wasm32.whl",
|
|
20848
|
+
sha256: "3d4fd009acb9fad7a2070b0e2e498f6257e9c0cc28f806950d8c04cbb6849c24",
|
|
20849
|
+
abiId: "sbxabi1-c2637d04695ad927",
|
|
20850
|
+
requires: [],
|
|
20851
|
+
provenance: {
|
|
20852
|
+
sourceTreeDigest: "2a0c67ecb71628cb89b46090372d11dc4cf6e91c2406260a4e544c17331a31da",
|
|
20853
|
+
recipeSha256: "e9f3fe48e14241bd38d1d20dc691413d8c646984d67ae3898f4b27d10af4bf80",
|
|
20854
|
+
buildTools: [
|
|
20855
|
+
"setuptools==84.0.0",
|
|
20856
|
+
"setuptools-scm==10.2.3"
|
|
20857
|
+
],
|
|
20858
|
+
nativeDependencies: [],
|
|
20859
|
+
patches: []
|
|
20860
|
+
}
|
|
20861
|
+
},
|
|
20532
20862
|
{
|
|
20533
20863
|
name: "uuid-utils",
|
|
20534
20864
|
version: "0.17.1",
|
|
@@ -20611,7 +20941,7 @@ var wheels_default = {
|
|
|
20611
20941
|
|
|
20612
20942
|
// package.json
|
|
20613
20943
|
var package_default = {
|
|
20614
|
-
version: "0.1.
|
|
20944
|
+
version: "0.1.94"};
|
|
20615
20945
|
|
|
20616
20946
|
// src/python/extension-abi.ts
|
|
20617
20947
|
var EXTENSION_ABI = {
|
|
@@ -21649,6 +21979,8 @@ var VirtualTcpSocket = class _VirtualTcpSocket {
|
|
|
21649
21979
|
network;
|
|
21650
21980
|
listenerValue = null;
|
|
21651
21981
|
connectionValue = null;
|
|
21982
|
+
/** Held so closing the guest's socket closes the host's, not just the queues. */
|
|
21983
|
+
hostConnection = null;
|
|
21652
21984
|
boundPort = 0;
|
|
21653
21985
|
boundAddress = "0.0.0.0";
|
|
21654
21986
|
bind(address, port) {
|
|
@@ -21666,13 +21998,35 @@ var VirtualTcpSocket = class _VirtualTcpSocket {
|
|
|
21666
21998
|
}
|
|
21667
21999
|
this.listenerValue = this.network.listen(this.boundPort, this.boundAddress, backlog);
|
|
21668
22000
|
}
|
|
21669
|
-
|
|
22001
|
+
/**
|
|
22002
|
+
* Connect, in the container or out of it.
|
|
22003
|
+
*
|
|
22004
|
+
* The destination used to be discarded, which quietly turned every connect
|
|
22005
|
+
* into a loopback connect: a guest dialling example.com:443 would reach a
|
|
22006
|
+
* container server listening on 443 and believe it had reached the internet.
|
|
22007
|
+
* Loopback is still routed inside the container; anything else goes to the
|
|
22008
|
+
* host's outbound stack, which owns the names and the real sockets, and
|
|
22009
|
+
* which refuses with a reason when the host has no way out.
|
|
22010
|
+
*/
|
|
22011
|
+
async connect(port, address = "127.0.0.1") {
|
|
21670
22012
|
if (this.listenerValue || this.connectionValue) throw new PosixError(Errno.EISCONN);
|
|
21671
22013
|
if (!isLoopback(address)) {
|
|
21672
|
-
|
|
21673
|
-
|
|
21674
|
-
|
|
21675
|
-
|
|
22014
|
+
const outbound = this.network.outbound;
|
|
22015
|
+
if (!outbound) {
|
|
22016
|
+
throw new PosixError(
|
|
22017
|
+
Errno.ENETUNREACH,
|
|
22018
|
+
`cannot reach ${address}:${port}: outbound network access is not available through raw sockets in this container; only loopback is routed`
|
|
22019
|
+
);
|
|
22020
|
+
}
|
|
22021
|
+
const dialled = await outbound.connect(address, port, {
|
|
22022
|
+
address: this.boundAddress,
|
|
22023
|
+
port: this.boundPort
|
|
22024
|
+
});
|
|
22025
|
+
this.connectionValue = dialled.connection;
|
|
22026
|
+
this.hostConnection = dialled.host;
|
|
22027
|
+
this.boundPort = dialled.connection.local.port;
|
|
22028
|
+
this.boundAddress = dialled.connection.local.address;
|
|
22029
|
+
return;
|
|
21676
22030
|
}
|
|
21677
22031
|
this.connectionValue = this.network.connect(port, this.boundPort, this.boundAddress);
|
|
21678
22032
|
this.boundPort = this.connectionValue.local.port;
|
|
@@ -21723,6 +22077,8 @@ var VirtualTcpSocket = class _VirtualTcpSocket {
|
|
|
21723
22077
|
close() {
|
|
21724
22078
|
if (this.listenerValue) this.network.close(this.listenerValue);
|
|
21725
22079
|
this.connectionValue?.close();
|
|
22080
|
+
this.hostConnection?.close();
|
|
22081
|
+
this.hostConnection = null;
|
|
21726
22082
|
this.listenerValue = null;
|
|
21727
22083
|
this.connectionValue = null;
|
|
21728
22084
|
}
|
|
@@ -21787,7 +22143,7 @@ var SocketDescription = class _SocketDescription {
|
|
|
21787
22143
|
this.resource.listen(backlog);
|
|
21788
22144
|
}
|
|
21789
22145
|
connect(address, port) {
|
|
21790
|
-
this.resource.connect(port, address);
|
|
22146
|
+
return this.resource.connect(port, address);
|
|
21791
22147
|
}
|
|
21792
22148
|
accept() {
|
|
21793
22149
|
return new _SocketDescription(this.resource.accept(), this.flags);
|
|
@@ -21799,7 +22155,11 @@ var SocketDescription = class _SocketDescription {
|
|
|
21799
22155
|
this.options.set(`${level}:${option}`, value.slice());
|
|
21800
22156
|
}
|
|
21801
22157
|
getOption(level, option) {
|
|
21802
|
-
|
|
22158
|
+
const stored = this.options.get(`${level}:${option}`);
|
|
22159
|
+
if (stored) return stored.slice();
|
|
22160
|
+
if (level === SOL_SOCKET && option === SO_TYPE) return int32(SOCK_STREAM);
|
|
22161
|
+
if (level === SOL_SOCKET && option === SO_ERROR) return int32(0);
|
|
22162
|
+
return new Uint8Array(4);
|
|
21803
22163
|
}
|
|
21804
22164
|
localAddress() {
|
|
21805
22165
|
return this.resource.local;
|
|
@@ -21810,6 +22170,15 @@ var SocketDescription = class _SocketDescription {
|
|
|
21810
22170
|
return peer;
|
|
21811
22171
|
}
|
|
21812
22172
|
};
|
|
22173
|
+
var SOL_SOCKET = 1;
|
|
22174
|
+
var SO_ERROR = 4;
|
|
22175
|
+
var SO_TYPE = 3;
|
|
22176
|
+
var SOCK_STREAM = 1;
|
|
22177
|
+
function int32(value) {
|
|
22178
|
+
const bytes2 = new Uint8Array(4);
|
|
22179
|
+
new DataView(bytes2.buffer).setInt32(0, value, true);
|
|
22180
|
+
return bytes2;
|
|
22181
|
+
}
|
|
21813
22182
|
function isLoopback(address) {
|
|
21814
22183
|
if (address === "" || address === "localhost" || address === "0.0.0.0") return true;
|
|
21815
22184
|
if (address === "::1" || address === "::" || address === "[::1]") return true;
|
|
@@ -21821,6 +22190,18 @@ var VirtualTcpNetwork = class {
|
|
|
21821
22190
|
}
|
|
21822
22191
|
occupied;
|
|
21823
22192
|
listeners = /* @__PURE__ */ new Map();
|
|
22193
|
+
/** The host's outbound stack, when this host can open sockets at all. */
|
|
22194
|
+
outbound = null;
|
|
22195
|
+
/**
|
|
22196
|
+
* Ports held by in-container servers that are not sockets.
|
|
22197
|
+
*
|
|
22198
|
+
* A JavaScript HTTP server in the container is registered with the request
|
|
22199
|
+
* router and never binds one of these sockets, so a guest connecting to its
|
|
22200
|
+
* port found nothing listening. Before sockets could leave the container
|
|
22201
|
+
* this did not arise -- every guest client went out through the egress --
|
|
22202
|
+
* and afterwards `urllib` dialling a container server got ECONNREFUSED.
|
|
22203
|
+
*/
|
|
22204
|
+
loopbackHttp = null;
|
|
21824
22205
|
listen(port, address = "0.0.0.0", backlog = 128) {
|
|
21825
22206
|
if (this.listeners.has(port) || this.occupied?.(port)) throw new PosixError(Errno.EADDRINUSE, `port ${port} is already in use`);
|
|
21826
22207
|
const listener = new VirtualTcpListener(port, address, backlog);
|
|
@@ -21834,7 +22215,11 @@ var VirtualTcpNetwork = class {
|
|
|
21834
22215
|
}
|
|
21835
22216
|
connect(port, localPort = 0, localAddress = "127.0.0.1") {
|
|
21836
22217
|
const listener = this.listeners.get(port);
|
|
21837
|
-
if (!listener || listener.isClosed)
|
|
22218
|
+
if (!listener || listener.isClosed) {
|
|
22219
|
+
const bridged = this.connectToNonSocketServer(port, localPort, localAddress);
|
|
22220
|
+
if (bridged) return bridged;
|
|
22221
|
+
throw new PosixError(Errno.ECONNREFUSED, `port ${port} is not listening`);
|
|
22222
|
+
}
|
|
21838
22223
|
const client = new VirtualTcpConnection(
|
|
21839
22224
|
{ address: localAddress, port: localPort },
|
|
21840
22225
|
{ address: listener.address, port }
|
|
@@ -21848,6 +22233,26 @@ var VirtualTcpNetwork = class {
|
|
|
21848
22233
|
listener.enqueue(server);
|
|
21849
22234
|
return client;
|
|
21850
22235
|
}
|
|
22236
|
+
/** A connection to a port an in-container server holds without a socket. */
|
|
22237
|
+
connectToNonSocketServer(port, localPort, localAddress) {
|
|
22238
|
+
if (!this.loopbackHttp) return null;
|
|
22239
|
+
const client = new VirtualTcpConnection(
|
|
22240
|
+
{ address: localAddress, port: localPort },
|
|
22241
|
+
{ address: "127.0.0.1", port }
|
|
22242
|
+
);
|
|
22243
|
+
const server = new VirtualTcpConnection(
|
|
22244
|
+
{ address: "127.0.0.1", port },
|
|
22245
|
+
{ address: localAddress, port: localPort }
|
|
22246
|
+
);
|
|
22247
|
+
client.pairWith(server);
|
|
22248
|
+
server.pairWith(client);
|
|
22249
|
+
if (!this.loopbackHttp(port, server)) {
|
|
22250
|
+
client.close();
|
|
22251
|
+
server.close();
|
|
22252
|
+
return null;
|
|
22253
|
+
}
|
|
22254
|
+
return client;
|
|
22255
|
+
}
|
|
21851
22256
|
hasListener(port) {
|
|
21852
22257
|
return this.listeners.has(port);
|
|
21853
22258
|
}
|
|
@@ -21865,7 +22270,7 @@ var POLLIN = 1;
|
|
|
21865
22270
|
var POLLOUT = 4;
|
|
21866
22271
|
var POLLHUP = 16;
|
|
21867
22272
|
var AF_INET = 2;
|
|
21868
|
-
var
|
|
22273
|
+
var SOCK_STREAM2 = 1;
|
|
21869
22274
|
var IPPROTO_TCP = 6;
|
|
21870
22275
|
function createHostAbiServer(proc) {
|
|
21871
22276
|
const files = new FileService(proc.vfs, proc.cred);
|
|
@@ -22156,7 +22561,7 @@ function createHostAbiServer(proc) {
|
|
|
22156
22561
|
const family = r.u32();
|
|
22157
22562
|
const type = r.u32();
|
|
22158
22563
|
const protocol = r.i32();
|
|
22159
|
-
if (family !== AF_INET || (type & 15) !==
|
|
22564
|
+
if (family !== AF_INET || (type & 15) !== SOCK_STREAM2) throw new PosixError(Errno.EAFNOSUPPORT);
|
|
22160
22565
|
if (protocol !== 0 && protocol !== IPPROTO_TCP) throw new PosixError(Errno.EPROTONOSUPPORT);
|
|
22161
22566
|
if (!proc.sockets) throw new PosixError(Errno.ENOSYS);
|
|
22162
22567
|
return { status: proc.table.add(new SocketDescription(new VirtualTcpSocket(proc.sockets))) };
|
|
@@ -22180,9 +22585,15 @@ function createHostAbiServer(proc) {
|
|
|
22180
22585
|
}
|
|
22181
22586
|
case Op.connect: {
|
|
22182
22587
|
const description = socketDescription(proc.table.get(r.i32()));
|
|
22183
|
-
description.connect(r.string(), r.u32());
|
|
22588
|
+
await description.connect(r.string(), r.u32());
|
|
22184
22589
|
return { status: 0 };
|
|
22185
22590
|
}
|
|
22591
|
+
case Op.resolve: {
|
|
22592
|
+
const sockets = proc.sockets;
|
|
22593
|
+
if (!sockets?.outbound) throw new PosixError(Errno.ENOSYS);
|
|
22594
|
+
const address = sockets.outbound.resolve(r.string());
|
|
22595
|
+
return { status: 0, payload: new Writer().string(address).finish() };
|
|
22596
|
+
}
|
|
22186
22597
|
case Op.send: {
|
|
22187
22598
|
const description = socketDescription(proc.table.get(r.i32()));
|
|
22188
22599
|
return { status: await writeBlocking(description, r.bytes32()) };
|
|
@@ -22716,6 +23127,38 @@ async function serve(ctx, connection) {
|
|
|
22716
23127
|
headers: forwarded,
|
|
22717
23128
|
...request.body.length > 0 ? { body: request.body } : {}
|
|
22718
23129
|
});
|
|
23130
|
+
const {
|
|
23131
|
+
"content-encoding": _encoding,
|
|
23132
|
+
"content-length": _length,
|
|
23133
|
+
"transfer-encoding": _chunked,
|
|
23134
|
+
...responseHeaders
|
|
23135
|
+
} = result.headers;
|
|
23136
|
+
writeResponse(connection, result.status, result.statusText, responseHeaders, result.body);
|
|
23137
|
+
} catch (error) {
|
|
23138
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
23139
|
+
try {
|
|
23140
|
+
writeResponse(
|
|
23141
|
+
connection,
|
|
23142
|
+
502,
|
|
23143
|
+
"Bad Gateway",
|
|
23144
|
+
{ "x-sbx-error": "1" },
|
|
23145
|
+
new TextEncoder().encode(`${message}
|
|
23146
|
+
`)
|
|
23147
|
+
);
|
|
23148
|
+
} catch {
|
|
23149
|
+
}
|
|
23150
|
+
} finally {
|
|
23151
|
+
connection.close();
|
|
23152
|
+
}
|
|
23153
|
+
}
|
|
23154
|
+
async function serveHttpOverConnection(connection, handle) {
|
|
23155
|
+
try {
|
|
23156
|
+
const request = await readRequest(connection);
|
|
23157
|
+
if (!request) {
|
|
23158
|
+
writeResponse(connection, 400, "Bad Request", {}, new TextEncoder().encode("malformed request\n"));
|
|
23159
|
+
return;
|
|
23160
|
+
}
|
|
23161
|
+
const result = await handle(request);
|
|
22719
23162
|
writeResponse(connection, result.status, result.statusText, result.headers, result.body);
|
|
22720
23163
|
} catch (error) {
|
|
22721
23164
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -23015,16 +23458,33 @@ def run(main, *, loop_factory=new_event_loop, debug=None, **run_kwargs):
|
|
|
23015
23458
|
with __asyncio.Runner(loop_factory=loop_factory, debug=debug) as runner:
|
|
23016
23459
|
return runner.run(main)
|
|
23017
23460
|
`;
|
|
23018
|
-
|
|
23461
|
+
var HTTPX2_JSFETCH_VERSION = "1.0";
|
|
23462
|
+
var HTTPX2_JSFETCH_SOURCE = `"""SandboxedJs stand-in for httpx2-jsfetch.
|
|
23463
|
+
|
|
23464
|
+
The real package drives the browser fetch API through Pyodide's \`js\` module.
|
|
23465
|
+
This runtime has no \`js\` module but does have sockets, so httpx2's regular
|
|
23466
|
+
httpcore2 transports are used under the names httpx2 imports on Emscripten.
|
|
23467
|
+
"""
|
|
23468
|
+
|
|
23469
|
+
from httpx2._transports.default import (
|
|
23470
|
+
AsyncHTTPTransport as AsyncJavascriptFetchTransport,
|
|
23471
|
+
HTTPTransport as JavascriptFetchTransport,
|
|
23472
|
+
)
|
|
23473
|
+
|
|
23474
|
+
__all__ = ["AsyncJavascriptFetchTransport", "JavascriptFetchTransport"]
|
|
23475
|
+
`;
|
|
23476
|
+
function builtinDistribution(name, version, summary, files, requires = []) {
|
|
23019
23477
|
return {
|
|
23020
23478
|
version,
|
|
23479
|
+
requires,
|
|
23021
23480
|
files: {
|
|
23022
23481
|
...files,
|
|
23023
23482
|
[`${name}-${version}.dist-info/METADATA`]: `Metadata-Version: 2.1
|
|
23024
23483
|
Name: ${name}
|
|
23025
23484
|
Version: ${version}
|
|
23026
23485
|
Summary: ${summary}
|
|
23027
|
-
|
|
23486
|
+
` + requires.map((r) => `Requires-Dist: ${r}
|
|
23487
|
+
`).join(""),
|
|
23028
23488
|
[`${name}-${version}.dist-info/WHEEL`]: "Wheel-Version: 1.0\nGenerator: sandboxedjs\nRoot-Is-Purelib: true\nTag: py3-none-any\n",
|
|
23029
23489
|
[`${name}-${version}.dist-info/INSTALLER`]: "sandboxedjs\n",
|
|
23030
23490
|
[`${name}-${version}.dist-info/RECORD`]: ""
|
|
@@ -23032,6 +23492,13 @@ Summary: ${summary}
|
|
|
23032
23492
|
};
|
|
23033
23493
|
}
|
|
23034
23494
|
var BUILTINS3 = {
|
|
23495
|
+
"httpx2-jsfetch": builtinDistribution(
|
|
23496
|
+
"httpx2_jsfetch",
|
|
23497
|
+
HTTPX2_JSFETCH_VERSION,
|
|
23498
|
+
"SandboxedJs stand-in: httpx2's socket transports under the jsfetch names",
|
|
23499
|
+
{ "httpx2_jsfetch/__init__.py": HTTPX2_JSFETCH_SOURCE },
|
|
23500
|
+
["httpcore2[asyncio]", "anyio>=4.10", "truststore>=0.10"]
|
|
23501
|
+
),
|
|
23035
23502
|
greenlet: builtinDistribution(
|
|
23036
23503
|
"greenlet",
|
|
23037
23504
|
GREENLET_VERSION,
|
|
@@ -23056,7 +23523,7 @@ function builtinCandidates(name) {
|
|
|
23056
23523
|
filename: `${name}-${builtin.version}-py3-none-any.whl`,
|
|
23057
23524
|
sha256: "",
|
|
23058
23525
|
metadataUrl: null,
|
|
23059
|
-
indexedRequires:
|
|
23526
|
+
indexedRequires: builtin.requires
|
|
23060
23527
|
}];
|
|
23061
23528
|
}
|
|
23062
23529
|
function isBuiltinOnly(name) {
|
|
@@ -23240,7 +23707,7 @@ async function resolve2(options) {
|
|
|
23240
23707
|
if (viable.length === 0) {
|
|
23241
23708
|
const wanted = [...unusable.get(name)?.versions ?? []].filter((version) => clauses.every((s) => compareVersions(version, s.version, s.op)));
|
|
23242
23709
|
const nothingUsable = all.length === 0 || wanted.length > 0;
|
|
23243
|
-
const
|
|
23710
|
+
const error = new ResolutionError(
|
|
23244
23711
|
nothingUsable ? `no distribution of ${name}${all.length > 0 ? ` ${newest(wanted)}` : ""} is usable by this runtime (it needs a pure-Python wheel or one tagged ${WHEEL_TAG})` : `no version of ${name} satisfies every requirement`,
|
|
23245
23712
|
explain(name, next.constraints, null, all),
|
|
23246
23713
|
nothingUsable ? {
|
|
@@ -23252,8 +23719,8 @@ async function resolve2(options) {
|
|
|
23252
23719
|
sourceAvailable: unusable.get(name)?.sourceAvailable ?? false
|
|
23253
23720
|
} : { reason: "conflicting-requirements", package: name }
|
|
23254
23721
|
);
|
|
23255
|
-
nogoods.set(signature,
|
|
23256
|
-
throw
|
|
23722
|
+
nogoods.set(signature, error);
|
|
23723
|
+
throw error;
|
|
23257
23724
|
}
|
|
23258
23725
|
let failure2 = null;
|
|
23259
23726
|
const seenVersions = /* @__PURE__ */ new Set();
|
|
@@ -23265,9 +23732,9 @@ async function resolve2(options) {
|
|
|
23265
23732
|
let requires;
|
|
23266
23733
|
try {
|
|
23267
23734
|
requires = await requiresFor(candidate);
|
|
23268
|
-
} catch (
|
|
23735
|
+
} catch (error) {
|
|
23269
23736
|
failure2 = new ResolutionError(
|
|
23270
|
-
`could not read the dependencies of ${name} ${candidate.version}: ${
|
|
23737
|
+
`could not read the dependencies of ${name} ${candidate.version}: ${error.message}`
|
|
23271
23738
|
);
|
|
23272
23739
|
continue;
|
|
23273
23740
|
}
|
|
@@ -23291,15 +23758,13 @@ async function resolve2(options) {
|
|
|
23291
23758
|
chosen.set(name, { ...candidate, requires });
|
|
23292
23759
|
try {
|
|
23293
23760
|
return await search([...edges, ...rest], { chosen, constraints: next.constraints });
|
|
23294
|
-
} catch (
|
|
23295
|
-
if (!(
|
|
23296
|
-
if (/cancelled|budget/.test(
|
|
23297
|
-
failure2 =
|
|
23761
|
+
} catch (error) {
|
|
23762
|
+
if (!(error instanceof ResolutionError)) throw error;
|
|
23763
|
+
if (/cancelled|budget/.test(error.message)) throw error;
|
|
23764
|
+
failure2 = error;
|
|
23298
23765
|
}
|
|
23299
23766
|
}
|
|
23300
|
-
|
|
23301
|
-
nogoods.set(signature, error);
|
|
23302
|
-
throw error;
|
|
23767
|
+
throw failure2 ?? new ResolutionError(`could not resolve ${name}`);
|
|
23303
23768
|
};
|
|
23304
23769
|
const seeded = /* @__PURE__ */ new Map();
|
|
23305
23770
|
for (const root of roots) {
|
|
@@ -23805,8 +24270,18 @@ async function runOwnedPython(ctx, argv) {
|
|
|
23805
24270
|
if (!manifest) throw new Error("no Python runtime manifest is configured");
|
|
23806
24271
|
const mounts = containerMounts(ctx);
|
|
23807
24272
|
const env2 = withSitePackages(ctx.env);
|
|
24273
|
+
await ensureTrustStore(ctx);
|
|
23808
24274
|
const egressPort = ensureHttpEgress(ctx);
|
|
23809
24275
|
if (egressPort > 0) env2[EGRESS_ENV] = `127.0.0.1:${egressPort}`;
|
|
24276
|
+
try {
|
|
24277
|
+
if (ctx.vfs.stat(CA_BUNDLE_PATH, { cred: ctx.cred }).size > 0) {
|
|
24278
|
+
env2.SSL_CERT_FILE = CA_BUNDLE_PATH;
|
|
24279
|
+
env2.SSL_CERT_DIR = "/etc/ssl/certs";
|
|
24280
|
+
env2.REQUESTS_CA_BUNDLE = CA_BUNDLE_PATH;
|
|
24281
|
+
}
|
|
24282
|
+
} catch {
|
|
24283
|
+
}
|
|
24284
|
+
if (ctx.kernel.pod.sockets?.outbound) env2.SBX_OUTBOUND_SOCKETS = "1";
|
|
23810
24285
|
env2.SBX_SERIAL_HOST_CALLS = "1";
|
|
23811
24286
|
const inherit = claimInheritance(env2);
|
|
23812
24287
|
const process2 = await startPythonProcess({
|
|
@@ -23847,6 +24322,200 @@ function isTerminal(ctx) {
|
|
|
23847
24322
|
const stdin = ctx.stdin;
|
|
23848
24323
|
return Boolean(stdin.isTTY || stdin.interactive);
|
|
23849
24324
|
}
|
|
24325
|
+
var TLS_MODULE_SOURCE = `"""TLS over a SandboxedJs socket, using this interpreter's own OpenSSL.
|
|
24326
|
+
|
|
24327
|
+
CPython's \`ssl\` cannot wrap one of these sockets directly: \`_ssl\` wants a real
|
|
24328
|
+
\`_socket.socket\` whose descriptor OpenSSL can read and write, and a socket here
|
|
24329
|
+
is a host resource reached through the SandboxedJs ABI. But OpenSSL does not
|
|
24330
|
+
need a descriptor -- \`SSLContext.wrap_bio\` drives the same protocol over a pair
|
|
24331
|
+
of memory buffers, which is how asyncio has always done TLS. So the handshake
|
|
24332
|
+
and every record are performed here, in the container, and only the ciphertext
|
|
24333
|
+
crosses to the host. Certificates are verified by this interpreter against the
|
|
24334
|
+
trust store the host writes into the image, and \`getpeercert\` answers.
|
|
24335
|
+
"""
|
|
24336
|
+
|
|
24337
|
+
import io
|
|
24338
|
+
import socket as _socket_module
|
|
24339
|
+
import ssl as _ssl
|
|
24340
|
+
|
|
24341
|
+
|
|
24342
|
+
def wrappable(sock):
|
|
24343
|
+
"""Whether this socket is one of the container's own."""
|
|
24344
|
+
target = getattr(sock, "_sock", sock)
|
|
24345
|
+
return hasattr(target, "_fd")
|
|
24346
|
+
|
|
24347
|
+
|
|
24348
|
+
class SbxTlsSocket:
|
|
24349
|
+
"""What \`SSLContext.wrap_socket\` returns for a container socket."""
|
|
24350
|
+
|
|
24351
|
+
def __init__(self, context, sock, server_hostname, suppress_ragged_eofs=True):
|
|
24352
|
+
self._sock = sock
|
|
24353
|
+
self.context = context
|
|
24354
|
+
self.server_hostname = server_hostname
|
|
24355
|
+
self.suppress_ragged_eofs = suppress_ragged_eofs
|
|
24356
|
+
self._incoming = _ssl.MemoryBIO()
|
|
24357
|
+
self._outgoing = _ssl.MemoryBIO()
|
|
24358
|
+
self._closed = False
|
|
24359
|
+
self._tls = context.wrap_bio(
|
|
24360
|
+
self._incoming, self._outgoing,
|
|
24361
|
+
server_side=False,
|
|
24362
|
+
server_hostname=server_hostname,
|
|
24363
|
+
)
|
|
24364
|
+
|
|
24365
|
+
# -- moving bytes between OpenSSL and the socket ------------------------
|
|
24366
|
+
def _flush(self):
|
|
24367
|
+
data = self._outgoing.read()
|
|
24368
|
+
if data:
|
|
24369
|
+
self._sock.sendall(data)
|
|
24370
|
+
|
|
24371
|
+
def _feed(self):
|
|
24372
|
+
chunk = self._sock.recv(32768)
|
|
24373
|
+
if not chunk:
|
|
24374
|
+
self._incoming.write_eof()
|
|
24375
|
+
return False
|
|
24376
|
+
self._incoming.write(chunk)
|
|
24377
|
+
return True
|
|
24378
|
+
|
|
24379
|
+
def _run(self, operation):
|
|
24380
|
+
"""Drive one OpenSSL operation, pumping the socket as it asks."""
|
|
24381
|
+
while True:
|
|
24382
|
+
try:
|
|
24383
|
+
result = operation()
|
|
24384
|
+
except _ssl.SSLWantReadError:
|
|
24385
|
+
self._flush()
|
|
24386
|
+
if not self._feed():
|
|
24387
|
+
raise
|
|
24388
|
+
continue
|
|
24389
|
+
except _ssl.SSLWantWriteError:
|
|
24390
|
+
self._flush()
|
|
24391
|
+
continue
|
|
24392
|
+
self._flush()
|
|
24393
|
+
return result
|
|
24394
|
+
|
|
24395
|
+
# -- the handshake ------------------------------------------------------
|
|
24396
|
+
def do_handshake(self, block=False):
|
|
24397
|
+
self._run(self._tls.do_handshake)
|
|
24398
|
+
|
|
24399
|
+
# -- the socket ---------------------------------------------------------
|
|
24400
|
+
def recv(self, length=1024, flags=0):
|
|
24401
|
+
if flags:
|
|
24402
|
+
raise OSError("socket flags are not supported")
|
|
24403
|
+
try:
|
|
24404
|
+
return self._run(lambda: self._tls.read(length))
|
|
24405
|
+
except _ssl.SSLZeroReturnError:
|
|
24406
|
+
return b""
|
|
24407
|
+
except _ssl.SSLWantReadError:
|
|
24408
|
+
# The peer closed without close_notify. Ragged, and normal enough
|
|
24409
|
+
# on the public internet that clients ask to tolerate it.
|
|
24410
|
+
if self.suppress_ragged_eofs:
|
|
24411
|
+
return b""
|
|
24412
|
+
raise
|
|
24413
|
+
|
|
24414
|
+
def recv_into(self, buffer, nbytes=0, flags=0):
|
|
24415
|
+
data = self.recv(nbytes or len(buffer), flags)
|
|
24416
|
+
buffer[:len(data)] = data
|
|
24417
|
+
return len(data)
|
|
24418
|
+
|
|
24419
|
+
def read(self, length=1024, buffer=None):
|
|
24420
|
+
if buffer is not None:
|
|
24421
|
+
return self.recv_into(buffer, length)
|
|
24422
|
+
return self.recv(length)
|
|
24423
|
+
|
|
24424
|
+
def send(self, data, flags=0):
|
|
24425
|
+
if flags:
|
|
24426
|
+
raise OSError("socket flags are not supported")
|
|
24427
|
+
return self._run(lambda: self._tls.write(data))
|
|
24428
|
+
|
|
24429
|
+
def sendall(self, data, flags=0):
|
|
24430
|
+
view = memoryview(data)
|
|
24431
|
+
while view:
|
|
24432
|
+
view = view[self.send(view, flags):]
|
|
24433
|
+
|
|
24434
|
+
def write(self, data):
|
|
24435
|
+
return self.send(data)
|
|
24436
|
+
|
|
24437
|
+
_io_refs = 0
|
|
24438
|
+
|
|
24439
|
+
def _decref_socketios(self):
|
|
24440
|
+
# socket.SocketIO calls this when a file object built on this socket is
|
|
24441
|
+
# closed; without it http.client failed with "no attribute
|
|
24442
|
+
# _decref_socketios" the moment it read a response.
|
|
24443
|
+
if self._io_refs > 0:
|
|
24444
|
+
self._io_refs -= 1
|
|
24445
|
+
|
|
24446
|
+
def makefile(self, mode="r", buffering=None, *, encoding=None, errors=None, newline=None):
|
|
24447
|
+
# http.client and everything built on it read the response through a
|
|
24448
|
+
# file object, so this has to work like socket.makefile does.
|
|
24449
|
+
raw = _socket_module.SocketIO(self, "rw" if "w" in mode else "r")
|
|
24450
|
+
self._io_refs += 1
|
|
24451
|
+
buffer = io.BufferedRWPair(raw, raw) if "w" in mode and "r" in mode else (
|
|
24452
|
+
io.BufferedWriter(raw) if "w" in mode else io.BufferedReader(raw)
|
|
24453
|
+
)
|
|
24454
|
+
if "b" in mode:
|
|
24455
|
+
return buffer
|
|
24456
|
+
text = io.TextIOWrapper(buffer, encoding, errors, newline)
|
|
24457
|
+
text.mode = mode
|
|
24458
|
+
return text
|
|
24459
|
+
|
|
24460
|
+
def settimeout(self, value): return self._sock.settimeout(value)
|
|
24461
|
+
def gettimeout(self): return self._sock.gettimeout()
|
|
24462
|
+
def setblocking(self, flag): return self._sock.setblocking(flag)
|
|
24463
|
+
def getblocking(self): return self._sock.getblocking()
|
|
24464
|
+
def setsockopt(self, *args): return self._sock.setsockopt(*args)
|
|
24465
|
+
def getsockopt(self, *args): return self._sock.getsockopt(*args)
|
|
24466
|
+
def fileno(self): return self._sock.fileno()
|
|
24467
|
+
def getpeername(self): return self._sock.getpeername()
|
|
24468
|
+
def getsockname(self): return self._sock.getsockname()
|
|
24469
|
+
def detach(self): return self._sock.detach()
|
|
24470
|
+
|
|
24471
|
+
def shutdown(self, how):
|
|
24472
|
+
return self._sock.shutdown(how)
|
|
24473
|
+
|
|
24474
|
+
def unwrap(self):
|
|
24475
|
+
self._run(self._tls.unwrap)
|
|
24476
|
+
return self._sock
|
|
24477
|
+
|
|
24478
|
+
def close(self):
|
|
24479
|
+
if not self._closed:
|
|
24480
|
+
self._closed = True
|
|
24481
|
+
self._sock.close()
|
|
24482
|
+
|
|
24483
|
+
def __enter__(self): return self
|
|
24484
|
+
def __exit__(self, *args): self.close()
|
|
24485
|
+
|
|
24486
|
+
@property
|
|
24487
|
+
def family(self): return getattr(self._sock, "family", _socket_module.AF_INET)
|
|
24488
|
+
@property
|
|
24489
|
+
def type(self): return getattr(self._sock, "type", _socket_module.SOCK_STREAM)
|
|
24490
|
+
@property
|
|
24491
|
+
def proto(self): return getattr(self._sock, "proto", 0)
|
|
24492
|
+
@property
|
|
24493
|
+
def session(self): return self._tls.session
|
|
24494
|
+
@session.setter
|
|
24495
|
+
def session(self, value): self._tls.session = value
|
|
24496
|
+
@property
|
|
24497
|
+
def session_reused(self): return self._tls.session_reused
|
|
24498
|
+
|
|
24499
|
+
# -- what OpenSSL knows about this connection ---------------------------
|
|
24500
|
+
def getpeercert(self, binary_form=False): return self._tls.getpeercert(binary_form)
|
|
24501
|
+
def get_unverified_chain(self): return self._tls.get_unverified_chain()
|
|
24502
|
+
def get_verified_chain(self): return self._tls.get_verified_chain()
|
|
24503
|
+
def cipher(self): return self._tls.cipher()
|
|
24504
|
+
def shared_ciphers(self): return self._tls.shared_ciphers()
|
|
24505
|
+
def compression(self): return self._tls.compression()
|
|
24506
|
+
def selected_alpn_protocol(self): return self._tls.selected_alpn_protocol()
|
|
24507
|
+
def selected_npn_protocol(self): return None
|
|
24508
|
+
def version(self): return self._tls.version()
|
|
24509
|
+
def pending(self): return self._tls.pending()
|
|
24510
|
+
def get_channel_binding(self, cb_type="tls-unique"):
|
|
24511
|
+
return self._tls.get_channel_binding(cb_type)
|
|
24512
|
+
|
|
24513
|
+
|
|
24514
|
+
def wrap(context, sock, server_hostname, suppress_ragged_eofs=True):
|
|
24515
|
+
wrapped = SbxTlsSocket(context, sock, server_hostname, suppress_ragged_eofs)
|
|
24516
|
+
wrapped.do_handshake()
|
|
24517
|
+
return wrapped
|
|
24518
|
+
`;
|
|
23850
24519
|
var SHIM_HEADER = "# generated by sandboxedjs - safe to delete or replace\n";
|
|
23851
24520
|
var SHIM_SOURCE = `"""Rebuild os.system and os.popen on the container's process table."""
|
|
23852
24521
|
import io, os, subprocess, sys
|
|
@@ -23952,8 +24621,82 @@ if sys.platform in ("emscripten", "wasi"):
|
|
|
23952
24621
|
|
|
23953
24622
|
sys.meta_path.insert(0, _SbxAsyncioWatcherHook())
|
|
23954
24623
|
|
|
24624
|
+
class _SbxPyodideFetchHook:
|
|
24625
|
+
"""Libraries read \`sys.platform == "emscripten"\` as "running in Pyodide"
|
|
24626
|
+
and switch to a browser-fetch backend that imports Pyodide's \`js\`
|
|
24627
|
+
module. This runtime has no \`js\` but does have sockets, so those
|
|
24628
|
+
backends are replaced by modules that leave the socket path in place.
|
|
24629
|
+
urllib3 (and so requests) imports its backend at package import, which
|
|
24630
|
+
made anything using requests fail with "No module named 'js'"."""
|
|
23955
24631
|
|
|
23956
|
-
|
|
24632
|
+
_STANDINS = {
|
|
24633
|
+
# urllib3/__init__.py calls inject_into_urllib3() on emscripten.
|
|
24634
|
+
"urllib3.contrib.emscripten": "def inject_into_urllib3():\\n pass\\n",
|
|
24635
|
+
}
|
|
24636
|
+
|
|
24637
|
+
def find_spec(self, name, path=None, target=None):
|
|
24638
|
+
source = self._STANDINS.get(name)
|
|
24639
|
+
if source is None:
|
|
24640
|
+
return None
|
|
24641
|
+
import importlib.util
|
|
24642
|
+
|
|
24643
|
+
class _Loader:
|
|
24644
|
+
def create_module(self, spec):
|
|
24645
|
+
return None
|
|
24646
|
+
|
|
24647
|
+
def exec_module(self, module):
|
|
24648
|
+
exec(compile(source, f"<sandboxedjs {name}>", "exec"), module.__dict__)
|
|
24649
|
+
|
|
24650
|
+
return importlib.util.spec_from_loader(name, _Loader(), is_package=False)
|
|
24651
|
+
|
|
24652
|
+
sys.meta_path.insert(0, _SbxPyodideFetchHook())
|
|
24653
|
+
|
|
24654
|
+
|
|
24655
|
+
# The egress shims exist for a host that cannot open sockets. Where the host
|
|
24656
|
+
# can, the guest's own clients are used instead: real streaming, real TLS, and
|
|
24657
|
+
# whatever protocol the library speaks rather than the subset HTTP-over-egress
|
|
24658
|
+
# allows. Both worlds are supported, and which one this is comes from the host
|
|
24659
|
+
# rather than from probing.
|
|
24660
|
+
if os.environ.get("SBX_OUTBOUND_SOCKETS") == "1":
|
|
24661
|
+
|
|
24662
|
+
class _SbxTruststoreHook:
|
|
24663
|
+
"""truststore verifies the peer's chain itself, against the operating
|
|
24664
|
+
system's trust store. In this container the handshake happens on the
|
|
24665
|
+
host, which verified the chain against exactly that store, and the
|
|
24666
|
+
chain is not carried back -- so truststore's own check cannot run and
|
|
24667
|
+
httpx2 failed with "no attribute get_unverified_chain". Its context is
|
|
24668
|
+
therefore the standard one: same verification, done once, where the
|
|
24669
|
+
connection is."""
|
|
24670
|
+
|
|
24671
|
+
def find_spec(self, name, path=None, target=None):
|
|
24672
|
+
if name != "truststore":
|
|
24673
|
+
return None
|
|
24674
|
+
import importlib.util
|
|
24675
|
+
|
|
24676
|
+
class _Loader:
|
|
24677
|
+
def create_module(self, spec):
|
|
24678
|
+
return None
|
|
24679
|
+
|
|
24680
|
+
def exec_module(self, module):
|
|
24681
|
+
import ssl as _ssl
|
|
24682
|
+
module.SSLContext = _ssl.SSLContext
|
|
24683
|
+
|
|
24684
|
+
def inject_into_ssl():
|
|
24685
|
+
pass
|
|
24686
|
+
|
|
24687
|
+
def extract_from_ssl():
|
|
24688
|
+
pass
|
|
24689
|
+
|
|
24690
|
+
module.inject_into_ssl = inject_into_ssl
|
|
24691
|
+
module.extract_from_ssl = extract_from_ssl
|
|
24692
|
+
module.__version__ = "sandboxedjs"
|
|
24693
|
+
|
|
24694
|
+
return importlib.util.spec_from_loader(name, _Loader(), is_package=True)
|
|
24695
|
+
|
|
24696
|
+
sys.meta_path.insert(0, _SbxTruststoreHook())
|
|
24697
|
+
|
|
24698
|
+
|
|
24699
|
+
_egress = None if os.environ.get("SBX_OUTBOUND_SOCKETS") == "1" else os.environ.get("SBX_HTTP_EGRESS")
|
|
23957
24700
|
if _egress:
|
|
23958
24701
|
import http.client
|
|
23959
24702
|
import urllib.request
|
|
@@ -24033,6 +24776,255 @@ if _egress:
|
|
|
24033
24776
|
http.client.HTTPConnection = _SbxEgressConnection
|
|
24034
24777
|
http.client.HTTPSConnection = _SbxEgressTlsConnection
|
|
24035
24778
|
|
|
24779
|
+
# Everything above routes code that reaches for the standard library. The
|
|
24780
|
+
# popular HTTP clients do not: urllib3 (so requests), httpcore (so httpx)
|
|
24781
|
+
# and httpcore2 (so httpx2) open sockets themselves, and raw sockets do not
|
|
24782
|
+
# leave this container -- connect() to anything but loopback is
|
|
24783
|
+
# ENETUNREACH, and CPython's ssl cannot wrap these sockets anyway. So each
|
|
24784
|
+
# client's connection layer is replaced by one that speaks plain HTTP to
|
|
24785
|
+
# the egress endpoint with the absolute URL in the request line, which is
|
|
24786
|
+
# what the standard-library shims above already do.
|
|
24787
|
+
|
|
24788
|
+
def _sbx_egress_request(method, url, headers, body, timeout=None):
|
|
24789
|
+
"""One request through the egress. Returns (status, reason, headers, body)."""
|
|
24790
|
+
connection = _RealHTTPConnection(_egress_host, _egress_port, timeout=timeout)
|
|
24791
|
+
try:
|
|
24792
|
+
connection.request(method, url, body, {k: v for k, v in headers if k.lower() != "host"})
|
|
24793
|
+
response = connection.getresponse()
|
|
24794
|
+
payload = response.read()
|
|
24795
|
+
if response.getheader("x-sbx-error"):
|
|
24796
|
+
raise OSError(payload.decode("utf-8", "replace").strip())
|
|
24797
|
+
return response.status, response.reason, response.getheaders(), payload
|
|
24798
|
+
finally:
|
|
24799
|
+
connection.close()
|
|
24800
|
+
|
|
24801
|
+
async def _sbx_egress_request_async(method, url, headers, body):
|
|
24802
|
+
"""The same hop as _sbx_egress_request, over asyncio streams."""
|
|
24803
|
+
import asyncio
|
|
24804
|
+
reader, writer = await asyncio.open_connection(_egress_host, _egress_port)
|
|
24805
|
+
try:
|
|
24806
|
+
payload = body or b""
|
|
24807
|
+
if isinstance(payload, str):
|
|
24808
|
+
payload = payload.encode()
|
|
24809
|
+
lines = [f"{method} {url} HTTP/1.1", f"Host: {_egress_host}:{_egress_port}"]
|
|
24810
|
+
for key, value in headers:
|
|
24811
|
+
if key.lower() in ("host", "connection", "content-length", "transfer-encoding"):
|
|
24812
|
+
continue
|
|
24813
|
+
lines.append(f"{key}: {value}")
|
|
24814
|
+
lines.append(f"Content-Length: {len(payload)}")
|
|
24815
|
+
# Read to EOF rather than tracking keep-alive framing: one request
|
|
24816
|
+
# per connection, which is what this hop is for.
|
|
24817
|
+
lines.append("Connection: close")
|
|
24818
|
+
writer.write(("\\r\\n".join(lines) + "\\r\\n\\r\\n").encode("latin-1") + payload)
|
|
24819
|
+
await writer.drain()
|
|
24820
|
+
|
|
24821
|
+
head = await reader.readuntil(b"\\r\\n\\r\\n")
|
|
24822
|
+
start, _, rest = head.decode("latin-1").partition("\\r\\n")
|
|
24823
|
+
_, _, status_text = start.partition(" ")
|
|
24824
|
+
code, _, reason = status_text.partition(" ")
|
|
24825
|
+
received = []
|
|
24826
|
+
for line in rest.split("\\r\\n"):
|
|
24827
|
+
key, sep, value = line.partition(":")
|
|
24828
|
+
if sep:
|
|
24829
|
+
received.append((key.strip(), value.strip()))
|
|
24830
|
+
content = dict((k.lower(), v) for k, v in received)
|
|
24831
|
+
if "content-length" in content:
|
|
24832
|
+
data = await reader.readexactly(int(content["content-length"]))
|
|
24833
|
+
else:
|
|
24834
|
+
data = await reader.read()
|
|
24835
|
+
if content.get("x-sbx-error"):
|
|
24836
|
+
raise OSError(data.decode("utf-8", "replace").strip())
|
|
24837
|
+
return int(code), reason, received, data
|
|
24838
|
+
finally:
|
|
24839
|
+
writer.close()
|
|
24840
|
+
|
|
24841
|
+
class _SbxUrllib3Hook:
|
|
24842
|
+
"""urllib3's emscripten backend is Pyodide's \`js\` fetch, which this
|
|
24843
|
+
runtime has no module for; its socket backend cannot leave the
|
|
24844
|
+
container. Both connection classes become egress ones."""
|
|
24845
|
+
|
|
24846
|
+
def find_spec(self, name, path=None, target=None):
|
|
24847
|
+
if name != "urllib3.contrib.emscripten":
|
|
24848
|
+
return None
|
|
24849
|
+
import importlib.util
|
|
24850
|
+
|
|
24851
|
+
def inject_into_urllib3():
|
|
24852
|
+
import urllib3.connection
|
|
24853
|
+
from urllib3.connectionpool import HTTPConnectionPool, HTTPSConnectionPool
|
|
24854
|
+
|
|
24855
|
+
class _Plain(urllib3.connection.HTTPConnection):
|
|
24856
|
+
"""urllib3's own connection, dialled at the egress.
|
|
24857
|
+
|
|
24858
|
+
urllib3.connection.HTTPConnection still derives from
|
|
24859
|
+
http.client.HTTPConnection, so request building is
|
|
24860
|
+
inherited; only where it connects and what goes in the
|
|
24861
|
+
request line change.
|
|
24862
|
+
"""
|
|
24863
|
+
|
|
24864
|
+
_sbx_scheme = "http"
|
|
24865
|
+
default_port = 80
|
|
24866
|
+
is_verified = True
|
|
24867
|
+
proxy_is_verified = None
|
|
24868
|
+
|
|
24869
|
+
def __init__(self, host, port=None, **kwargs):
|
|
24870
|
+
# A distinct name from the http.client shim's
|
|
24871
|
+
# _sbx_origin: that class is further up this MRO and
|
|
24872
|
+
# its __init__ runs after this one, so sharing the name
|
|
24873
|
+
# overwrote the real destination with the egress and
|
|
24874
|
+
# every request went out addressed to the egress itself.
|
|
24875
|
+
self._sbx_target = (host, port or self.default_port)
|
|
24876
|
+
for unsupported in ("ssl_context", "ssl_version", "ssl_minimum_version",
|
|
24877
|
+
"ssl_maximum_version", "assert_hostname",
|
|
24878
|
+
"assert_fingerprint", "server_hostname", "ca_certs",
|
|
24879
|
+
"ca_cert_dir", "ca_cert_data", "cert_file", "key_file",
|
|
24880
|
+
"key_password", "cert_reqs"):
|
|
24881
|
+
kwargs.pop(unsupported, None)
|
|
24882
|
+
super().__init__(_egress_host, port=_egress_port, **kwargs)
|
|
24883
|
+
|
|
24884
|
+
def _sbx_absolute(self, url):
|
|
24885
|
+
host, port = self._sbx_target
|
|
24886
|
+
if url.startswith("http://") or url.startswith("https://"):
|
|
24887
|
+
return url
|
|
24888
|
+
netloc = host if port == self.default_port else f"{host}:{port}"
|
|
24889
|
+
return f"{self._sbx_scheme}://{netloc}{url}"
|
|
24890
|
+
|
|
24891
|
+
# urllib3 v2 writes the request line in its own request()
|
|
24892
|
+
# rather than through http.client's putrequest, so both
|
|
24893
|
+
# are absolutised; the egress refuses an origin-form target.
|
|
24894
|
+
def request(self, method, url, *args, **kwargs):
|
|
24895
|
+
return super().request(method, self._sbx_absolute(url), *args, **kwargs)
|
|
24896
|
+
|
|
24897
|
+
def putrequest(self, method, url, *args, **kwargs):
|
|
24898
|
+
return super().putrequest(method, self._sbx_absolute(url), *args, **kwargs)
|
|
24899
|
+
|
|
24900
|
+
def set_tunnel(self, *args, **kwargs):
|
|
24901
|
+
raise OSError("this container reaches the network through its egress, not CONNECT")
|
|
24902
|
+
|
|
24903
|
+
class _Tls(_Plain):
|
|
24904
|
+
_sbx_scheme = "https"
|
|
24905
|
+
default_port = 443
|
|
24906
|
+
|
|
24907
|
+
HTTPConnectionPool.ConnectionCls = _Plain
|
|
24908
|
+
HTTPSConnectionPool.ConnectionCls = _Tls
|
|
24909
|
+
urllib3.connection.HTTPConnection = _Plain
|
|
24910
|
+
urllib3.connection.HTTPSConnection = _Tls
|
|
24911
|
+
|
|
24912
|
+
class _Loader:
|
|
24913
|
+
def create_module(self, spec):
|
|
24914
|
+
return None
|
|
24915
|
+
|
|
24916
|
+
def exec_module(self, module):
|
|
24917
|
+
module.inject_into_urllib3 = inject_into_urllib3
|
|
24918
|
+
|
|
24919
|
+
return importlib.util.spec_from_loader(name, _Loader(), is_package=True)
|
|
24920
|
+
|
|
24921
|
+
sys.meta_path.insert(0, _SbxUrllib3Hook())
|
|
24922
|
+
|
|
24923
|
+
def _sbx_httpx_transports(httpx_module):
|
|
24924
|
+
"""Sync and async httpx transports that send through the egress."""
|
|
24925
|
+
|
|
24926
|
+
class _Transport(httpx_module.BaseTransport):
|
|
24927
|
+
def __init__(self, *args, **kwargs):
|
|
24928
|
+
pass
|
|
24929
|
+
|
|
24930
|
+
def handle_request(self, request):
|
|
24931
|
+
body = request.read()
|
|
24932
|
+
sent = [(k.decode("latin-1"), v.decode("latin-1")) for k, v in request.headers.raw]
|
|
24933
|
+
status, reason, headers, payload = _sbx_egress_request(
|
|
24934
|
+
request.method, str(request.url), sent, body,
|
|
24935
|
+
)
|
|
24936
|
+
# Content-Length/Transfer-Encoding describe the egress hop's
|
|
24937
|
+
# framing, and httpx re-derives them from the body it is given.
|
|
24938
|
+
keep = [(k, v) for k, v in headers
|
|
24939
|
+
if k.lower() not in ("content-length", "transfer-encoding", "content-encoding")]
|
|
24940
|
+
return httpx_module.Response(
|
|
24941
|
+
status, headers=keep, content=payload, request=request,
|
|
24942
|
+
extensions={"reason_phrase": (reason or "").encode()},
|
|
24943
|
+
)
|
|
24944
|
+
|
|
24945
|
+
def close(self):
|
|
24946
|
+
pass
|
|
24947
|
+
|
|
24948
|
+
def __enter__(self):
|
|
24949
|
+
return self
|
|
24950
|
+
|
|
24951
|
+
def __exit__(self, *args):
|
|
24952
|
+
pass
|
|
24953
|
+
|
|
24954
|
+
class _AsyncTransport(httpx_module.AsyncBaseTransport):
|
|
24955
|
+
def __init__(self, *args, **kwargs):
|
|
24956
|
+
pass
|
|
24957
|
+
|
|
24958
|
+
async def handle_async_request(self, request):
|
|
24959
|
+
# Not the blocking client on the loop thread: this image
|
|
24960
|
+
# proxies every syscall to one thread, so a blocking recv
|
|
24961
|
+
# inside a coroutine stops the loop that must service it. The
|
|
24962
|
+
# egress is loopback, which asyncio streams reach.
|
|
24963
|
+
import asyncio
|
|
24964
|
+
body = await request.aread()
|
|
24965
|
+
sent = [(k.decode("latin-1"), v.decode("latin-1")) for k, v in request.headers.raw]
|
|
24966
|
+
status, reason, headers, payload = await _sbx_egress_request_async(
|
|
24967
|
+
request.method, str(request.url), sent, body,
|
|
24968
|
+
)
|
|
24969
|
+
keep = [(k, v) for k, v in headers
|
|
24970
|
+
if k.lower() not in ("content-length", "transfer-encoding", "content-encoding")]
|
|
24971
|
+
return httpx_module.Response(
|
|
24972
|
+
status, headers=keep, content=payload, request=request,
|
|
24973
|
+
extensions={"reason_phrase": (reason or "").encode()},
|
|
24974
|
+
)
|
|
24975
|
+
|
|
24976
|
+
async def aclose(self):
|
|
24977
|
+
pass
|
|
24978
|
+
|
|
24979
|
+
async def __aenter__(self):
|
|
24980
|
+
return self
|
|
24981
|
+
|
|
24982
|
+
async def __aexit__(self, *args):
|
|
24983
|
+
pass
|
|
24984
|
+
|
|
24985
|
+
return _Transport, _AsyncTransport
|
|
24986
|
+
|
|
24987
|
+
class _SbxHttpxHook:
|
|
24988
|
+
"""httpx and httpx2 build their default transports on httpcore, which
|
|
24989
|
+
opens sockets. Their default transport classes are replaced as the
|
|
24990
|
+
module loads, so \`httpx.get(...)\` and every client built from the
|
|
24991
|
+
defaults leave through the egress."""
|
|
24992
|
+
|
|
24993
|
+
_PACKAGES = {"httpx": "httpx", "httpx2": "httpx2"}
|
|
24994
|
+
|
|
24995
|
+
def find_spec(self, name, path=None, target=None):
|
|
24996
|
+
package = self._PACKAGES.get(name)
|
|
24997
|
+
if package is None or getattr(self, "_loading", None) == name:
|
|
24998
|
+
return None
|
|
24999
|
+
import importlib.machinery
|
|
25000
|
+
spec = importlib.machinery.PathFinder.find_spec(name, path)
|
|
25001
|
+
if spec is None or spec.loader is None:
|
|
25002
|
+
return spec
|
|
25003
|
+
load = spec.loader.exec_module
|
|
25004
|
+
|
|
25005
|
+
def exec_module(module):
|
|
25006
|
+
self._loading = name
|
|
25007
|
+
try:
|
|
25008
|
+
load(module)
|
|
25009
|
+
finally:
|
|
25010
|
+
self._loading = None
|
|
25011
|
+
transport, async_transport = _sbx_httpx_transports(module)
|
|
25012
|
+
module.HTTPTransport = transport
|
|
25013
|
+
module.AsyncHTTPTransport = async_transport
|
|
25014
|
+
defaults = sys.modules.get(f"{package}._transports.default")
|
|
25015
|
+
if defaults is not None:
|
|
25016
|
+
defaults.HTTPTransport = transport
|
|
25017
|
+
defaults.AsyncHTTPTransport = async_transport
|
|
25018
|
+
client = sys.modules.get(f"{package}._client")
|
|
25019
|
+
if client is not None:
|
|
25020
|
+
client.HTTPTransport = transport
|
|
25021
|
+
client.AsyncHTTPTransport = async_transport
|
|
25022
|
+
|
|
25023
|
+
spec.loader.exec_module = exec_module
|
|
25024
|
+
return spec
|
|
25025
|
+
|
|
25026
|
+
sys.meta_path.insert(0, _SbxHttpxHook())
|
|
25027
|
+
|
|
24036
25028
|
|
|
24037
25029
|
if os.environ.get("SBX_SERIAL_HOST_CALLS") == "1":
|
|
24038
25030
|
import asyncio
|
|
@@ -24061,10 +25053,127 @@ if os.environ.get("SBX_SERIAL_HOST_CALLS") == "1":
|
|
|
24061
25053
|
)
|
|
24062
25054
|
|
|
24063
25055
|
asyncio.to_thread = _to_thread
|
|
25056
|
+
|
|
25057
|
+
class _SbxAnyioThreadHook:
|
|
25058
|
+
"""anyio's thread offload, run inline.
|
|
25059
|
+
|
|
25060
|
+
anyio has its own worker threads, so it never reaches asyncio.to_thread
|
|
25061
|
+
and the refusal above -- it deadlocks instead, for the reason described
|
|
25062
|
+
there. Starlette sends every *synchronous* endpoint through
|
|
25063
|
+
anyio.to_thread.run_sync, so a plain def FastAPI route hung forever
|
|
25064
|
+
while an async def one answered.
|
|
25065
|
+
|
|
25066
|
+
Running the function on the loop thread keeps such an endpoint working.
|
|
25067
|
+
The cost is real and worth stating: a synchronous handler blocks the
|
|
25068
|
+
loop for its duration, so it does not overlap with other requests.
|
|
25069
|
+
"""
|
|
25070
|
+
|
|
25071
|
+
def find_spec(self, name, path=None, target=None):
|
|
25072
|
+
if name != "anyio.to_thread":
|
|
25073
|
+
return None
|
|
25074
|
+
import importlib.machinery
|
|
25075
|
+
spec = importlib.machinery.PathFinder.find_spec(name, path)
|
|
25076
|
+
if spec is None or spec.loader is None:
|
|
25077
|
+
return spec
|
|
25078
|
+
load = spec.loader.exec_module
|
|
25079
|
+
|
|
25080
|
+
def exec_module(module):
|
|
25081
|
+
load(module)
|
|
25082
|
+
|
|
25083
|
+
async def run_sync(func, *args, abandon_on_cancel=False,
|
|
25084
|
+
cancellable=None, limiter=None):
|
|
25085
|
+
return func(*args)
|
|
25086
|
+
|
|
25087
|
+
async def run_sync_in_worker_thread(func, *args, **kwargs):
|
|
25088
|
+
return func(*args)
|
|
25089
|
+
|
|
25090
|
+
module.run_sync = run_sync
|
|
25091
|
+
module.run_sync_in_worker_thread = run_sync_in_worker_thread
|
|
25092
|
+
|
|
25093
|
+
spec.loader.exec_module = exec_module
|
|
25094
|
+
return spec
|
|
25095
|
+
|
|
25096
|
+
sys.meta_path.insert(0, _SbxAnyioThreadHook())
|
|
25097
|
+
|
|
25098
|
+
class _SbxLoopResolverHook:
|
|
25099
|
+
"""asyncio resolves names on a worker thread, which cannot run here.
|
|
25100
|
+
|
|
25101
|
+
loop.getaddrinfo hands the call to a ThreadPoolExecutor and awaits
|
|
25102
|
+
it -- the deadlock described above -- so every asyncio client that
|
|
25103
|
+
connects by name hung: aiohttp never got past its first request. In
|
|
25104
|
+
this container resolution is a host call that answers immediately, so
|
|
25105
|
+
it is done on the loop thread instead of being handed away."""
|
|
25106
|
+
|
|
25107
|
+
def find_spec(self, name, path=None, target=None):
|
|
25108
|
+
if name != "asyncio.base_events":
|
|
25109
|
+
return None
|
|
25110
|
+
import importlib.machinery
|
|
25111
|
+
spec = importlib.machinery.PathFinder.find_spec(name, path)
|
|
25112
|
+
if spec is None or spec.loader is None:
|
|
25113
|
+
return spec
|
|
25114
|
+
load = spec.loader.exec_module
|
|
25115
|
+
|
|
25116
|
+
def exec_module(module):
|
|
25117
|
+
load(module)
|
|
25118
|
+
import socket as _socket
|
|
25119
|
+
|
|
25120
|
+
async def getaddrinfo(self, host, port, *, family=0, type=0, proto=0, flags=0):
|
|
25121
|
+
return _socket.getaddrinfo(host, port, family, type, proto, flags)
|
|
25122
|
+
|
|
25123
|
+
async def getnameinfo(self, sockaddr, flags=0):
|
|
25124
|
+
return _socket.getnameinfo(sockaddr, flags)
|
|
25125
|
+
|
|
25126
|
+
module.BaseEventLoop.getaddrinfo = getaddrinfo
|
|
25127
|
+
module.BaseEventLoop.getnameinfo = getnameinfo
|
|
25128
|
+
|
|
25129
|
+
spec.loader.exec_module = exec_module
|
|
25130
|
+
return spec
|
|
25131
|
+
|
|
25132
|
+
sys.meta_path.insert(0, _SbxLoopResolverHook())
|
|
25133
|
+
|
|
25134
|
+
def _sbx_patch_loop_resolver(module):
|
|
25135
|
+
"""Also patch the module if it is already imported.
|
|
25136
|
+
|
|
25137
|
+
This file imports asyncio itself, a few lines above, so by the time the
|
|
25138
|
+
hook is installed asyncio.base_events is usually already in
|
|
25139
|
+
sys.modules and the hook would never see it -- which is why aiohttp
|
|
25140
|
+
still hung after the hook existed."""
|
|
25141
|
+
import socket as _socket
|
|
25142
|
+
|
|
25143
|
+
async def getaddrinfo(self, host, port, *, family=0, type=0, proto=0, flags=0):
|
|
25144
|
+
return _socket.getaddrinfo(host, port, family, type, proto, flags)
|
|
25145
|
+
|
|
25146
|
+
async def getnameinfo(self, sockaddr, flags=0):
|
|
25147
|
+
return _socket.getnameinfo(sockaddr, flags)
|
|
25148
|
+
|
|
25149
|
+
module.BaseEventLoop.getaddrinfo = getaddrinfo
|
|
25150
|
+
module.BaseEventLoop.getnameinfo = getnameinfo
|
|
25151
|
+
|
|
25152
|
+
_loaded_base_events = sys.modules.get("asyncio.base_events")
|
|
25153
|
+
if _loaded_base_events is not None:
|
|
25154
|
+
_sbx_patch_loop_resolver(_loaded_base_events)
|
|
24064
25155
|
`;
|
|
25156
|
+
var CA_BUNDLE_PATH = "/etc/ssl/certs/ca-certificates.crt";
|
|
25157
|
+
async function ensureTrustStore(ctx) {
|
|
25158
|
+
try {
|
|
25159
|
+
if (ctx.vfs.stat(CA_BUNDLE_PATH, { cred: ctx.cred }).size > 0) return;
|
|
25160
|
+
} catch {
|
|
25161
|
+
}
|
|
25162
|
+
try {
|
|
25163
|
+
const tls = await nodeBuiltin("tls");
|
|
25164
|
+
const bundle = tls.rootCertificates.join("\n");
|
|
25165
|
+
if (!bundle) return;
|
|
25166
|
+
ctx.vfs.mkdir("/etc/ssl/certs", { cred: ctx.cred, recursive: true });
|
|
25167
|
+
ctx.vfs.writeFile(CA_BUNDLE_PATH, bundle, { privileged: true, mode: 420 });
|
|
25168
|
+
} catch {
|
|
25169
|
+
}
|
|
25170
|
+
}
|
|
24065
25171
|
function ensureProcessShims(ctx) {
|
|
24066
|
-
|
|
24067
|
-
|
|
25172
|
+
writeShim(ctx, `${SITE_PACKAGES}/sitecustomize.py`, SHIM_SOURCE);
|
|
25173
|
+
writeShim(ctx, `${SITE_PACKAGES}/_sbx_tls.py`, TLS_MODULE_SOURCE);
|
|
25174
|
+
}
|
|
25175
|
+
function writeShim(ctx, hook, body) {
|
|
25176
|
+
const source = `${SHIM_HEADER}${body}`;
|
|
24068
25177
|
try {
|
|
24069
25178
|
const existing = ctx.vfs.readText(hook, ctx.cred);
|
|
24070
25179
|
if (existing === source || !existing.startsWith(SHIM_HEADER)) return;
|
|
@@ -24111,6 +25220,7 @@ function containerMounts(ctx) {
|
|
|
24111
25220
|
|
|
24112
25221
|
// src/python/pip-command.ts
|
|
24113
25222
|
init_binary();
|
|
25223
|
+
var RESOLVE_DEADLINE_MS = 15 * 6e4;
|
|
24114
25224
|
async function requestRemoteBuild(service, requirement, line) {
|
|
24115
25225
|
line(`Asking ${new URL(service).host} to build ${requirement}`);
|
|
24116
25226
|
try {
|
|
@@ -24313,6 +25423,14 @@ environments. A package with no usable wheel is an error, never a silent skip.`,
|
|
|
24313
25423
|
vfs: ctx.vfs,
|
|
24314
25424
|
cred: ctx.cred,
|
|
24315
25425
|
requirements,
|
|
25426
|
+
/* The resolver's own default is three minutes, which is a fine bound
|
|
25427
|
+
* for a library but far too tight for a real requirements file: a
|
|
25428
|
+
* `langchain` plus `deepagents` graph is hundreds of distributions,
|
|
25429
|
+
* each needing a metadata fetch, and the install died mid-resolution
|
|
25430
|
+
* with "exceeded its 180s budget" while still making progress. `pip`
|
|
25431
|
+
* already has a 20-minute ceiling on the whole command; this leaves
|
|
25432
|
+
* room for the download phase inside it. */
|
|
25433
|
+
deadlineMs: RESOLVE_DEADLINE_MS,
|
|
24316
25434
|
pythonVersion: pythonBackend().manifest?.pythonVersion ?? "3.13.5",
|
|
24317
25435
|
progress: {
|
|
24318
25436
|
collecting: (name) => ctx.line(`Collecting ${name}`),
|
|
@@ -24465,7 +25583,7 @@ function mountContainerFs(FS, opts) {
|
|
|
24465
25583
|
const throwErrno = (code) => {
|
|
24466
25584
|
throw new FS.ErrnoError(emErrno(code));
|
|
24467
25585
|
};
|
|
24468
|
-
const
|
|
25586
|
+
const translate2 = (e) => {
|
|
24469
25587
|
if (isSysError(e)) return throwErrno(e.code);
|
|
24470
25588
|
throw new FS.ErrnoError(emErrno("EIO"));
|
|
24471
25589
|
};
|
|
@@ -24514,7 +25632,7 @@ function mountContainerFs(FS, opts) {
|
|
|
24514
25632
|
blocks: Math.ceil(st.size / 512)
|
|
24515
25633
|
};
|
|
24516
25634
|
} catch (e) {
|
|
24517
|
-
return
|
|
25635
|
+
return translate2(e);
|
|
24518
25636
|
}
|
|
24519
25637
|
},
|
|
24520
25638
|
setattr(node2, attr) {
|
|
@@ -24530,7 +25648,7 @@ function mountContainerFs(FS, opts) {
|
|
|
24530
25648
|
opts.onWrite?.(path);
|
|
24531
25649
|
}
|
|
24532
25650
|
} catch (e) {
|
|
24533
|
-
|
|
25651
|
+
translate2(e);
|
|
24534
25652
|
}
|
|
24535
25653
|
},
|
|
24536
25654
|
lookup(parent, name) {
|
|
@@ -24555,7 +25673,7 @@ function mountContainerFs(FS, opts) {
|
|
|
24555
25673
|
}
|
|
24556
25674
|
opts.onWrite?.(path);
|
|
24557
25675
|
} catch (e) {
|
|
24558
|
-
|
|
25676
|
+
translate2(e);
|
|
24559
25677
|
}
|
|
24560
25678
|
return backend.createNode(parent, name, mode, dev);
|
|
24561
25679
|
},
|
|
@@ -24566,7 +25684,7 @@ function mountContainerFs(FS, opts) {
|
|
|
24566
25684
|
vfs.rename(from, to, cred);
|
|
24567
25685
|
opts.onWrite?.(to);
|
|
24568
25686
|
} catch (e) {
|
|
24569
|
-
|
|
25687
|
+
translate2(e);
|
|
24570
25688
|
}
|
|
24571
25689
|
oldNode.name = newName;
|
|
24572
25690
|
oldNode.parent = newDir;
|
|
@@ -24577,7 +25695,7 @@ function mountContainerFs(FS, opts) {
|
|
|
24577
25695
|
vfs.unlink(path, cred);
|
|
24578
25696
|
opts.onWrite?.(path);
|
|
24579
25697
|
} catch (e) {
|
|
24580
|
-
|
|
25698
|
+
translate2(e);
|
|
24581
25699
|
}
|
|
24582
25700
|
},
|
|
24583
25701
|
rmdir(parent, name) {
|
|
@@ -24586,7 +25704,7 @@ function mountContainerFs(FS, opts) {
|
|
|
24586
25704
|
vfs.rmdir(path, cred);
|
|
24587
25705
|
opts.onWrite?.(path);
|
|
24588
25706
|
} catch (e) {
|
|
24589
|
-
|
|
25707
|
+
translate2(e);
|
|
24590
25708
|
}
|
|
24591
25709
|
},
|
|
24592
25710
|
readdir(node2) {
|
|
@@ -24594,7 +25712,7 @@ function mountContainerFs(FS, opts) {
|
|
|
24594
25712
|
try {
|
|
24595
25713
|
return [".", "..", ...vfs.readdir(path, cred)];
|
|
24596
25714
|
} catch (e) {
|
|
24597
|
-
return
|
|
25715
|
+
return translate2(e);
|
|
24598
25716
|
}
|
|
24599
25717
|
},
|
|
24600
25718
|
symlink(parent, newName, oldPath) {
|
|
@@ -24603,7 +25721,7 @@ function mountContainerFs(FS, opts) {
|
|
|
24603
25721
|
vfs.symlink(oldPath, path, cred);
|
|
24604
25722
|
opts.onWrite?.(path);
|
|
24605
25723
|
} catch (e) {
|
|
24606
|
-
|
|
25724
|
+
translate2(e);
|
|
24607
25725
|
}
|
|
24608
25726
|
},
|
|
24609
25727
|
readlink(node2) {
|
|
@@ -24611,7 +25729,7 @@ function mountContainerFs(FS, opts) {
|
|
|
24611
25729
|
try {
|
|
24612
25730
|
return vfs.readlink(path, cred);
|
|
24613
25731
|
} catch (e) {
|
|
24614
|
-
return
|
|
25732
|
+
return translate2(e);
|
|
24615
25733
|
}
|
|
24616
25734
|
}
|
|
24617
25735
|
};
|
|
@@ -24623,7 +25741,7 @@ function mountContainerFs(FS, opts) {
|
|
|
24623
25741
|
stream.sbxBuffer = vfs.lexists(path) ? vfs.readFile(path, cred) : new Uint8Array(0);
|
|
24624
25742
|
stream.sbxDirty = false;
|
|
24625
25743
|
} catch (e) {
|
|
24626
|
-
|
|
25744
|
+
translate2(e);
|
|
24627
25745
|
}
|
|
24628
25746
|
},
|
|
24629
25747
|
close(stream) {
|
|
@@ -24633,7 +25751,7 @@ function mountContainerFs(FS, opts) {
|
|
|
24633
25751
|
vfs.writeFile(path, stream.sbxBuffer, { cred });
|
|
24634
25752
|
opts.onWrite?.(path);
|
|
24635
25753
|
} catch (e) {
|
|
24636
|
-
|
|
25754
|
+
translate2(e);
|
|
24637
25755
|
}
|
|
24638
25756
|
stream.sbxDirty = false;
|
|
24639
25757
|
},
|
|
@@ -24659,7 +25777,7 @@ function mountContainerFs(FS, opts) {
|
|
|
24659
25777
|
opts.onWrite?.(path);
|
|
24660
25778
|
stream.sbxDirty = false;
|
|
24661
25779
|
} catch (e) {
|
|
24662
|
-
|
|
25780
|
+
translate2(e);
|
|
24663
25781
|
}
|
|
24664
25782
|
return length;
|
|
24665
25783
|
},
|
|
@@ -34340,6 +35458,212 @@ ${BUNDLER_HINT}`),
|
|
|
34340
35458
|
|
|
34341
35459
|
// src/worker/local-runtime-pod.ts
|
|
34342
35460
|
init_path();
|
|
35461
|
+
|
|
35462
|
+
// src/net/outbound-tcp.ts
|
|
35463
|
+
var FIRST_SYNTHETIC = 2887581696;
|
|
35464
|
+
var SYNTHETIC_COUNT = 65534;
|
|
35465
|
+
function toDotted(value) {
|
|
35466
|
+
return [value >>> 24, value >>> 16 & 255, value >>> 8 & 255, value & 255].join(".");
|
|
35467
|
+
}
|
|
35468
|
+
var OutboundTcp = class {
|
|
35469
|
+
constructor(policy, dialer) {
|
|
35470
|
+
this.policy = policy;
|
|
35471
|
+
this.dialer = dialer;
|
|
35472
|
+
}
|
|
35473
|
+
policy;
|
|
35474
|
+
dialer;
|
|
35475
|
+
byName = /* @__PURE__ */ new Map();
|
|
35476
|
+
byAddress = /* @__PURE__ */ new Map();
|
|
35477
|
+
next = 0;
|
|
35478
|
+
/** Whether this address was handed out by `resolve`. */
|
|
35479
|
+
knows(address) {
|
|
35480
|
+
return this.byAddress.has(address);
|
|
35481
|
+
}
|
|
35482
|
+
hostnameFor(address) {
|
|
35483
|
+
return this.byAddress.get(address);
|
|
35484
|
+
}
|
|
35485
|
+
/**
|
|
35486
|
+
* The address for `hostname`, allocating one on first use.
|
|
35487
|
+
*
|
|
35488
|
+
* Refusal happens here as well as at connect, because a name that cannot be
|
|
35489
|
+
* reached should fail as a resolution failure -- which is what every client
|
|
35490
|
+
* reports as "unknown host" rather than as a mid-connection error.
|
|
35491
|
+
*/
|
|
35492
|
+
resolve(hostname) {
|
|
35493
|
+
const name = hostname.trim().toLowerCase();
|
|
35494
|
+
if (!name) throw new PosixError(Errno.EINVAL, "empty hostname");
|
|
35495
|
+
if (/^\d{1,3}(\.\d{1,3}){3}$/.test(name)) return name;
|
|
35496
|
+
if (isLoopbackHostname(name)) return "127.0.0.1";
|
|
35497
|
+
const known = this.byName.get(name);
|
|
35498
|
+
if (known) return known;
|
|
35499
|
+
if (!this.allowed(name)) {
|
|
35500
|
+
throw new PosixError(
|
|
35501
|
+
Errno.ENETUNREACH,
|
|
35502
|
+
`cannot resolve ${hostname}: outbound network access is not allowed for this container`
|
|
35503
|
+
);
|
|
35504
|
+
}
|
|
35505
|
+
if (this.next >= SYNTHETIC_COUNT) {
|
|
35506
|
+
throw new PosixError(Errno.ENOSPC, "no addresses left for resolved names");
|
|
35507
|
+
}
|
|
35508
|
+
const address = toDotted(FIRST_SYNTHETIC + this.next + 1);
|
|
35509
|
+
this.next += 1;
|
|
35510
|
+
this.byName.set(name, address);
|
|
35511
|
+
this.byAddress.set(address, name);
|
|
35512
|
+
return address;
|
|
35513
|
+
}
|
|
35514
|
+
/** Open a connection to a resolved address, or to a literal one. */
|
|
35515
|
+
async connect(address, port, local) {
|
|
35516
|
+
const hostname = this.byAddress.get(address) ?? address;
|
|
35517
|
+
if (!this.allowed(hostname)) {
|
|
35518
|
+
throw new PosixError(
|
|
35519
|
+
Errno.ENETUNREACH,
|
|
35520
|
+
`cannot reach ${hostname}:${port}: outbound network access is not allowed for this container`
|
|
35521
|
+
);
|
|
35522
|
+
}
|
|
35523
|
+
if (!this.dialer) {
|
|
35524
|
+
throw new PosixError(
|
|
35525
|
+
Errno.ENETUNREACH,
|
|
35526
|
+
`cannot reach ${hostname}:${port}: this host cannot open sockets (a browser cannot), so outbound traffic has to leave over HTTP through the container's egress`
|
|
35527
|
+
);
|
|
35528
|
+
}
|
|
35529
|
+
const host2 = await this.dialer(hostname, port);
|
|
35530
|
+
const guest = new VirtualTcpConnection(
|
|
35531
|
+
{ address: local.address, port: host2.localPort || local.port },
|
|
35532
|
+
{ address, port }
|
|
35533
|
+
);
|
|
35534
|
+
const bridge = new VirtualTcpConnection({ address, port }, { address: local.address, port: local.port });
|
|
35535
|
+
guest.pairWith(bridge);
|
|
35536
|
+
bridge.pairWith(guest);
|
|
35537
|
+
host2.onData((bytes2) => {
|
|
35538
|
+
try {
|
|
35539
|
+
bridge.write(bytes2);
|
|
35540
|
+
} catch {
|
|
35541
|
+
host2.close();
|
|
35542
|
+
}
|
|
35543
|
+
});
|
|
35544
|
+
host2.onClose(() => {
|
|
35545
|
+
bridge.shutdown(false, true);
|
|
35546
|
+
bridge.close();
|
|
35547
|
+
});
|
|
35548
|
+
void (async () => {
|
|
35549
|
+
for (; ; ) {
|
|
35550
|
+
if (bridge.readable) {
|
|
35551
|
+
let chunk;
|
|
35552
|
+
try {
|
|
35553
|
+
chunk = bridge.read(64 * 1024);
|
|
35554
|
+
} catch {
|
|
35555
|
+
break;
|
|
35556
|
+
}
|
|
35557
|
+
if (chunk.length === 0) {
|
|
35558
|
+
host2.end();
|
|
35559
|
+
break;
|
|
35560
|
+
}
|
|
35561
|
+
host2.write(chunk);
|
|
35562
|
+
continue;
|
|
35563
|
+
}
|
|
35564
|
+
await bridge.waitForChange();
|
|
35565
|
+
}
|
|
35566
|
+
})();
|
|
35567
|
+
return { connection: guest, host: host2 };
|
|
35568
|
+
}
|
|
35569
|
+
allowed(hostname) {
|
|
35570
|
+
const policy = this.policy();
|
|
35571
|
+
if (isLoopbackHostname(hostname)) return true;
|
|
35572
|
+
if (!policy.allowOutbound) return false;
|
|
35573
|
+
const hosts = policy.allowedHosts;
|
|
35574
|
+
if (!hosts) return true;
|
|
35575
|
+
return hosts.some((allowed) => hostname === allowed || hostname.endsWith(`.${allowed}`));
|
|
35576
|
+
}
|
|
35577
|
+
};
|
|
35578
|
+
|
|
35579
|
+
// src/net/node-tcp-dialer.ts
|
|
35580
|
+
init_binary();
|
|
35581
|
+
function canDialTcp() {
|
|
35582
|
+
return typeof process !== "undefined" && Boolean(process.versions?.node);
|
|
35583
|
+
}
|
|
35584
|
+
function createNodeTcpDialer() {
|
|
35585
|
+
return async (host2, port) => {
|
|
35586
|
+
const net = await nodeBuiltin("net");
|
|
35587
|
+
return await new Promise((resolve3, reject) => {
|
|
35588
|
+
const socket = net.connect({ host: host2, port });
|
|
35589
|
+
const pending = [];
|
|
35590
|
+
let onData = null;
|
|
35591
|
+
let onClose = null;
|
|
35592
|
+
let closed = false;
|
|
35593
|
+
socket.on("data", (chunk) => {
|
|
35594
|
+
const bytes2 = new Uint8Array(chunk);
|
|
35595
|
+
if (onData) onData(bytes2);
|
|
35596
|
+
else pending.push(bytes2);
|
|
35597
|
+
});
|
|
35598
|
+
const finish = () => {
|
|
35599
|
+
if (closed) return;
|
|
35600
|
+
closed = true;
|
|
35601
|
+
onClose?.();
|
|
35602
|
+
};
|
|
35603
|
+
socket.on("close", finish);
|
|
35604
|
+
socket.on("end", finish);
|
|
35605
|
+
socket.on("error", (error) => {
|
|
35606
|
+
if (!closed) {
|
|
35607
|
+
closed = true;
|
|
35608
|
+
onClose?.();
|
|
35609
|
+
}
|
|
35610
|
+
reject(translate(error, host2, port));
|
|
35611
|
+
});
|
|
35612
|
+
socket.on("connect", () => {
|
|
35613
|
+
socket.setNoDelay(true);
|
|
35614
|
+
resolve3({
|
|
35615
|
+
write(bytes2) {
|
|
35616
|
+
socket.write(bytes2);
|
|
35617
|
+
},
|
|
35618
|
+
end() {
|
|
35619
|
+
socket.end();
|
|
35620
|
+
},
|
|
35621
|
+
close() {
|
|
35622
|
+
socket.destroy();
|
|
35623
|
+
},
|
|
35624
|
+
onData(handler) {
|
|
35625
|
+
onData = handler;
|
|
35626
|
+
for (const chunk of pending.splice(0)) handler(chunk);
|
|
35627
|
+
},
|
|
35628
|
+
onClose(handler) {
|
|
35629
|
+
onClose = handler;
|
|
35630
|
+
if (closed) handler();
|
|
35631
|
+
},
|
|
35632
|
+
get remoteAddress() {
|
|
35633
|
+
return socket.remoteAddress ?? host2;
|
|
35634
|
+
},
|
|
35635
|
+
get remotePort() {
|
|
35636
|
+
return socket.remotePort ?? port;
|
|
35637
|
+
},
|
|
35638
|
+
get localPort() {
|
|
35639
|
+
return socket.localPort ?? 0;
|
|
35640
|
+
}
|
|
35641
|
+
});
|
|
35642
|
+
});
|
|
35643
|
+
});
|
|
35644
|
+
};
|
|
35645
|
+
}
|
|
35646
|
+
function translate(error, host2, port) {
|
|
35647
|
+
const where = `${host2}:${port}`;
|
|
35648
|
+
switch (error.code) {
|
|
35649
|
+
case "ECONNREFUSED":
|
|
35650
|
+
return new PosixError(Errno.ECONNREFUSED, `connection refused by ${where}`);
|
|
35651
|
+
case "ENOTFOUND":
|
|
35652
|
+
case "EAI_AGAIN":
|
|
35653
|
+
return new PosixError(Errno.ENETUNREACH, `cannot resolve ${host2}`);
|
|
35654
|
+
case "ETIMEDOUT":
|
|
35655
|
+
return new PosixError(Errno.ETIMEDOUT, `connection to ${where} timed out`);
|
|
35656
|
+
case "ECONNRESET":
|
|
35657
|
+
return new PosixError(Errno.ECONNRESET, `connection to ${where} was reset`);
|
|
35658
|
+
case "EHOSTUNREACH":
|
|
35659
|
+
case "ENETUNREACH":
|
|
35660
|
+
return new PosixError(Errno.ENETUNREACH, `cannot reach ${where}`);
|
|
35661
|
+
default:
|
|
35662
|
+
return new PosixError(Errno.ECONNREFUSED, `connection to ${where} failed: ${error.message}`);
|
|
35663
|
+
}
|
|
35664
|
+
}
|
|
35665
|
+
|
|
35666
|
+
// src/worker/local-runtime-pod.ts
|
|
34343
35667
|
var WASM_ALIASES = {
|
|
34344
35668
|
esbuild: "esbuild-wasm",
|
|
34345
35669
|
rollup: "@rollup/wasm-node"
|
|
@@ -34695,6 +36019,32 @@ var LocalRuntimePod = class _LocalRuntimePod {
|
|
|
34695
36019
|
constructor(options) {
|
|
34696
36020
|
ensureProcessGlobal();
|
|
34697
36021
|
this.sockets = new VirtualTcpNetwork((port) => this.router.activePortsIncludes(port));
|
|
36022
|
+
this.sockets.loopbackHttp = (port, connection) => {
|
|
36023
|
+
if (!this.router.activePortsIncludes(port)) return false;
|
|
36024
|
+
void serveHttpOverConnection(connection, async (request) => {
|
|
36025
|
+
const url = new URL(request.target, `http://127.0.0.1:${port}`);
|
|
36026
|
+
const answer = await this.router.request(port, {
|
|
36027
|
+
method: request.method,
|
|
36028
|
+
path: `${url.pathname}${url.search}`,
|
|
36029
|
+
headers: request.headers,
|
|
36030
|
+
...request.body.length > 0 ? { body: request.body } : {}
|
|
36031
|
+
});
|
|
36032
|
+
const payload = answer.body ?? new Uint8Array();
|
|
36033
|
+
return {
|
|
36034
|
+
status: Number(answer.statusCode ?? 200),
|
|
36035
|
+
statusText: String(answer.statusMessage ?? ""),
|
|
36036
|
+
headers: answer.headers ?? {},
|
|
36037
|
+
body: typeof payload === "string" ? new TextEncoder().encode(payload) : payload instanceof Uint8Array ? payload : new Uint8Array(payload)
|
|
36038
|
+
};
|
|
36039
|
+
});
|
|
36040
|
+
return true;
|
|
36041
|
+
};
|
|
36042
|
+
if (canDialTcp()) {
|
|
36043
|
+
this.sockets.outbound = new OutboundTcp(
|
|
36044
|
+
() => this.networkPolicy ?? { allowOutbound: false, allowedHosts: null },
|
|
36045
|
+
createNodeTcpDialer()
|
|
36046
|
+
);
|
|
36047
|
+
}
|
|
34698
36048
|
this.router = new VirtualHttpRouter(this.sockets);
|
|
34699
36049
|
this.workdir = options.workdir ?? "/";
|
|
34700
36050
|
this.env = { ...options.env };
|