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.cjs
CHANGED
|
@@ -19821,6 +19821,7 @@ var Op = {
|
|
|
19821
19821
|
setsockopt: 1802,
|
|
19822
19822
|
getsockopt: 1803,
|
|
19823
19823
|
socket_close: 1804,
|
|
19824
|
+
resolve: 1805,
|
|
19824
19825
|
spawn: 2048,
|
|
19825
19826
|
waitpid: 2049,
|
|
19826
19827
|
kill: 2050
|
|
@@ -20057,6 +20058,28 @@ var wheels_default = {
|
|
|
20057
20058
|
patches: []
|
|
20058
20059
|
}
|
|
20059
20060
|
},
|
|
20061
|
+
{
|
|
20062
|
+
name: "asyncpg",
|
|
20063
|
+
version: "0.31.0",
|
|
20064
|
+
filename: "asyncpg-0.31.0-cp313-cp313-emscripten_5_0_6_wasm32.whl",
|
|
20065
|
+
sha256: "3eaf965cbd78d2314e8f12b9384e5642725323063aaff2708a8a4b3c01ef10ba",
|
|
20066
|
+
abiId: "sbxabi1-c2637d04695ad927",
|
|
20067
|
+
requires: [
|
|
20068
|
+
'async_timeout>=4.0.3; python_version < "3.11.0"',
|
|
20069
|
+
'gssapi; platform_system != "Windows" and extra == "gssauth"',
|
|
20070
|
+
'sspilib; platform_system == "Windows" and extra == "gssauth"'
|
|
20071
|
+
],
|
|
20072
|
+
provenance: {
|
|
20073
|
+
sourceTreeDigest: "a9ab0177da5c0c2d0ba635d1f5568687efbf8aefc08cd51cd086277184eba76f",
|
|
20074
|
+
recipeSha256: "fb9cf1a9fd35d8d63ac69dcf6a1671ceeebcb23f381a100b412058d75ff2eedc",
|
|
20075
|
+
buildTools: [
|
|
20076
|
+
"setuptools==84.0.0",
|
|
20077
|
+
"Cython==3.1.4"
|
|
20078
|
+
],
|
|
20079
|
+
nativeDependencies: [],
|
|
20080
|
+
patches: []
|
|
20081
|
+
}
|
|
20082
|
+
},
|
|
20060
20083
|
{
|
|
20061
20084
|
name: "bcrypt",
|
|
20062
20085
|
version: "5.0.0",
|
|
@@ -20074,6 +20097,26 @@ var wheels_default = {
|
|
|
20074
20097
|
patches: []
|
|
20075
20098
|
}
|
|
20076
20099
|
},
|
|
20100
|
+
{
|
|
20101
|
+
name: "brotli",
|
|
20102
|
+
version: "1.2.0",
|
|
20103
|
+
filename: "brotli-1.2.0-cp313-cp313-emscripten_5_0_6_wasm32.whl",
|
|
20104
|
+
sha256: "d8b06cc04770a048b41956c9cc767e7332c49f0229e69744f4b1c2892b888b27",
|
|
20105
|
+
abiId: "sbxabi1-c2637d04695ad927",
|
|
20106
|
+
requires: [],
|
|
20107
|
+
provenance: {
|
|
20108
|
+
sourceTreeDigest: "7b563fa2e7f20edac204cc80004cae35c844b36a83758e5957184383332d1209",
|
|
20109
|
+
recipeSha256: "1326c898e999feba536ff15f080fed9ac4b9cd14e0dbfee39a950decf19607ef",
|
|
20110
|
+
buildTools: [
|
|
20111
|
+
"setuptools==84.0.0",
|
|
20112
|
+
"pkgconfig==1.6.0"
|
|
20113
|
+
],
|
|
20114
|
+
nativeDependencies: [],
|
|
20115
|
+
patches: [
|
|
20116
|
+
"0001-meth-noargs-signatures.patch"
|
|
20117
|
+
]
|
|
20118
|
+
}
|
|
20119
|
+
},
|
|
20077
20120
|
{
|
|
20078
20121
|
name: "cffi",
|
|
20079
20122
|
version: "2.1.1",
|
|
@@ -20099,14 +20142,14 @@ var wheels_default = {
|
|
|
20099
20142
|
name: "cryptography",
|
|
20100
20143
|
version: "50.0.1",
|
|
20101
20144
|
filename: "cryptography-50.0.1-cp313-cp313-emscripten_5_0_6_wasm32.whl",
|
|
20102
|
-
sha256: "
|
|
20145
|
+
sha256: "10bb6568bf4368a593ea3a990ea870e57d4eeb1d3278af89bf24a8713473238e",
|
|
20103
20146
|
abiId: "sbxabi1-c2637d04695ad927",
|
|
20104
20147
|
requires: [
|
|
20105
20148
|
'cffi>=2.0.0; platform_python_implementation != "PyPy"'
|
|
20106
20149
|
],
|
|
20107
20150
|
provenance: {
|
|
20108
20151
|
sourceTreeDigest: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
20109
|
-
recipeSha256: "
|
|
20152
|
+
recipeSha256: "082169d1d317ba001e37ccfb7a40a0dafd8d3d34e42034da8590f0207ae73ea5",
|
|
20110
20153
|
buildTools: [
|
|
20111
20154
|
"setuptools==84.0.0",
|
|
20112
20155
|
"cffi==2.1.1"
|
|
@@ -20132,6 +20175,25 @@ var wheels_default = {
|
|
|
20132
20175
|
patches: []
|
|
20133
20176
|
}
|
|
20134
20177
|
},
|
|
20178
|
+
{
|
|
20179
|
+
name: "google-crc32c",
|
|
20180
|
+
version: "1.8.0",
|
|
20181
|
+
filename: "google_crc32c-1.8.0-cp313-cp313-emscripten_5_0_6_wasm32.whl",
|
|
20182
|
+
sha256: "e64c21841598ea21121d7b7bb203e124d58c035db49ffce03631c55216c942cd",
|
|
20183
|
+
abiId: "sbxabi1-c2637d04695ad927",
|
|
20184
|
+
requires: [],
|
|
20185
|
+
provenance: {
|
|
20186
|
+
sourceTreeDigest: "fa277032a78adfccf6adcda32cadc4f6958a324979040c231e01f0963821cb75",
|
|
20187
|
+
recipeSha256: "843dea57727061bdfc7bcd27ac380d16a767effa538cbab6257e106939689f43",
|
|
20188
|
+
buildTools: [
|
|
20189
|
+
"setuptools==84.0.0"
|
|
20190
|
+
],
|
|
20191
|
+
nativeDependencies: [
|
|
20192
|
+
"crc32c==1.1.2"
|
|
20193
|
+
],
|
|
20194
|
+
patches: []
|
|
20195
|
+
}
|
|
20196
|
+
},
|
|
20135
20197
|
{
|
|
20136
20198
|
name: "httptools",
|
|
20137
20199
|
version: "0.8.0",
|
|
@@ -20168,6 +20230,58 @@ var wheels_default = {
|
|
|
20168
20230
|
patches: []
|
|
20169
20231
|
}
|
|
20170
20232
|
},
|
|
20233
|
+
{
|
|
20234
|
+
name: "lxml",
|
|
20235
|
+
version: "6.1.3",
|
|
20236
|
+
filename: "lxml-6.1.3-cp313-cp313-emscripten_5_0_6_wasm32.whl",
|
|
20237
|
+
sha256: "dc76d45314b806934bdaf35f9e4ae61e86e741e07bac53fae7e94f9d3dab9d4e",
|
|
20238
|
+
abiId: "sbxabi1-c2637d04695ad927",
|
|
20239
|
+
requires: [
|
|
20240
|
+
'cssselect>=0.7; extra == "cssselect"',
|
|
20241
|
+
'html5lib; extra == "html5"',
|
|
20242
|
+
'BeautifulSoup4; extra == "htmlsoup"',
|
|
20243
|
+
'lxml_html_clean; extra == "html-clean"'
|
|
20244
|
+
],
|
|
20245
|
+
provenance: {
|
|
20246
|
+
sourceTreeDigest: "aedea680c1fe58ae9a124d81ea28f2316765e444e3809691854ddbf173c22bf5",
|
|
20247
|
+
recipeSha256: "aa62da97bb13eb5fa0a047f7c61ed93554f60142be9fcae64e3053e2a5673e9f",
|
|
20248
|
+
buildTools: [
|
|
20249
|
+
"setuptools==84.0.0",
|
|
20250
|
+
"Cython==3.1.4"
|
|
20251
|
+
],
|
|
20252
|
+
nativeDependencies: [
|
|
20253
|
+
"xml2==2.13.8",
|
|
20254
|
+
"xslt==1.1.43"
|
|
20255
|
+
],
|
|
20256
|
+
patches: []
|
|
20257
|
+
}
|
|
20258
|
+
},
|
|
20259
|
+
{
|
|
20260
|
+
name: "lz4",
|
|
20261
|
+
version: "4.4.5",
|
|
20262
|
+
filename: "lz4-4.4.5-cp313-cp313-emscripten_5_0_6_wasm32.whl",
|
|
20263
|
+
sha256: "f94c0ef7d84c9bf700cc86f69a28382699262e442e29d9763736afdb61141ffe",
|
|
20264
|
+
abiId: "sbxabi1-c2637d04695ad927",
|
|
20265
|
+
requires: [
|
|
20266
|
+
'pytest!=3.3.0; extra == "tests"',
|
|
20267
|
+
'psutil; extra == "tests"',
|
|
20268
|
+
'pytest-cov; extra == "tests"',
|
|
20269
|
+
'sphinx>=1.6.0; extra == "docs"',
|
|
20270
|
+
'sphinx_bootstrap_theme; extra == "docs"',
|
|
20271
|
+
'flake8; extra == "flake8"'
|
|
20272
|
+
],
|
|
20273
|
+
provenance: {
|
|
20274
|
+
sourceTreeDigest: "5f7bda9bfb026d0a30bb5e3383a4b25ae6b7f44fd6dc74950653d57f84c9d88e",
|
|
20275
|
+
recipeSha256: "80250de0ae159cd9f977bdfad059de0d192f28555225b084b42b5a180f684e0c",
|
|
20276
|
+
buildTools: [
|
|
20277
|
+
"setuptools==84.0.0",
|
|
20278
|
+
"pkgconfig==1.6.0",
|
|
20279
|
+
"setuptools-scm==10.2.3"
|
|
20280
|
+
],
|
|
20281
|
+
nativeDependencies: [],
|
|
20282
|
+
patches: []
|
|
20283
|
+
}
|
|
20284
|
+
},
|
|
20171
20285
|
{
|
|
20172
20286
|
name: "msgpack",
|
|
20173
20287
|
version: "1.1.0",
|
|
@@ -20228,16 +20342,172 @@ var wheels_default = {
|
|
|
20228
20342
|
name: "ormsgpack",
|
|
20229
20343
|
version: "1.12.2",
|
|
20230
20344
|
filename: "ormsgpack-1.12.2-cp313-cp313-emscripten_5_0_6_wasm32.whl",
|
|
20231
|
-
sha256: "
|
|
20345
|
+
sha256: "7fa4463a69aad2fe057c111642cce71c8f72af8465c22769d839415dbc15ac79",
|
|
20232
20346
|
abiId: "sbxabi1-c2637d04695ad927",
|
|
20233
20347
|
requires: [],
|
|
20234
20348
|
provenance: {
|
|
20235
|
-
sourceTreeDigest: "
|
|
20236
|
-
recipeSha256: "
|
|
20349
|
+
sourceTreeDigest: "eff3a7be3bc6de2a15e479c0e3e94905c35203fd83369124073f2df5cce46409",
|
|
20350
|
+
recipeSha256: "bd398762801fce51d35b33439f5e57037e1d4d274f25bd795683e2ad17d34b62",
|
|
20237
20351
|
buildTools: [
|
|
20238
20352
|
"setuptools==84.0.0"
|
|
20239
20353
|
],
|
|
20240
20354
|
nativeDependencies: [],
|
|
20355
|
+
patches: [
|
|
20356
|
+
"0001-pylong-asbytearray-313-signature.patch"
|
|
20357
|
+
]
|
|
20358
|
+
}
|
|
20359
|
+
},
|
|
20360
|
+
{
|
|
20361
|
+
name: "pandas",
|
|
20362
|
+
version: "3.0.5",
|
|
20363
|
+
filename: "pandas-3.0.5-cp313-cp313-emscripten_5_0_6_wasm32.whl",
|
|
20364
|
+
sha256: "ae36ad9b403b6fc28cb2bb79f54a38e1897ab43635c596fff2bce87f8148ac48",
|
|
20365
|
+
abiId: "sbxabi1-c2637d04695ad927",
|
|
20366
|
+
requires: [
|
|
20367
|
+
'numpy>=1.26.0; python_version < "3.14"',
|
|
20368
|
+
'numpy>=2.3.3; python_version >= "3.14"',
|
|
20369
|
+
"python-dateutil>=2.8.2",
|
|
20370
|
+
'tzdata; sys_platform == "win32"',
|
|
20371
|
+
'tzdata; sys_platform == "emscripten"',
|
|
20372
|
+
'hypothesis>=6.116.0; extra == "test"',
|
|
20373
|
+
'pytest<9.1,>=8.3.4; extra == "test"',
|
|
20374
|
+
'pytest-xdist>=3.6.1; extra == "test"',
|
|
20375
|
+
'pyarrow>=13.0.0; extra == "pyarrow"',
|
|
20376
|
+
'bottleneck>=1.4.2; extra == "performance"',
|
|
20377
|
+
'numba>=0.60.0; extra == "performance"',
|
|
20378
|
+
'numexpr>=2.10.2; extra == "performance"',
|
|
20379
|
+
'scipy>=1.14.1; extra == "computation"',
|
|
20380
|
+
'xarray>=2024.10.0; extra == "computation"',
|
|
20381
|
+
'fsspec>=2024.10.0; extra == "fss"',
|
|
20382
|
+
's3fs>=2024.10.0; extra == "aws"',
|
|
20383
|
+
'gcsfs>=2024.10.0; extra == "gcp"',
|
|
20384
|
+
'odfpy>=1.4.1; extra == "excel"',
|
|
20385
|
+
'openpyxl>=3.1.5; extra == "excel"',
|
|
20386
|
+
'python-calamine>=0.3.0; extra == "excel"',
|
|
20387
|
+
'pyxlsb>=1.0.10; extra == "excel"',
|
|
20388
|
+
'xlrd>=2.0.1; extra == "excel"',
|
|
20389
|
+
'xlsxwriter>=3.2.0; extra == "excel"',
|
|
20390
|
+
'pyarrow>=13.0.0; extra == "parquet"',
|
|
20391
|
+
'pyarrow>=13.0.0; extra == "feather"',
|
|
20392
|
+
'pyiceberg>=0.8.1; extra == "iceberg"',
|
|
20393
|
+
'tables>=3.10.1; extra == "hdf5"',
|
|
20394
|
+
'pyreadstat>=1.2.8; extra == "spss"',
|
|
20395
|
+
'SQLAlchemy>=2.0.36; extra == "postgresql"',
|
|
20396
|
+
'psycopg2>=2.9.10; extra == "postgresql"',
|
|
20397
|
+
'adbc-driver-postgresql>=1.2.0; extra == "postgresql"',
|
|
20398
|
+
'SQLAlchemy>=2.0.36; extra == "mysql"',
|
|
20399
|
+
'pymysql>=1.1.1; extra == "mysql"',
|
|
20400
|
+
'SQLAlchemy>=2.0.36; extra == "sql-other"',
|
|
20401
|
+
'adbc-driver-postgresql>=1.2.0; extra == "sql-other"',
|
|
20402
|
+
'adbc-driver-sqlite>=1.2.0; extra == "sql-other"',
|
|
20403
|
+
'beautifulsoup4>=4.12.3; extra == "html"',
|
|
20404
|
+
'html5lib>=1.1; extra == "html"',
|
|
20405
|
+
'lxml>=5.3.0; extra == "html"',
|
|
20406
|
+
'lxml>=5.3.0; extra == "xml"',
|
|
20407
|
+
'matplotlib>=3.9.3; extra == "plot"',
|
|
20408
|
+
'jinja2>=3.1.5; extra == "output-formatting"',
|
|
20409
|
+
'tabulate>=0.9.0; extra == "output-formatting"',
|
|
20410
|
+
'PyQt5>=5.15.9; extra == "clipboard"',
|
|
20411
|
+
'qtpy>=2.4.2; extra == "clipboard"',
|
|
20412
|
+
'zstandard>=0.23.0; extra == "compression"',
|
|
20413
|
+
'pytz>=2020.1; extra == "timezone"',
|
|
20414
|
+
'adbc-driver-postgresql>=1.2.0; extra == "all"',
|
|
20415
|
+
'adbc-driver-sqlite>=1.2.0; extra == "all"',
|
|
20416
|
+
'beautifulsoup4>=4.12.3; extra == "all"',
|
|
20417
|
+
'bottleneck>=1.4.2; extra == "all"',
|
|
20418
|
+
'fastparquet>=2024.11.0; extra == "all"',
|
|
20419
|
+
'fsspec>=2024.10.0; extra == "all"',
|
|
20420
|
+
'gcsfs>=2024.10.0; extra == "all"',
|
|
20421
|
+
'html5lib>=1.1; extra == "all"',
|
|
20422
|
+
'hypothesis>=6.116.0; extra == "all"',
|
|
20423
|
+
'jinja2>=3.1.5; extra == "all"',
|
|
20424
|
+
'lxml>=5.3.0; extra == "all"',
|
|
20425
|
+
'matplotlib>=3.9.3; extra == "all"',
|
|
20426
|
+
'numba>=0.60.0; extra == "all"',
|
|
20427
|
+
'numexpr>=2.10.2; extra == "all"',
|
|
20428
|
+
'odfpy>=1.4.1; extra == "all"',
|
|
20429
|
+
'openpyxl>=3.1.5; extra == "all"',
|
|
20430
|
+
'psycopg2>=2.9.10; extra == "all"',
|
|
20431
|
+
'pyarrow>=13.0.0; extra == "all"',
|
|
20432
|
+
'pyiceberg>=0.8.1; extra == "all"',
|
|
20433
|
+
'pymysql>=1.1.1; extra == "all"',
|
|
20434
|
+
'PyQt5>=5.15.9; extra == "all"',
|
|
20435
|
+
'pyreadstat>=1.2.8; extra == "all"',
|
|
20436
|
+
'pytest>=8.3.4; extra == "all"',
|
|
20437
|
+
'pytest-xdist>=3.6.1; extra == "all"',
|
|
20438
|
+
'python-calamine>=0.3.0; extra == "all"',
|
|
20439
|
+
'pytz>=2020.1; extra == "all"',
|
|
20440
|
+
'pyxlsb>=1.0.10; extra == "all"',
|
|
20441
|
+
'qtpy>=2.4.2; extra == "all"',
|
|
20442
|
+
'scipy>=1.14.1; extra == "all"',
|
|
20443
|
+
's3fs>=2024.10.0; extra == "all"',
|
|
20444
|
+
'SQLAlchemy>=2.0.36; extra == "all"',
|
|
20445
|
+
'tables>=3.10.1; extra == "all"',
|
|
20446
|
+
'tabulate>=0.9.0; extra == "all"',
|
|
20447
|
+
'xarray>=2024.10.0; extra == "all"',
|
|
20448
|
+
'xlrd>=2.0.1; extra == "all"',
|
|
20449
|
+
'xlsxwriter>=3.2.0; extra == "all"',
|
|
20450
|
+
'zstandard>=0.23.0; extra == "all"'
|
|
20451
|
+
],
|
|
20452
|
+
provenance: {
|
|
20453
|
+
sourceTreeDigest: "373f08151b5a209a84dd35b086316ef11a0d4030150457c2bfdab38305903d54",
|
|
20454
|
+
recipeSha256: "56976ed58b17053971334a62b503d5397b8ff739911fb596a116df23aeccabb0",
|
|
20455
|
+
buildTools: [
|
|
20456
|
+
"setuptools==84.0.0",
|
|
20457
|
+
"Cython==3.1.4",
|
|
20458
|
+
"meson-python==0.21.0",
|
|
20459
|
+
"meson==1.12.0",
|
|
20460
|
+
"ninja==1.13.2",
|
|
20461
|
+
"versioneer==0.29"
|
|
20462
|
+
],
|
|
20463
|
+
nativeDependencies: [],
|
|
20464
|
+
patches: []
|
|
20465
|
+
}
|
|
20466
|
+
},
|
|
20467
|
+
{
|
|
20468
|
+
name: "pillow",
|
|
20469
|
+
version: "12.3.0",
|
|
20470
|
+
filename: "pillow-12.3.0-cp313-cp313-emscripten_5_0_6_wasm32.whl",
|
|
20471
|
+
sha256: "7e585c71bf943029d8201283308a1b43f2be5f166d9beb36697d1f6bfba865e9",
|
|
20472
|
+
abiId: "sbxabi1-c2637d04695ad927",
|
|
20473
|
+
requires: [
|
|
20474
|
+
'furo; extra == "docs"',
|
|
20475
|
+
'olefile; extra == "docs"',
|
|
20476
|
+
'sphinx>=8.2; extra == "docs"',
|
|
20477
|
+
'sphinx-autobuild; extra == "docs"',
|
|
20478
|
+
'sphinx-copybutton; extra == "docs"',
|
|
20479
|
+
'sphinx-inline-tabs; extra == "docs"',
|
|
20480
|
+
'sphinxext-opengraph; extra == "docs"',
|
|
20481
|
+
'olefile; extra == "fpx"',
|
|
20482
|
+
'olefile; extra == "mic"',
|
|
20483
|
+
'arro3-compute; extra == "test-arrow"',
|
|
20484
|
+
'arro3-core; extra == "test-arrow"',
|
|
20485
|
+
'nanoarrow; extra == "test-arrow"',
|
|
20486
|
+
'pyarrow; extra == "test-arrow"',
|
|
20487
|
+
'coverage>=7.4.2; extra == "tests"',
|
|
20488
|
+
'defusedxml; extra == "tests"',
|
|
20489
|
+
'markdown2; extra == "tests"',
|
|
20490
|
+
'olefile; extra == "tests"',
|
|
20491
|
+
'packaging; extra == "tests"',
|
|
20492
|
+
'pytest; extra == "tests"',
|
|
20493
|
+
'pytest-cov; extra == "tests"',
|
|
20494
|
+
'pytest-timeout; extra == "tests"',
|
|
20495
|
+
'pytest-xdist; extra == "tests"',
|
|
20496
|
+
'setuptools; extra == "tests"',
|
|
20497
|
+
'trove-classifiers>=2024.10.12; extra == "tests"',
|
|
20498
|
+
'defusedxml; extra == "xmp"'
|
|
20499
|
+
],
|
|
20500
|
+
provenance: {
|
|
20501
|
+
sourceTreeDigest: "5427eff55a6a0e5f667d70527c2c3749f2c0786598903d7bbe7aaf16c4e95e31",
|
|
20502
|
+
recipeSha256: "77418b7e139e4779f2a422a06abbea27c941174b1f86597a2f3c4483bb236b7f",
|
|
20503
|
+
buildTools: [
|
|
20504
|
+
"setuptools==84.0.0",
|
|
20505
|
+
"pybind11==3.1.0"
|
|
20506
|
+
],
|
|
20507
|
+
nativeDependencies: [
|
|
20508
|
+
"z==1.3.1",
|
|
20509
|
+
"jpeg==9f"
|
|
20510
|
+
],
|
|
20241
20511
|
patches: []
|
|
20242
20512
|
}
|
|
20243
20513
|
},
|
|
@@ -20298,6 +20568,31 @@ var wheels_default = {
|
|
|
20298
20568
|
patches: []
|
|
20299
20569
|
}
|
|
20300
20570
|
},
|
|
20571
|
+
{
|
|
20572
|
+
name: "rapidfuzz",
|
|
20573
|
+
version: "3.14.6",
|
|
20574
|
+
filename: "rapidfuzz-3.14.6-cp313-cp313-emscripten_5_0_6_wasm32.whl",
|
|
20575
|
+
sha256: "b9ed2ea058350925beda765364c0f382bf9a7c08b3bec33840795178de021d32",
|
|
20576
|
+
abiId: "sbxabi1-c2637d04695ad927",
|
|
20577
|
+
requires: [
|
|
20578
|
+
'numpy; extra == "all"'
|
|
20579
|
+
],
|
|
20580
|
+
provenance: {
|
|
20581
|
+
sourceTreeDigest: "ae2b2bb95f7aba2babbbe668b68d48b5347ca923e6508b76d24228cd13ac7cd3",
|
|
20582
|
+
recipeSha256: "46954814226fff070aa6771d2f09d32a96b718ce8ef7a9f9404375850f7d1f77",
|
|
20583
|
+
buildTools: [
|
|
20584
|
+
"setuptools==84.0.0",
|
|
20585
|
+
"scikit-build-core==1.0.3",
|
|
20586
|
+
"cmake==4.4.3",
|
|
20587
|
+
"ninja==1.13.2",
|
|
20588
|
+
"Cython==3.1.4",
|
|
20589
|
+
"pathspec==1.1.1",
|
|
20590
|
+
"packaging==26.3"
|
|
20591
|
+
],
|
|
20592
|
+
nativeDependencies: [],
|
|
20593
|
+
patches: []
|
|
20594
|
+
}
|
|
20595
|
+
},
|
|
20301
20596
|
{
|
|
20302
20597
|
name: "regex",
|
|
20303
20598
|
version: "2026.9.10",
|
|
@@ -20503,6 +20798,23 @@ var wheels_default = {
|
|
|
20503
20798
|
patches: []
|
|
20504
20799
|
}
|
|
20505
20800
|
},
|
|
20801
|
+
{
|
|
20802
|
+
name: "simplejson",
|
|
20803
|
+
version: "4.1.2",
|
|
20804
|
+
filename: "simplejson-4.1.2-cp313-cp313-emscripten_5_0_6_wasm32.whl",
|
|
20805
|
+
sha256: "80671c2b02beb0d429108df8cefb6f27857b34e032bb5a5f1e0f957e7bf3cab6",
|
|
20806
|
+
abiId: "sbxabi1-c2637d04695ad927",
|
|
20807
|
+
requires: [],
|
|
20808
|
+
provenance: {
|
|
20809
|
+
sourceTreeDigest: "055af71755ff30d71076fdebc6f605e66f5876e6f01350a7d6075e8679006111",
|
|
20810
|
+
recipeSha256: "e163a1f2e78f32265c3ffdbf743dab27bc9e0829f21c553cabf0bafafd7d69fd",
|
|
20811
|
+
buildTools: [
|
|
20812
|
+
"setuptools==84.0.0"
|
|
20813
|
+
],
|
|
20814
|
+
nativeDependencies: [],
|
|
20815
|
+
patches: []
|
|
20816
|
+
}
|
|
20817
|
+
},
|
|
20506
20818
|
{
|
|
20507
20819
|
name: "siphash24",
|
|
20508
20820
|
version: "1.9",
|
|
@@ -20546,6 +20858,24 @@ var wheels_default = {
|
|
|
20546
20858
|
patches: []
|
|
20547
20859
|
}
|
|
20548
20860
|
},
|
|
20861
|
+
{
|
|
20862
|
+
name: "ujson",
|
|
20863
|
+
version: "6.0.0",
|
|
20864
|
+
filename: "ujson-6.0.0-cp313-cp313-emscripten_5_0_6_wasm32.whl",
|
|
20865
|
+
sha256: "3d4fd009acb9fad7a2070b0e2e498f6257e9c0cc28f806950d8c04cbb6849c24",
|
|
20866
|
+
abiId: "sbxabi1-c2637d04695ad927",
|
|
20867
|
+
requires: [],
|
|
20868
|
+
provenance: {
|
|
20869
|
+
sourceTreeDigest: "2a0c67ecb71628cb89b46090372d11dc4cf6e91c2406260a4e544c17331a31da",
|
|
20870
|
+
recipeSha256: "e9f3fe48e14241bd38d1d20dc691413d8c646984d67ae3898f4b27d10af4bf80",
|
|
20871
|
+
buildTools: [
|
|
20872
|
+
"setuptools==84.0.0",
|
|
20873
|
+
"setuptools-scm==10.2.3"
|
|
20874
|
+
],
|
|
20875
|
+
nativeDependencies: [],
|
|
20876
|
+
patches: []
|
|
20877
|
+
}
|
|
20878
|
+
},
|
|
20549
20879
|
{
|
|
20550
20880
|
name: "uuid-utils",
|
|
20551
20881
|
version: "0.17.1",
|
|
@@ -20628,7 +20958,7 @@ var wheels_default = {
|
|
|
20628
20958
|
|
|
20629
20959
|
// package.json
|
|
20630
20960
|
var package_default = {
|
|
20631
|
-
version: "0.1.
|
|
20961
|
+
version: "0.1.94"};
|
|
20632
20962
|
|
|
20633
20963
|
// src/python/extension-abi.ts
|
|
20634
20964
|
var EXTENSION_ABI = {
|
|
@@ -21666,6 +21996,8 @@ var VirtualTcpSocket = class _VirtualTcpSocket {
|
|
|
21666
21996
|
network;
|
|
21667
21997
|
listenerValue = null;
|
|
21668
21998
|
connectionValue = null;
|
|
21999
|
+
/** Held so closing the guest's socket closes the host's, not just the queues. */
|
|
22000
|
+
hostConnection = null;
|
|
21669
22001
|
boundPort = 0;
|
|
21670
22002
|
boundAddress = "0.0.0.0";
|
|
21671
22003
|
bind(address, port) {
|
|
@@ -21683,13 +22015,35 @@ var VirtualTcpSocket = class _VirtualTcpSocket {
|
|
|
21683
22015
|
}
|
|
21684
22016
|
this.listenerValue = this.network.listen(this.boundPort, this.boundAddress, backlog);
|
|
21685
22017
|
}
|
|
21686
|
-
|
|
22018
|
+
/**
|
|
22019
|
+
* Connect, in the container or out of it.
|
|
22020
|
+
*
|
|
22021
|
+
* The destination used to be discarded, which quietly turned every connect
|
|
22022
|
+
* into a loopback connect: a guest dialling example.com:443 would reach a
|
|
22023
|
+
* container server listening on 443 and believe it had reached the internet.
|
|
22024
|
+
* Loopback is still routed inside the container; anything else goes to the
|
|
22025
|
+
* host's outbound stack, which owns the names and the real sockets, and
|
|
22026
|
+
* which refuses with a reason when the host has no way out.
|
|
22027
|
+
*/
|
|
22028
|
+
async connect(port, address = "127.0.0.1") {
|
|
21687
22029
|
if (this.listenerValue || this.connectionValue) throw new PosixError(Errno.EISCONN);
|
|
21688
22030
|
if (!isLoopback(address)) {
|
|
21689
|
-
|
|
21690
|
-
|
|
21691
|
-
|
|
21692
|
-
|
|
22031
|
+
const outbound = this.network.outbound;
|
|
22032
|
+
if (!outbound) {
|
|
22033
|
+
throw new PosixError(
|
|
22034
|
+
Errno.ENETUNREACH,
|
|
22035
|
+
`cannot reach ${address}:${port}: outbound network access is not available through raw sockets in this container; only loopback is routed`
|
|
22036
|
+
);
|
|
22037
|
+
}
|
|
22038
|
+
const dialled = await outbound.connect(address, port, {
|
|
22039
|
+
address: this.boundAddress,
|
|
22040
|
+
port: this.boundPort
|
|
22041
|
+
});
|
|
22042
|
+
this.connectionValue = dialled.connection;
|
|
22043
|
+
this.hostConnection = dialled.host;
|
|
22044
|
+
this.boundPort = dialled.connection.local.port;
|
|
22045
|
+
this.boundAddress = dialled.connection.local.address;
|
|
22046
|
+
return;
|
|
21693
22047
|
}
|
|
21694
22048
|
this.connectionValue = this.network.connect(port, this.boundPort, this.boundAddress);
|
|
21695
22049
|
this.boundPort = this.connectionValue.local.port;
|
|
@@ -21740,6 +22094,8 @@ var VirtualTcpSocket = class _VirtualTcpSocket {
|
|
|
21740
22094
|
close() {
|
|
21741
22095
|
if (this.listenerValue) this.network.close(this.listenerValue);
|
|
21742
22096
|
this.connectionValue?.close();
|
|
22097
|
+
this.hostConnection?.close();
|
|
22098
|
+
this.hostConnection = null;
|
|
21743
22099
|
this.listenerValue = null;
|
|
21744
22100
|
this.connectionValue = null;
|
|
21745
22101
|
}
|
|
@@ -21804,7 +22160,7 @@ var SocketDescription = class _SocketDescription {
|
|
|
21804
22160
|
this.resource.listen(backlog);
|
|
21805
22161
|
}
|
|
21806
22162
|
connect(address, port) {
|
|
21807
|
-
this.resource.connect(port, address);
|
|
22163
|
+
return this.resource.connect(port, address);
|
|
21808
22164
|
}
|
|
21809
22165
|
accept() {
|
|
21810
22166
|
return new _SocketDescription(this.resource.accept(), this.flags);
|
|
@@ -21816,7 +22172,11 @@ var SocketDescription = class _SocketDescription {
|
|
|
21816
22172
|
this.options.set(`${level}:${option}`, value.slice());
|
|
21817
22173
|
}
|
|
21818
22174
|
getOption(level, option) {
|
|
21819
|
-
|
|
22175
|
+
const stored = this.options.get(`${level}:${option}`);
|
|
22176
|
+
if (stored) return stored.slice();
|
|
22177
|
+
if (level === SOL_SOCKET && option === SO_TYPE) return int32(SOCK_STREAM);
|
|
22178
|
+
if (level === SOL_SOCKET && option === SO_ERROR) return int32(0);
|
|
22179
|
+
return new Uint8Array(4);
|
|
21820
22180
|
}
|
|
21821
22181
|
localAddress() {
|
|
21822
22182
|
return this.resource.local;
|
|
@@ -21827,6 +22187,15 @@ var SocketDescription = class _SocketDescription {
|
|
|
21827
22187
|
return peer;
|
|
21828
22188
|
}
|
|
21829
22189
|
};
|
|
22190
|
+
var SOL_SOCKET = 1;
|
|
22191
|
+
var SO_ERROR = 4;
|
|
22192
|
+
var SO_TYPE = 3;
|
|
22193
|
+
var SOCK_STREAM = 1;
|
|
22194
|
+
function int32(value) {
|
|
22195
|
+
const bytes2 = new Uint8Array(4);
|
|
22196
|
+
new DataView(bytes2.buffer).setInt32(0, value, true);
|
|
22197
|
+
return bytes2;
|
|
22198
|
+
}
|
|
21830
22199
|
function isLoopback(address) {
|
|
21831
22200
|
if (address === "" || address === "localhost" || address === "0.0.0.0") return true;
|
|
21832
22201
|
if (address === "::1" || address === "::" || address === "[::1]") return true;
|
|
@@ -21838,6 +22207,18 @@ var VirtualTcpNetwork = class {
|
|
|
21838
22207
|
}
|
|
21839
22208
|
occupied;
|
|
21840
22209
|
listeners = /* @__PURE__ */ new Map();
|
|
22210
|
+
/** The host's outbound stack, when this host can open sockets at all. */
|
|
22211
|
+
outbound = null;
|
|
22212
|
+
/**
|
|
22213
|
+
* Ports held by in-container servers that are not sockets.
|
|
22214
|
+
*
|
|
22215
|
+
* A JavaScript HTTP server in the container is registered with the request
|
|
22216
|
+
* router and never binds one of these sockets, so a guest connecting to its
|
|
22217
|
+
* port found nothing listening. Before sockets could leave the container
|
|
22218
|
+
* this did not arise -- every guest client went out through the egress --
|
|
22219
|
+
* and afterwards `urllib` dialling a container server got ECONNREFUSED.
|
|
22220
|
+
*/
|
|
22221
|
+
loopbackHttp = null;
|
|
21841
22222
|
listen(port, address = "0.0.0.0", backlog = 128) {
|
|
21842
22223
|
if (this.listeners.has(port) || this.occupied?.(port)) throw new PosixError(Errno.EADDRINUSE, `port ${port} is already in use`);
|
|
21843
22224
|
const listener = new VirtualTcpListener(port, address, backlog);
|
|
@@ -21851,7 +22232,11 @@ var VirtualTcpNetwork = class {
|
|
|
21851
22232
|
}
|
|
21852
22233
|
connect(port, localPort = 0, localAddress = "127.0.0.1") {
|
|
21853
22234
|
const listener = this.listeners.get(port);
|
|
21854
|
-
if (!listener || listener.isClosed)
|
|
22235
|
+
if (!listener || listener.isClosed) {
|
|
22236
|
+
const bridged = this.connectToNonSocketServer(port, localPort, localAddress);
|
|
22237
|
+
if (bridged) return bridged;
|
|
22238
|
+
throw new PosixError(Errno.ECONNREFUSED, `port ${port} is not listening`);
|
|
22239
|
+
}
|
|
21855
22240
|
const client = new VirtualTcpConnection(
|
|
21856
22241
|
{ address: localAddress, port: localPort },
|
|
21857
22242
|
{ address: listener.address, port }
|
|
@@ -21865,6 +22250,26 @@ var VirtualTcpNetwork = class {
|
|
|
21865
22250
|
listener.enqueue(server);
|
|
21866
22251
|
return client;
|
|
21867
22252
|
}
|
|
22253
|
+
/** A connection to a port an in-container server holds without a socket. */
|
|
22254
|
+
connectToNonSocketServer(port, localPort, localAddress) {
|
|
22255
|
+
if (!this.loopbackHttp) return null;
|
|
22256
|
+
const client = new VirtualTcpConnection(
|
|
22257
|
+
{ address: localAddress, port: localPort },
|
|
22258
|
+
{ address: "127.0.0.1", port }
|
|
22259
|
+
);
|
|
22260
|
+
const server = new VirtualTcpConnection(
|
|
22261
|
+
{ address: "127.0.0.1", port },
|
|
22262
|
+
{ address: localAddress, port: localPort }
|
|
22263
|
+
);
|
|
22264
|
+
client.pairWith(server);
|
|
22265
|
+
server.pairWith(client);
|
|
22266
|
+
if (!this.loopbackHttp(port, server)) {
|
|
22267
|
+
client.close();
|
|
22268
|
+
server.close();
|
|
22269
|
+
return null;
|
|
22270
|
+
}
|
|
22271
|
+
return client;
|
|
22272
|
+
}
|
|
21868
22273
|
hasListener(port) {
|
|
21869
22274
|
return this.listeners.has(port);
|
|
21870
22275
|
}
|
|
@@ -21882,7 +22287,7 @@ var POLLIN = 1;
|
|
|
21882
22287
|
var POLLOUT = 4;
|
|
21883
22288
|
var POLLHUP = 16;
|
|
21884
22289
|
var AF_INET = 2;
|
|
21885
|
-
var
|
|
22290
|
+
var SOCK_STREAM2 = 1;
|
|
21886
22291
|
var IPPROTO_TCP = 6;
|
|
21887
22292
|
function createHostAbiServer(proc) {
|
|
21888
22293
|
const files = new FileService(proc.vfs, proc.cred);
|
|
@@ -22173,7 +22578,7 @@ function createHostAbiServer(proc) {
|
|
|
22173
22578
|
const family = r.u32();
|
|
22174
22579
|
const type = r.u32();
|
|
22175
22580
|
const protocol = r.i32();
|
|
22176
|
-
if (family !== AF_INET || (type & 15) !==
|
|
22581
|
+
if (family !== AF_INET || (type & 15) !== SOCK_STREAM2) throw new PosixError(Errno.EAFNOSUPPORT);
|
|
22177
22582
|
if (protocol !== 0 && protocol !== IPPROTO_TCP) throw new PosixError(Errno.EPROTONOSUPPORT);
|
|
22178
22583
|
if (!proc.sockets) throw new PosixError(Errno.ENOSYS);
|
|
22179
22584
|
return { status: proc.table.add(new SocketDescription(new VirtualTcpSocket(proc.sockets))) };
|
|
@@ -22197,9 +22602,15 @@ function createHostAbiServer(proc) {
|
|
|
22197
22602
|
}
|
|
22198
22603
|
case Op.connect: {
|
|
22199
22604
|
const description = socketDescription(proc.table.get(r.i32()));
|
|
22200
|
-
description.connect(r.string(), r.u32());
|
|
22605
|
+
await description.connect(r.string(), r.u32());
|
|
22201
22606
|
return { status: 0 };
|
|
22202
22607
|
}
|
|
22608
|
+
case Op.resolve: {
|
|
22609
|
+
const sockets = proc.sockets;
|
|
22610
|
+
if (!sockets?.outbound) throw new PosixError(Errno.ENOSYS);
|
|
22611
|
+
const address = sockets.outbound.resolve(r.string());
|
|
22612
|
+
return { status: 0, payload: new Writer().string(address).finish() };
|
|
22613
|
+
}
|
|
22203
22614
|
case Op.send: {
|
|
22204
22615
|
const description = socketDescription(proc.table.get(r.i32()));
|
|
22205
22616
|
return { status: await writeBlocking(description, r.bytes32()) };
|
|
@@ -22733,6 +23144,38 @@ async function serve(ctx, connection) {
|
|
|
22733
23144
|
headers: forwarded,
|
|
22734
23145
|
...request.body.length > 0 ? { body: request.body } : {}
|
|
22735
23146
|
});
|
|
23147
|
+
const {
|
|
23148
|
+
"content-encoding": _encoding,
|
|
23149
|
+
"content-length": _length,
|
|
23150
|
+
"transfer-encoding": _chunked,
|
|
23151
|
+
...responseHeaders
|
|
23152
|
+
} = result.headers;
|
|
23153
|
+
writeResponse(connection, result.status, result.statusText, responseHeaders, result.body);
|
|
23154
|
+
} catch (error) {
|
|
23155
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
23156
|
+
try {
|
|
23157
|
+
writeResponse(
|
|
23158
|
+
connection,
|
|
23159
|
+
502,
|
|
23160
|
+
"Bad Gateway",
|
|
23161
|
+
{ "x-sbx-error": "1" },
|
|
23162
|
+
new TextEncoder().encode(`${message}
|
|
23163
|
+
`)
|
|
23164
|
+
);
|
|
23165
|
+
} catch {
|
|
23166
|
+
}
|
|
23167
|
+
} finally {
|
|
23168
|
+
connection.close();
|
|
23169
|
+
}
|
|
23170
|
+
}
|
|
23171
|
+
async function serveHttpOverConnection(connection, handle) {
|
|
23172
|
+
try {
|
|
23173
|
+
const request = await readRequest(connection);
|
|
23174
|
+
if (!request) {
|
|
23175
|
+
writeResponse(connection, 400, "Bad Request", {}, new TextEncoder().encode("malformed request\n"));
|
|
23176
|
+
return;
|
|
23177
|
+
}
|
|
23178
|
+
const result = await handle(request);
|
|
22736
23179
|
writeResponse(connection, result.status, result.statusText, result.headers, result.body);
|
|
22737
23180
|
} catch (error) {
|
|
22738
23181
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -23032,16 +23475,33 @@ def run(main, *, loop_factory=new_event_loop, debug=None, **run_kwargs):
|
|
|
23032
23475
|
with __asyncio.Runner(loop_factory=loop_factory, debug=debug) as runner:
|
|
23033
23476
|
return runner.run(main)
|
|
23034
23477
|
`;
|
|
23035
|
-
|
|
23478
|
+
var HTTPX2_JSFETCH_VERSION = "1.0";
|
|
23479
|
+
var HTTPX2_JSFETCH_SOURCE = `"""SandboxedJs stand-in for httpx2-jsfetch.
|
|
23480
|
+
|
|
23481
|
+
The real package drives the browser fetch API through Pyodide's \`js\` module.
|
|
23482
|
+
This runtime has no \`js\` module but does have sockets, so httpx2's regular
|
|
23483
|
+
httpcore2 transports are used under the names httpx2 imports on Emscripten.
|
|
23484
|
+
"""
|
|
23485
|
+
|
|
23486
|
+
from httpx2._transports.default import (
|
|
23487
|
+
AsyncHTTPTransport as AsyncJavascriptFetchTransport,
|
|
23488
|
+
HTTPTransport as JavascriptFetchTransport,
|
|
23489
|
+
)
|
|
23490
|
+
|
|
23491
|
+
__all__ = ["AsyncJavascriptFetchTransport", "JavascriptFetchTransport"]
|
|
23492
|
+
`;
|
|
23493
|
+
function builtinDistribution(name, version, summary, files, requires = []) {
|
|
23036
23494
|
return {
|
|
23037
23495
|
version,
|
|
23496
|
+
requires,
|
|
23038
23497
|
files: {
|
|
23039
23498
|
...files,
|
|
23040
23499
|
[`${name}-${version}.dist-info/METADATA`]: `Metadata-Version: 2.1
|
|
23041
23500
|
Name: ${name}
|
|
23042
23501
|
Version: ${version}
|
|
23043
23502
|
Summary: ${summary}
|
|
23044
|
-
|
|
23503
|
+
` + requires.map((r) => `Requires-Dist: ${r}
|
|
23504
|
+
`).join(""),
|
|
23045
23505
|
[`${name}-${version}.dist-info/WHEEL`]: "Wheel-Version: 1.0\nGenerator: sandboxedjs\nRoot-Is-Purelib: true\nTag: py3-none-any\n",
|
|
23046
23506
|
[`${name}-${version}.dist-info/INSTALLER`]: "sandboxedjs\n",
|
|
23047
23507
|
[`${name}-${version}.dist-info/RECORD`]: ""
|
|
@@ -23049,6 +23509,13 @@ Summary: ${summary}
|
|
|
23049
23509
|
};
|
|
23050
23510
|
}
|
|
23051
23511
|
var BUILTINS3 = {
|
|
23512
|
+
"httpx2-jsfetch": builtinDistribution(
|
|
23513
|
+
"httpx2_jsfetch",
|
|
23514
|
+
HTTPX2_JSFETCH_VERSION,
|
|
23515
|
+
"SandboxedJs stand-in: httpx2's socket transports under the jsfetch names",
|
|
23516
|
+
{ "httpx2_jsfetch/__init__.py": HTTPX2_JSFETCH_SOURCE },
|
|
23517
|
+
["httpcore2[asyncio]", "anyio>=4.10", "truststore>=0.10"]
|
|
23518
|
+
),
|
|
23052
23519
|
greenlet: builtinDistribution(
|
|
23053
23520
|
"greenlet",
|
|
23054
23521
|
GREENLET_VERSION,
|
|
@@ -23073,7 +23540,7 @@ function builtinCandidates(name) {
|
|
|
23073
23540
|
filename: `${name}-${builtin.version}-py3-none-any.whl`,
|
|
23074
23541
|
sha256: "",
|
|
23075
23542
|
metadataUrl: null,
|
|
23076
|
-
indexedRequires:
|
|
23543
|
+
indexedRequires: builtin.requires
|
|
23077
23544
|
}];
|
|
23078
23545
|
}
|
|
23079
23546
|
function isBuiltinOnly(name) {
|
|
@@ -23257,7 +23724,7 @@ async function resolve2(options) {
|
|
|
23257
23724
|
if (viable.length === 0) {
|
|
23258
23725
|
const wanted = [...unusable.get(name)?.versions ?? []].filter((version) => clauses.every((s) => compareVersions(version, s.version, s.op)));
|
|
23259
23726
|
const nothingUsable = all.length === 0 || wanted.length > 0;
|
|
23260
|
-
const
|
|
23727
|
+
const error = new ResolutionError(
|
|
23261
23728
|
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`,
|
|
23262
23729
|
explain(name, next.constraints, null, all),
|
|
23263
23730
|
nothingUsable ? {
|
|
@@ -23269,8 +23736,8 @@ async function resolve2(options) {
|
|
|
23269
23736
|
sourceAvailable: unusable.get(name)?.sourceAvailable ?? false
|
|
23270
23737
|
} : { reason: "conflicting-requirements", package: name }
|
|
23271
23738
|
);
|
|
23272
|
-
nogoods.set(signature,
|
|
23273
|
-
throw
|
|
23739
|
+
nogoods.set(signature, error);
|
|
23740
|
+
throw error;
|
|
23274
23741
|
}
|
|
23275
23742
|
let failure2 = null;
|
|
23276
23743
|
const seenVersions = /* @__PURE__ */ new Set();
|
|
@@ -23282,9 +23749,9 @@ async function resolve2(options) {
|
|
|
23282
23749
|
let requires;
|
|
23283
23750
|
try {
|
|
23284
23751
|
requires = await requiresFor(candidate);
|
|
23285
|
-
} catch (
|
|
23752
|
+
} catch (error) {
|
|
23286
23753
|
failure2 = new ResolutionError(
|
|
23287
|
-
`could not read the dependencies of ${name} ${candidate.version}: ${
|
|
23754
|
+
`could not read the dependencies of ${name} ${candidate.version}: ${error.message}`
|
|
23288
23755
|
);
|
|
23289
23756
|
continue;
|
|
23290
23757
|
}
|
|
@@ -23308,15 +23775,13 @@ async function resolve2(options) {
|
|
|
23308
23775
|
chosen.set(name, { ...candidate, requires });
|
|
23309
23776
|
try {
|
|
23310
23777
|
return await search([...edges, ...rest], { chosen, constraints: next.constraints });
|
|
23311
|
-
} catch (
|
|
23312
|
-
if (!(
|
|
23313
|
-
if (/cancelled|budget/.test(
|
|
23314
|
-
failure2 =
|
|
23778
|
+
} catch (error) {
|
|
23779
|
+
if (!(error instanceof ResolutionError)) throw error;
|
|
23780
|
+
if (/cancelled|budget/.test(error.message)) throw error;
|
|
23781
|
+
failure2 = error;
|
|
23315
23782
|
}
|
|
23316
23783
|
}
|
|
23317
|
-
|
|
23318
|
-
nogoods.set(signature, error);
|
|
23319
|
-
throw error;
|
|
23784
|
+
throw failure2 ?? new ResolutionError(`could not resolve ${name}`);
|
|
23320
23785
|
};
|
|
23321
23786
|
const seeded = /* @__PURE__ */ new Map();
|
|
23322
23787
|
for (const root of roots) {
|
|
@@ -23822,8 +24287,18 @@ async function runOwnedPython(ctx, argv) {
|
|
|
23822
24287
|
if (!manifest) throw new Error("no Python runtime manifest is configured");
|
|
23823
24288
|
const mounts = containerMounts(ctx);
|
|
23824
24289
|
const env2 = withSitePackages(ctx.env);
|
|
24290
|
+
await ensureTrustStore(ctx);
|
|
23825
24291
|
const egressPort = ensureHttpEgress(ctx);
|
|
23826
24292
|
if (egressPort > 0) env2[EGRESS_ENV] = `127.0.0.1:${egressPort}`;
|
|
24293
|
+
try {
|
|
24294
|
+
if (ctx.vfs.stat(CA_BUNDLE_PATH, { cred: ctx.cred }).size > 0) {
|
|
24295
|
+
env2.SSL_CERT_FILE = CA_BUNDLE_PATH;
|
|
24296
|
+
env2.SSL_CERT_DIR = "/etc/ssl/certs";
|
|
24297
|
+
env2.REQUESTS_CA_BUNDLE = CA_BUNDLE_PATH;
|
|
24298
|
+
}
|
|
24299
|
+
} catch {
|
|
24300
|
+
}
|
|
24301
|
+
if (ctx.kernel.pod.sockets?.outbound) env2.SBX_OUTBOUND_SOCKETS = "1";
|
|
23827
24302
|
env2.SBX_SERIAL_HOST_CALLS = "1";
|
|
23828
24303
|
const inherit = claimInheritance(env2);
|
|
23829
24304
|
const process2 = await startPythonProcess({
|
|
@@ -23864,6 +24339,200 @@ function isTerminal(ctx) {
|
|
|
23864
24339
|
const stdin = ctx.stdin;
|
|
23865
24340
|
return Boolean(stdin.isTTY || stdin.interactive);
|
|
23866
24341
|
}
|
|
24342
|
+
var TLS_MODULE_SOURCE = `"""TLS over a SandboxedJs socket, using this interpreter's own OpenSSL.
|
|
24343
|
+
|
|
24344
|
+
CPython's \`ssl\` cannot wrap one of these sockets directly: \`_ssl\` wants a real
|
|
24345
|
+
\`_socket.socket\` whose descriptor OpenSSL can read and write, and a socket here
|
|
24346
|
+
is a host resource reached through the SandboxedJs ABI. But OpenSSL does not
|
|
24347
|
+
need a descriptor -- \`SSLContext.wrap_bio\` drives the same protocol over a pair
|
|
24348
|
+
of memory buffers, which is how asyncio has always done TLS. So the handshake
|
|
24349
|
+
and every record are performed here, in the container, and only the ciphertext
|
|
24350
|
+
crosses to the host. Certificates are verified by this interpreter against the
|
|
24351
|
+
trust store the host writes into the image, and \`getpeercert\` answers.
|
|
24352
|
+
"""
|
|
24353
|
+
|
|
24354
|
+
import io
|
|
24355
|
+
import socket as _socket_module
|
|
24356
|
+
import ssl as _ssl
|
|
24357
|
+
|
|
24358
|
+
|
|
24359
|
+
def wrappable(sock):
|
|
24360
|
+
"""Whether this socket is one of the container's own."""
|
|
24361
|
+
target = getattr(sock, "_sock", sock)
|
|
24362
|
+
return hasattr(target, "_fd")
|
|
24363
|
+
|
|
24364
|
+
|
|
24365
|
+
class SbxTlsSocket:
|
|
24366
|
+
"""What \`SSLContext.wrap_socket\` returns for a container socket."""
|
|
24367
|
+
|
|
24368
|
+
def __init__(self, context, sock, server_hostname, suppress_ragged_eofs=True):
|
|
24369
|
+
self._sock = sock
|
|
24370
|
+
self.context = context
|
|
24371
|
+
self.server_hostname = server_hostname
|
|
24372
|
+
self.suppress_ragged_eofs = suppress_ragged_eofs
|
|
24373
|
+
self._incoming = _ssl.MemoryBIO()
|
|
24374
|
+
self._outgoing = _ssl.MemoryBIO()
|
|
24375
|
+
self._closed = False
|
|
24376
|
+
self._tls = context.wrap_bio(
|
|
24377
|
+
self._incoming, self._outgoing,
|
|
24378
|
+
server_side=False,
|
|
24379
|
+
server_hostname=server_hostname,
|
|
24380
|
+
)
|
|
24381
|
+
|
|
24382
|
+
# -- moving bytes between OpenSSL and the socket ------------------------
|
|
24383
|
+
def _flush(self):
|
|
24384
|
+
data = self._outgoing.read()
|
|
24385
|
+
if data:
|
|
24386
|
+
self._sock.sendall(data)
|
|
24387
|
+
|
|
24388
|
+
def _feed(self):
|
|
24389
|
+
chunk = self._sock.recv(32768)
|
|
24390
|
+
if not chunk:
|
|
24391
|
+
self._incoming.write_eof()
|
|
24392
|
+
return False
|
|
24393
|
+
self._incoming.write(chunk)
|
|
24394
|
+
return True
|
|
24395
|
+
|
|
24396
|
+
def _run(self, operation):
|
|
24397
|
+
"""Drive one OpenSSL operation, pumping the socket as it asks."""
|
|
24398
|
+
while True:
|
|
24399
|
+
try:
|
|
24400
|
+
result = operation()
|
|
24401
|
+
except _ssl.SSLWantReadError:
|
|
24402
|
+
self._flush()
|
|
24403
|
+
if not self._feed():
|
|
24404
|
+
raise
|
|
24405
|
+
continue
|
|
24406
|
+
except _ssl.SSLWantWriteError:
|
|
24407
|
+
self._flush()
|
|
24408
|
+
continue
|
|
24409
|
+
self._flush()
|
|
24410
|
+
return result
|
|
24411
|
+
|
|
24412
|
+
# -- the handshake ------------------------------------------------------
|
|
24413
|
+
def do_handshake(self, block=False):
|
|
24414
|
+
self._run(self._tls.do_handshake)
|
|
24415
|
+
|
|
24416
|
+
# -- the socket ---------------------------------------------------------
|
|
24417
|
+
def recv(self, length=1024, flags=0):
|
|
24418
|
+
if flags:
|
|
24419
|
+
raise OSError("socket flags are not supported")
|
|
24420
|
+
try:
|
|
24421
|
+
return self._run(lambda: self._tls.read(length))
|
|
24422
|
+
except _ssl.SSLZeroReturnError:
|
|
24423
|
+
return b""
|
|
24424
|
+
except _ssl.SSLWantReadError:
|
|
24425
|
+
# The peer closed without close_notify. Ragged, and normal enough
|
|
24426
|
+
# on the public internet that clients ask to tolerate it.
|
|
24427
|
+
if self.suppress_ragged_eofs:
|
|
24428
|
+
return b""
|
|
24429
|
+
raise
|
|
24430
|
+
|
|
24431
|
+
def recv_into(self, buffer, nbytes=0, flags=0):
|
|
24432
|
+
data = self.recv(nbytes or len(buffer), flags)
|
|
24433
|
+
buffer[:len(data)] = data
|
|
24434
|
+
return len(data)
|
|
24435
|
+
|
|
24436
|
+
def read(self, length=1024, buffer=None):
|
|
24437
|
+
if buffer is not None:
|
|
24438
|
+
return self.recv_into(buffer, length)
|
|
24439
|
+
return self.recv(length)
|
|
24440
|
+
|
|
24441
|
+
def send(self, data, flags=0):
|
|
24442
|
+
if flags:
|
|
24443
|
+
raise OSError("socket flags are not supported")
|
|
24444
|
+
return self._run(lambda: self._tls.write(data))
|
|
24445
|
+
|
|
24446
|
+
def sendall(self, data, flags=0):
|
|
24447
|
+
view = memoryview(data)
|
|
24448
|
+
while view:
|
|
24449
|
+
view = view[self.send(view, flags):]
|
|
24450
|
+
|
|
24451
|
+
def write(self, data):
|
|
24452
|
+
return self.send(data)
|
|
24453
|
+
|
|
24454
|
+
_io_refs = 0
|
|
24455
|
+
|
|
24456
|
+
def _decref_socketios(self):
|
|
24457
|
+
# socket.SocketIO calls this when a file object built on this socket is
|
|
24458
|
+
# closed; without it http.client failed with "no attribute
|
|
24459
|
+
# _decref_socketios" the moment it read a response.
|
|
24460
|
+
if self._io_refs > 0:
|
|
24461
|
+
self._io_refs -= 1
|
|
24462
|
+
|
|
24463
|
+
def makefile(self, mode="r", buffering=None, *, encoding=None, errors=None, newline=None):
|
|
24464
|
+
# http.client and everything built on it read the response through a
|
|
24465
|
+
# file object, so this has to work like socket.makefile does.
|
|
24466
|
+
raw = _socket_module.SocketIO(self, "rw" if "w" in mode else "r")
|
|
24467
|
+
self._io_refs += 1
|
|
24468
|
+
buffer = io.BufferedRWPair(raw, raw) if "w" in mode and "r" in mode else (
|
|
24469
|
+
io.BufferedWriter(raw) if "w" in mode else io.BufferedReader(raw)
|
|
24470
|
+
)
|
|
24471
|
+
if "b" in mode:
|
|
24472
|
+
return buffer
|
|
24473
|
+
text = io.TextIOWrapper(buffer, encoding, errors, newline)
|
|
24474
|
+
text.mode = mode
|
|
24475
|
+
return text
|
|
24476
|
+
|
|
24477
|
+
def settimeout(self, value): return self._sock.settimeout(value)
|
|
24478
|
+
def gettimeout(self): return self._sock.gettimeout()
|
|
24479
|
+
def setblocking(self, flag): return self._sock.setblocking(flag)
|
|
24480
|
+
def getblocking(self): return self._sock.getblocking()
|
|
24481
|
+
def setsockopt(self, *args): return self._sock.setsockopt(*args)
|
|
24482
|
+
def getsockopt(self, *args): return self._sock.getsockopt(*args)
|
|
24483
|
+
def fileno(self): return self._sock.fileno()
|
|
24484
|
+
def getpeername(self): return self._sock.getpeername()
|
|
24485
|
+
def getsockname(self): return self._sock.getsockname()
|
|
24486
|
+
def detach(self): return self._sock.detach()
|
|
24487
|
+
|
|
24488
|
+
def shutdown(self, how):
|
|
24489
|
+
return self._sock.shutdown(how)
|
|
24490
|
+
|
|
24491
|
+
def unwrap(self):
|
|
24492
|
+
self._run(self._tls.unwrap)
|
|
24493
|
+
return self._sock
|
|
24494
|
+
|
|
24495
|
+
def close(self):
|
|
24496
|
+
if not self._closed:
|
|
24497
|
+
self._closed = True
|
|
24498
|
+
self._sock.close()
|
|
24499
|
+
|
|
24500
|
+
def __enter__(self): return self
|
|
24501
|
+
def __exit__(self, *args): self.close()
|
|
24502
|
+
|
|
24503
|
+
@property
|
|
24504
|
+
def family(self): return getattr(self._sock, "family", _socket_module.AF_INET)
|
|
24505
|
+
@property
|
|
24506
|
+
def type(self): return getattr(self._sock, "type", _socket_module.SOCK_STREAM)
|
|
24507
|
+
@property
|
|
24508
|
+
def proto(self): return getattr(self._sock, "proto", 0)
|
|
24509
|
+
@property
|
|
24510
|
+
def session(self): return self._tls.session
|
|
24511
|
+
@session.setter
|
|
24512
|
+
def session(self, value): self._tls.session = value
|
|
24513
|
+
@property
|
|
24514
|
+
def session_reused(self): return self._tls.session_reused
|
|
24515
|
+
|
|
24516
|
+
# -- what OpenSSL knows about this connection ---------------------------
|
|
24517
|
+
def getpeercert(self, binary_form=False): return self._tls.getpeercert(binary_form)
|
|
24518
|
+
def get_unverified_chain(self): return self._tls.get_unverified_chain()
|
|
24519
|
+
def get_verified_chain(self): return self._tls.get_verified_chain()
|
|
24520
|
+
def cipher(self): return self._tls.cipher()
|
|
24521
|
+
def shared_ciphers(self): return self._tls.shared_ciphers()
|
|
24522
|
+
def compression(self): return self._tls.compression()
|
|
24523
|
+
def selected_alpn_protocol(self): return self._tls.selected_alpn_protocol()
|
|
24524
|
+
def selected_npn_protocol(self): return None
|
|
24525
|
+
def version(self): return self._tls.version()
|
|
24526
|
+
def pending(self): return self._tls.pending()
|
|
24527
|
+
def get_channel_binding(self, cb_type="tls-unique"):
|
|
24528
|
+
return self._tls.get_channel_binding(cb_type)
|
|
24529
|
+
|
|
24530
|
+
|
|
24531
|
+
def wrap(context, sock, server_hostname, suppress_ragged_eofs=True):
|
|
24532
|
+
wrapped = SbxTlsSocket(context, sock, server_hostname, suppress_ragged_eofs)
|
|
24533
|
+
wrapped.do_handshake()
|
|
24534
|
+
return wrapped
|
|
24535
|
+
`;
|
|
23867
24536
|
var SHIM_HEADER = "# generated by sandboxedjs - safe to delete or replace\n";
|
|
23868
24537
|
var SHIM_SOURCE = `"""Rebuild os.system and os.popen on the container's process table."""
|
|
23869
24538
|
import io, os, subprocess, sys
|
|
@@ -23969,8 +24638,82 @@ if sys.platform in ("emscripten", "wasi"):
|
|
|
23969
24638
|
|
|
23970
24639
|
sys.meta_path.insert(0, _SbxAsyncioWatcherHook())
|
|
23971
24640
|
|
|
24641
|
+
class _SbxPyodideFetchHook:
|
|
24642
|
+
"""Libraries read \`sys.platform == "emscripten"\` as "running in Pyodide"
|
|
24643
|
+
and switch to a browser-fetch backend that imports Pyodide's \`js\`
|
|
24644
|
+
module. This runtime has no \`js\` but does have sockets, so those
|
|
24645
|
+
backends are replaced by modules that leave the socket path in place.
|
|
24646
|
+
urllib3 (and so requests) imports its backend at package import, which
|
|
24647
|
+
made anything using requests fail with "No module named 'js'"."""
|
|
23972
24648
|
|
|
23973
|
-
|
|
24649
|
+
_STANDINS = {
|
|
24650
|
+
# urllib3/__init__.py calls inject_into_urllib3() on emscripten.
|
|
24651
|
+
"urllib3.contrib.emscripten": "def inject_into_urllib3():\\n pass\\n",
|
|
24652
|
+
}
|
|
24653
|
+
|
|
24654
|
+
def find_spec(self, name, path=None, target=None):
|
|
24655
|
+
source = self._STANDINS.get(name)
|
|
24656
|
+
if source is None:
|
|
24657
|
+
return None
|
|
24658
|
+
import importlib.util
|
|
24659
|
+
|
|
24660
|
+
class _Loader:
|
|
24661
|
+
def create_module(self, spec):
|
|
24662
|
+
return None
|
|
24663
|
+
|
|
24664
|
+
def exec_module(self, module):
|
|
24665
|
+
exec(compile(source, f"<sandboxedjs {name}>", "exec"), module.__dict__)
|
|
24666
|
+
|
|
24667
|
+
return importlib.util.spec_from_loader(name, _Loader(), is_package=False)
|
|
24668
|
+
|
|
24669
|
+
sys.meta_path.insert(0, _SbxPyodideFetchHook())
|
|
24670
|
+
|
|
24671
|
+
|
|
24672
|
+
# The egress shims exist for a host that cannot open sockets. Where the host
|
|
24673
|
+
# can, the guest's own clients are used instead: real streaming, real TLS, and
|
|
24674
|
+
# whatever protocol the library speaks rather than the subset HTTP-over-egress
|
|
24675
|
+
# allows. Both worlds are supported, and which one this is comes from the host
|
|
24676
|
+
# rather than from probing.
|
|
24677
|
+
if os.environ.get("SBX_OUTBOUND_SOCKETS") == "1":
|
|
24678
|
+
|
|
24679
|
+
class _SbxTruststoreHook:
|
|
24680
|
+
"""truststore verifies the peer's chain itself, against the operating
|
|
24681
|
+
system's trust store. In this container the handshake happens on the
|
|
24682
|
+
host, which verified the chain against exactly that store, and the
|
|
24683
|
+
chain is not carried back -- so truststore's own check cannot run and
|
|
24684
|
+
httpx2 failed with "no attribute get_unverified_chain". Its context is
|
|
24685
|
+
therefore the standard one: same verification, done once, where the
|
|
24686
|
+
connection is."""
|
|
24687
|
+
|
|
24688
|
+
def find_spec(self, name, path=None, target=None):
|
|
24689
|
+
if name != "truststore":
|
|
24690
|
+
return None
|
|
24691
|
+
import importlib.util
|
|
24692
|
+
|
|
24693
|
+
class _Loader:
|
|
24694
|
+
def create_module(self, spec):
|
|
24695
|
+
return None
|
|
24696
|
+
|
|
24697
|
+
def exec_module(self, module):
|
|
24698
|
+
import ssl as _ssl
|
|
24699
|
+
module.SSLContext = _ssl.SSLContext
|
|
24700
|
+
|
|
24701
|
+
def inject_into_ssl():
|
|
24702
|
+
pass
|
|
24703
|
+
|
|
24704
|
+
def extract_from_ssl():
|
|
24705
|
+
pass
|
|
24706
|
+
|
|
24707
|
+
module.inject_into_ssl = inject_into_ssl
|
|
24708
|
+
module.extract_from_ssl = extract_from_ssl
|
|
24709
|
+
module.__version__ = "sandboxedjs"
|
|
24710
|
+
|
|
24711
|
+
return importlib.util.spec_from_loader(name, _Loader(), is_package=True)
|
|
24712
|
+
|
|
24713
|
+
sys.meta_path.insert(0, _SbxTruststoreHook())
|
|
24714
|
+
|
|
24715
|
+
|
|
24716
|
+
_egress = None if os.environ.get("SBX_OUTBOUND_SOCKETS") == "1" else os.environ.get("SBX_HTTP_EGRESS")
|
|
23974
24717
|
if _egress:
|
|
23975
24718
|
import http.client
|
|
23976
24719
|
import urllib.request
|
|
@@ -24050,6 +24793,255 @@ if _egress:
|
|
|
24050
24793
|
http.client.HTTPConnection = _SbxEgressConnection
|
|
24051
24794
|
http.client.HTTPSConnection = _SbxEgressTlsConnection
|
|
24052
24795
|
|
|
24796
|
+
# Everything above routes code that reaches for the standard library. The
|
|
24797
|
+
# popular HTTP clients do not: urllib3 (so requests), httpcore (so httpx)
|
|
24798
|
+
# and httpcore2 (so httpx2) open sockets themselves, and raw sockets do not
|
|
24799
|
+
# leave this container -- connect() to anything but loopback is
|
|
24800
|
+
# ENETUNREACH, and CPython's ssl cannot wrap these sockets anyway. So each
|
|
24801
|
+
# client's connection layer is replaced by one that speaks plain HTTP to
|
|
24802
|
+
# the egress endpoint with the absolute URL in the request line, which is
|
|
24803
|
+
# what the standard-library shims above already do.
|
|
24804
|
+
|
|
24805
|
+
def _sbx_egress_request(method, url, headers, body, timeout=None):
|
|
24806
|
+
"""One request through the egress. Returns (status, reason, headers, body)."""
|
|
24807
|
+
connection = _RealHTTPConnection(_egress_host, _egress_port, timeout=timeout)
|
|
24808
|
+
try:
|
|
24809
|
+
connection.request(method, url, body, {k: v for k, v in headers if k.lower() != "host"})
|
|
24810
|
+
response = connection.getresponse()
|
|
24811
|
+
payload = response.read()
|
|
24812
|
+
if response.getheader("x-sbx-error"):
|
|
24813
|
+
raise OSError(payload.decode("utf-8", "replace").strip())
|
|
24814
|
+
return response.status, response.reason, response.getheaders(), payload
|
|
24815
|
+
finally:
|
|
24816
|
+
connection.close()
|
|
24817
|
+
|
|
24818
|
+
async def _sbx_egress_request_async(method, url, headers, body):
|
|
24819
|
+
"""The same hop as _sbx_egress_request, over asyncio streams."""
|
|
24820
|
+
import asyncio
|
|
24821
|
+
reader, writer = await asyncio.open_connection(_egress_host, _egress_port)
|
|
24822
|
+
try:
|
|
24823
|
+
payload = body or b""
|
|
24824
|
+
if isinstance(payload, str):
|
|
24825
|
+
payload = payload.encode()
|
|
24826
|
+
lines = [f"{method} {url} HTTP/1.1", f"Host: {_egress_host}:{_egress_port}"]
|
|
24827
|
+
for key, value in headers:
|
|
24828
|
+
if key.lower() in ("host", "connection", "content-length", "transfer-encoding"):
|
|
24829
|
+
continue
|
|
24830
|
+
lines.append(f"{key}: {value}")
|
|
24831
|
+
lines.append(f"Content-Length: {len(payload)}")
|
|
24832
|
+
# Read to EOF rather than tracking keep-alive framing: one request
|
|
24833
|
+
# per connection, which is what this hop is for.
|
|
24834
|
+
lines.append("Connection: close")
|
|
24835
|
+
writer.write(("\\r\\n".join(lines) + "\\r\\n\\r\\n").encode("latin-1") + payload)
|
|
24836
|
+
await writer.drain()
|
|
24837
|
+
|
|
24838
|
+
head = await reader.readuntil(b"\\r\\n\\r\\n")
|
|
24839
|
+
start, _, rest = head.decode("latin-1").partition("\\r\\n")
|
|
24840
|
+
_, _, status_text = start.partition(" ")
|
|
24841
|
+
code, _, reason = status_text.partition(" ")
|
|
24842
|
+
received = []
|
|
24843
|
+
for line in rest.split("\\r\\n"):
|
|
24844
|
+
key, sep, value = line.partition(":")
|
|
24845
|
+
if sep:
|
|
24846
|
+
received.append((key.strip(), value.strip()))
|
|
24847
|
+
content = dict((k.lower(), v) for k, v in received)
|
|
24848
|
+
if "content-length" in content:
|
|
24849
|
+
data = await reader.readexactly(int(content["content-length"]))
|
|
24850
|
+
else:
|
|
24851
|
+
data = await reader.read()
|
|
24852
|
+
if content.get("x-sbx-error"):
|
|
24853
|
+
raise OSError(data.decode("utf-8", "replace").strip())
|
|
24854
|
+
return int(code), reason, received, data
|
|
24855
|
+
finally:
|
|
24856
|
+
writer.close()
|
|
24857
|
+
|
|
24858
|
+
class _SbxUrllib3Hook:
|
|
24859
|
+
"""urllib3's emscripten backend is Pyodide's \`js\` fetch, which this
|
|
24860
|
+
runtime has no module for; its socket backend cannot leave the
|
|
24861
|
+
container. Both connection classes become egress ones."""
|
|
24862
|
+
|
|
24863
|
+
def find_spec(self, name, path=None, target=None):
|
|
24864
|
+
if name != "urllib3.contrib.emscripten":
|
|
24865
|
+
return None
|
|
24866
|
+
import importlib.util
|
|
24867
|
+
|
|
24868
|
+
def inject_into_urllib3():
|
|
24869
|
+
import urllib3.connection
|
|
24870
|
+
from urllib3.connectionpool import HTTPConnectionPool, HTTPSConnectionPool
|
|
24871
|
+
|
|
24872
|
+
class _Plain(urllib3.connection.HTTPConnection):
|
|
24873
|
+
"""urllib3's own connection, dialled at the egress.
|
|
24874
|
+
|
|
24875
|
+
urllib3.connection.HTTPConnection still derives from
|
|
24876
|
+
http.client.HTTPConnection, so request building is
|
|
24877
|
+
inherited; only where it connects and what goes in the
|
|
24878
|
+
request line change.
|
|
24879
|
+
"""
|
|
24880
|
+
|
|
24881
|
+
_sbx_scheme = "http"
|
|
24882
|
+
default_port = 80
|
|
24883
|
+
is_verified = True
|
|
24884
|
+
proxy_is_verified = None
|
|
24885
|
+
|
|
24886
|
+
def __init__(self, host, port=None, **kwargs):
|
|
24887
|
+
# A distinct name from the http.client shim's
|
|
24888
|
+
# _sbx_origin: that class is further up this MRO and
|
|
24889
|
+
# its __init__ runs after this one, so sharing the name
|
|
24890
|
+
# overwrote the real destination with the egress and
|
|
24891
|
+
# every request went out addressed to the egress itself.
|
|
24892
|
+
self._sbx_target = (host, port or self.default_port)
|
|
24893
|
+
for unsupported in ("ssl_context", "ssl_version", "ssl_minimum_version",
|
|
24894
|
+
"ssl_maximum_version", "assert_hostname",
|
|
24895
|
+
"assert_fingerprint", "server_hostname", "ca_certs",
|
|
24896
|
+
"ca_cert_dir", "ca_cert_data", "cert_file", "key_file",
|
|
24897
|
+
"key_password", "cert_reqs"):
|
|
24898
|
+
kwargs.pop(unsupported, None)
|
|
24899
|
+
super().__init__(_egress_host, port=_egress_port, **kwargs)
|
|
24900
|
+
|
|
24901
|
+
def _sbx_absolute(self, url):
|
|
24902
|
+
host, port = self._sbx_target
|
|
24903
|
+
if url.startswith("http://") or url.startswith("https://"):
|
|
24904
|
+
return url
|
|
24905
|
+
netloc = host if port == self.default_port else f"{host}:{port}"
|
|
24906
|
+
return f"{self._sbx_scheme}://{netloc}{url}"
|
|
24907
|
+
|
|
24908
|
+
# urllib3 v2 writes the request line in its own request()
|
|
24909
|
+
# rather than through http.client's putrequest, so both
|
|
24910
|
+
# are absolutised; the egress refuses an origin-form target.
|
|
24911
|
+
def request(self, method, url, *args, **kwargs):
|
|
24912
|
+
return super().request(method, self._sbx_absolute(url), *args, **kwargs)
|
|
24913
|
+
|
|
24914
|
+
def putrequest(self, method, url, *args, **kwargs):
|
|
24915
|
+
return super().putrequest(method, self._sbx_absolute(url), *args, **kwargs)
|
|
24916
|
+
|
|
24917
|
+
def set_tunnel(self, *args, **kwargs):
|
|
24918
|
+
raise OSError("this container reaches the network through its egress, not CONNECT")
|
|
24919
|
+
|
|
24920
|
+
class _Tls(_Plain):
|
|
24921
|
+
_sbx_scheme = "https"
|
|
24922
|
+
default_port = 443
|
|
24923
|
+
|
|
24924
|
+
HTTPConnectionPool.ConnectionCls = _Plain
|
|
24925
|
+
HTTPSConnectionPool.ConnectionCls = _Tls
|
|
24926
|
+
urllib3.connection.HTTPConnection = _Plain
|
|
24927
|
+
urllib3.connection.HTTPSConnection = _Tls
|
|
24928
|
+
|
|
24929
|
+
class _Loader:
|
|
24930
|
+
def create_module(self, spec):
|
|
24931
|
+
return None
|
|
24932
|
+
|
|
24933
|
+
def exec_module(self, module):
|
|
24934
|
+
module.inject_into_urllib3 = inject_into_urllib3
|
|
24935
|
+
|
|
24936
|
+
return importlib.util.spec_from_loader(name, _Loader(), is_package=True)
|
|
24937
|
+
|
|
24938
|
+
sys.meta_path.insert(0, _SbxUrllib3Hook())
|
|
24939
|
+
|
|
24940
|
+
def _sbx_httpx_transports(httpx_module):
|
|
24941
|
+
"""Sync and async httpx transports that send through the egress."""
|
|
24942
|
+
|
|
24943
|
+
class _Transport(httpx_module.BaseTransport):
|
|
24944
|
+
def __init__(self, *args, **kwargs):
|
|
24945
|
+
pass
|
|
24946
|
+
|
|
24947
|
+
def handle_request(self, request):
|
|
24948
|
+
body = request.read()
|
|
24949
|
+
sent = [(k.decode("latin-1"), v.decode("latin-1")) for k, v in request.headers.raw]
|
|
24950
|
+
status, reason, headers, payload = _sbx_egress_request(
|
|
24951
|
+
request.method, str(request.url), sent, body,
|
|
24952
|
+
)
|
|
24953
|
+
# Content-Length/Transfer-Encoding describe the egress hop's
|
|
24954
|
+
# framing, and httpx re-derives them from the body it is given.
|
|
24955
|
+
keep = [(k, v) for k, v in headers
|
|
24956
|
+
if k.lower() not in ("content-length", "transfer-encoding", "content-encoding")]
|
|
24957
|
+
return httpx_module.Response(
|
|
24958
|
+
status, headers=keep, content=payload, request=request,
|
|
24959
|
+
extensions={"reason_phrase": (reason or "").encode()},
|
|
24960
|
+
)
|
|
24961
|
+
|
|
24962
|
+
def close(self):
|
|
24963
|
+
pass
|
|
24964
|
+
|
|
24965
|
+
def __enter__(self):
|
|
24966
|
+
return self
|
|
24967
|
+
|
|
24968
|
+
def __exit__(self, *args):
|
|
24969
|
+
pass
|
|
24970
|
+
|
|
24971
|
+
class _AsyncTransport(httpx_module.AsyncBaseTransport):
|
|
24972
|
+
def __init__(self, *args, **kwargs):
|
|
24973
|
+
pass
|
|
24974
|
+
|
|
24975
|
+
async def handle_async_request(self, request):
|
|
24976
|
+
# Not the blocking client on the loop thread: this image
|
|
24977
|
+
# proxies every syscall to one thread, so a blocking recv
|
|
24978
|
+
# inside a coroutine stops the loop that must service it. The
|
|
24979
|
+
# egress is loopback, which asyncio streams reach.
|
|
24980
|
+
import asyncio
|
|
24981
|
+
body = await request.aread()
|
|
24982
|
+
sent = [(k.decode("latin-1"), v.decode("latin-1")) for k, v in request.headers.raw]
|
|
24983
|
+
status, reason, headers, payload = await _sbx_egress_request_async(
|
|
24984
|
+
request.method, str(request.url), sent, body,
|
|
24985
|
+
)
|
|
24986
|
+
keep = [(k, v) for k, v in headers
|
|
24987
|
+
if k.lower() not in ("content-length", "transfer-encoding", "content-encoding")]
|
|
24988
|
+
return httpx_module.Response(
|
|
24989
|
+
status, headers=keep, content=payload, request=request,
|
|
24990
|
+
extensions={"reason_phrase": (reason or "").encode()},
|
|
24991
|
+
)
|
|
24992
|
+
|
|
24993
|
+
async def aclose(self):
|
|
24994
|
+
pass
|
|
24995
|
+
|
|
24996
|
+
async def __aenter__(self):
|
|
24997
|
+
return self
|
|
24998
|
+
|
|
24999
|
+
async def __aexit__(self, *args):
|
|
25000
|
+
pass
|
|
25001
|
+
|
|
25002
|
+
return _Transport, _AsyncTransport
|
|
25003
|
+
|
|
25004
|
+
class _SbxHttpxHook:
|
|
25005
|
+
"""httpx and httpx2 build their default transports on httpcore, which
|
|
25006
|
+
opens sockets. Their default transport classes are replaced as the
|
|
25007
|
+
module loads, so \`httpx.get(...)\` and every client built from the
|
|
25008
|
+
defaults leave through the egress."""
|
|
25009
|
+
|
|
25010
|
+
_PACKAGES = {"httpx": "httpx", "httpx2": "httpx2"}
|
|
25011
|
+
|
|
25012
|
+
def find_spec(self, name, path=None, target=None):
|
|
25013
|
+
package = self._PACKAGES.get(name)
|
|
25014
|
+
if package is None or getattr(self, "_loading", None) == name:
|
|
25015
|
+
return None
|
|
25016
|
+
import importlib.machinery
|
|
25017
|
+
spec = importlib.machinery.PathFinder.find_spec(name, path)
|
|
25018
|
+
if spec is None or spec.loader is None:
|
|
25019
|
+
return spec
|
|
25020
|
+
load = spec.loader.exec_module
|
|
25021
|
+
|
|
25022
|
+
def exec_module(module):
|
|
25023
|
+
self._loading = name
|
|
25024
|
+
try:
|
|
25025
|
+
load(module)
|
|
25026
|
+
finally:
|
|
25027
|
+
self._loading = None
|
|
25028
|
+
transport, async_transport = _sbx_httpx_transports(module)
|
|
25029
|
+
module.HTTPTransport = transport
|
|
25030
|
+
module.AsyncHTTPTransport = async_transport
|
|
25031
|
+
defaults = sys.modules.get(f"{package}._transports.default")
|
|
25032
|
+
if defaults is not None:
|
|
25033
|
+
defaults.HTTPTransport = transport
|
|
25034
|
+
defaults.AsyncHTTPTransport = async_transport
|
|
25035
|
+
client = sys.modules.get(f"{package}._client")
|
|
25036
|
+
if client is not None:
|
|
25037
|
+
client.HTTPTransport = transport
|
|
25038
|
+
client.AsyncHTTPTransport = async_transport
|
|
25039
|
+
|
|
25040
|
+
spec.loader.exec_module = exec_module
|
|
25041
|
+
return spec
|
|
25042
|
+
|
|
25043
|
+
sys.meta_path.insert(0, _SbxHttpxHook())
|
|
25044
|
+
|
|
24053
25045
|
|
|
24054
25046
|
if os.environ.get("SBX_SERIAL_HOST_CALLS") == "1":
|
|
24055
25047
|
import asyncio
|
|
@@ -24078,10 +25070,127 @@ if os.environ.get("SBX_SERIAL_HOST_CALLS") == "1":
|
|
|
24078
25070
|
)
|
|
24079
25071
|
|
|
24080
25072
|
asyncio.to_thread = _to_thread
|
|
25073
|
+
|
|
25074
|
+
class _SbxAnyioThreadHook:
|
|
25075
|
+
"""anyio's thread offload, run inline.
|
|
25076
|
+
|
|
25077
|
+
anyio has its own worker threads, so it never reaches asyncio.to_thread
|
|
25078
|
+
and the refusal above -- it deadlocks instead, for the reason described
|
|
25079
|
+
there. Starlette sends every *synchronous* endpoint through
|
|
25080
|
+
anyio.to_thread.run_sync, so a plain def FastAPI route hung forever
|
|
25081
|
+
while an async def one answered.
|
|
25082
|
+
|
|
25083
|
+
Running the function on the loop thread keeps such an endpoint working.
|
|
25084
|
+
The cost is real and worth stating: a synchronous handler blocks the
|
|
25085
|
+
loop for its duration, so it does not overlap with other requests.
|
|
25086
|
+
"""
|
|
25087
|
+
|
|
25088
|
+
def find_spec(self, name, path=None, target=None):
|
|
25089
|
+
if name != "anyio.to_thread":
|
|
25090
|
+
return None
|
|
25091
|
+
import importlib.machinery
|
|
25092
|
+
spec = importlib.machinery.PathFinder.find_spec(name, path)
|
|
25093
|
+
if spec is None or spec.loader is None:
|
|
25094
|
+
return spec
|
|
25095
|
+
load = spec.loader.exec_module
|
|
25096
|
+
|
|
25097
|
+
def exec_module(module):
|
|
25098
|
+
load(module)
|
|
25099
|
+
|
|
25100
|
+
async def run_sync(func, *args, abandon_on_cancel=False,
|
|
25101
|
+
cancellable=None, limiter=None):
|
|
25102
|
+
return func(*args)
|
|
25103
|
+
|
|
25104
|
+
async def run_sync_in_worker_thread(func, *args, **kwargs):
|
|
25105
|
+
return func(*args)
|
|
25106
|
+
|
|
25107
|
+
module.run_sync = run_sync
|
|
25108
|
+
module.run_sync_in_worker_thread = run_sync_in_worker_thread
|
|
25109
|
+
|
|
25110
|
+
spec.loader.exec_module = exec_module
|
|
25111
|
+
return spec
|
|
25112
|
+
|
|
25113
|
+
sys.meta_path.insert(0, _SbxAnyioThreadHook())
|
|
25114
|
+
|
|
25115
|
+
class _SbxLoopResolverHook:
|
|
25116
|
+
"""asyncio resolves names on a worker thread, which cannot run here.
|
|
25117
|
+
|
|
25118
|
+
loop.getaddrinfo hands the call to a ThreadPoolExecutor and awaits
|
|
25119
|
+
it -- the deadlock described above -- so every asyncio client that
|
|
25120
|
+
connects by name hung: aiohttp never got past its first request. In
|
|
25121
|
+
this container resolution is a host call that answers immediately, so
|
|
25122
|
+
it is done on the loop thread instead of being handed away."""
|
|
25123
|
+
|
|
25124
|
+
def find_spec(self, name, path=None, target=None):
|
|
25125
|
+
if name != "asyncio.base_events":
|
|
25126
|
+
return None
|
|
25127
|
+
import importlib.machinery
|
|
25128
|
+
spec = importlib.machinery.PathFinder.find_spec(name, path)
|
|
25129
|
+
if spec is None or spec.loader is None:
|
|
25130
|
+
return spec
|
|
25131
|
+
load = spec.loader.exec_module
|
|
25132
|
+
|
|
25133
|
+
def exec_module(module):
|
|
25134
|
+
load(module)
|
|
25135
|
+
import socket as _socket
|
|
25136
|
+
|
|
25137
|
+
async def getaddrinfo(self, host, port, *, family=0, type=0, proto=0, flags=0):
|
|
25138
|
+
return _socket.getaddrinfo(host, port, family, type, proto, flags)
|
|
25139
|
+
|
|
25140
|
+
async def getnameinfo(self, sockaddr, flags=0):
|
|
25141
|
+
return _socket.getnameinfo(sockaddr, flags)
|
|
25142
|
+
|
|
25143
|
+
module.BaseEventLoop.getaddrinfo = getaddrinfo
|
|
25144
|
+
module.BaseEventLoop.getnameinfo = getnameinfo
|
|
25145
|
+
|
|
25146
|
+
spec.loader.exec_module = exec_module
|
|
25147
|
+
return spec
|
|
25148
|
+
|
|
25149
|
+
sys.meta_path.insert(0, _SbxLoopResolverHook())
|
|
25150
|
+
|
|
25151
|
+
def _sbx_patch_loop_resolver(module):
|
|
25152
|
+
"""Also patch the module if it is already imported.
|
|
25153
|
+
|
|
25154
|
+
This file imports asyncio itself, a few lines above, so by the time the
|
|
25155
|
+
hook is installed asyncio.base_events is usually already in
|
|
25156
|
+
sys.modules and the hook would never see it -- which is why aiohttp
|
|
25157
|
+
still hung after the hook existed."""
|
|
25158
|
+
import socket as _socket
|
|
25159
|
+
|
|
25160
|
+
async def getaddrinfo(self, host, port, *, family=0, type=0, proto=0, flags=0):
|
|
25161
|
+
return _socket.getaddrinfo(host, port, family, type, proto, flags)
|
|
25162
|
+
|
|
25163
|
+
async def getnameinfo(self, sockaddr, flags=0):
|
|
25164
|
+
return _socket.getnameinfo(sockaddr, flags)
|
|
25165
|
+
|
|
25166
|
+
module.BaseEventLoop.getaddrinfo = getaddrinfo
|
|
25167
|
+
module.BaseEventLoop.getnameinfo = getnameinfo
|
|
25168
|
+
|
|
25169
|
+
_loaded_base_events = sys.modules.get("asyncio.base_events")
|
|
25170
|
+
if _loaded_base_events is not None:
|
|
25171
|
+
_sbx_patch_loop_resolver(_loaded_base_events)
|
|
24081
25172
|
`;
|
|
25173
|
+
var CA_BUNDLE_PATH = "/etc/ssl/certs/ca-certificates.crt";
|
|
25174
|
+
async function ensureTrustStore(ctx) {
|
|
25175
|
+
try {
|
|
25176
|
+
if (ctx.vfs.stat(CA_BUNDLE_PATH, { cred: ctx.cred }).size > 0) return;
|
|
25177
|
+
} catch {
|
|
25178
|
+
}
|
|
25179
|
+
try {
|
|
25180
|
+
const tls = await nodeBuiltin("tls");
|
|
25181
|
+
const bundle = tls.rootCertificates.join("\n");
|
|
25182
|
+
if (!bundle) return;
|
|
25183
|
+
ctx.vfs.mkdir("/etc/ssl/certs", { cred: ctx.cred, recursive: true });
|
|
25184
|
+
ctx.vfs.writeFile(CA_BUNDLE_PATH, bundle, { privileged: true, mode: 420 });
|
|
25185
|
+
} catch {
|
|
25186
|
+
}
|
|
25187
|
+
}
|
|
24082
25188
|
function ensureProcessShims(ctx) {
|
|
24083
|
-
|
|
24084
|
-
|
|
25189
|
+
writeShim(ctx, `${SITE_PACKAGES}/sitecustomize.py`, SHIM_SOURCE);
|
|
25190
|
+
writeShim(ctx, `${SITE_PACKAGES}/_sbx_tls.py`, TLS_MODULE_SOURCE);
|
|
25191
|
+
}
|
|
25192
|
+
function writeShim(ctx, hook, body) {
|
|
25193
|
+
const source = `${SHIM_HEADER}${body}`;
|
|
24085
25194
|
try {
|
|
24086
25195
|
const existing = ctx.vfs.readText(hook, ctx.cred);
|
|
24087
25196
|
if (existing === source || !existing.startsWith(SHIM_HEADER)) return;
|
|
@@ -24128,6 +25237,7 @@ function containerMounts(ctx) {
|
|
|
24128
25237
|
|
|
24129
25238
|
// src/python/pip-command.ts
|
|
24130
25239
|
init_binary();
|
|
25240
|
+
var RESOLVE_DEADLINE_MS = 15 * 6e4;
|
|
24131
25241
|
async function requestRemoteBuild(service, requirement, line) {
|
|
24132
25242
|
line(`Asking ${new URL(service).host} to build ${requirement}`);
|
|
24133
25243
|
try {
|
|
@@ -24330,6 +25440,14 @@ environments. A package with no usable wheel is an error, never a silent skip.`,
|
|
|
24330
25440
|
vfs: ctx.vfs,
|
|
24331
25441
|
cred: ctx.cred,
|
|
24332
25442
|
requirements,
|
|
25443
|
+
/* The resolver's own default is three minutes, which is a fine bound
|
|
25444
|
+
* for a library but far too tight for a real requirements file: a
|
|
25445
|
+
* `langchain` plus `deepagents` graph is hundreds of distributions,
|
|
25446
|
+
* each needing a metadata fetch, and the install died mid-resolution
|
|
25447
|
+
* with "exceeded its 180s budget" while still making progress. `pip`
|
|
25448
|
+
* already has a 20-minute ceiling on the whole command; this leaves
|
|
25449
|
+
* room for the download phase inside it. */
|
|
25450
|
+
deadlineMs: RESOLVE_DEADLINE_MS,
|
|
24333
25451
|
pythonVersion: pythonBackend().manifest?.pythonVersion ?? "3.13.5",
|
|
24334
25452
|
progress: {
|
|
24335
25453
|
collecting: (name) => ctx.line(`Collecting ${name}`),
|
|
@@ -24482,7 +25600,7 @@ function mountContainerFs(FS, opts) {
|
|
|
24482
25600
|
const throwErrno = (code) => {
|
|
24483
25601
|
throw new FS.ErrnoError(emErrno(code));
|
|
24484
25602
|
};
|
|
24485
|
-
const
|
|
25603
|
+
const translate2 = (e) => {
|
|
24486
25604
|
if (isSysError(e)) return throwErrno(e.code);
|
|
24487
25605
|
throw new FS.ErrnoError(emErrno("EIO"));
|
|
24488
25606
|
};
|
|
@@ -24531,7 +25649,7 @@ function mountContainerFs(FS, opts) {
|
|
|
24531
25649
|
blocks: Math.ceil(st.size / 512)
|
|
24532
25650
|
};
|
|
24533
25651
|
} catch (e) {
|
|
24534
|
-
return
|
|
25652
|
+
return translate2(e);
|
|
24535
25653
|
}
|
|
24536
25654
|
},
|
|
24537
25655
|
setattr(node2, attr) {
|
|
@@ -24547,7 +25665,7 @@ function mountContainerFs(FS, opts) {
|
|
|
24547
25665
|
opts.onWrite?.(path);
|
|
24548
25666
|
}
|
|
24549
25667
|
} catch (e) {
|
|
24550
|
-
|
|
25668
|
+
translate2(e);
|
|
24551
25669
|
}
|
|
24552
25670
|
},
|
|
24553
25671
|
lookup(parent, name) {
|
|
@@ -24572,7 +25690,7 @@ function mountContainerFs(FS, opts) {
|
|
|
24572
25690
|
}
|
|
24573
25691
|
opts.onWrite?.(path);
|
|
24574
25692
|
} catch (e) {
|
|
24575
|
-
|
|
25693
|
+
translate2(e);
|
|
24576
25694
|
}
|
|
24577
25695
|
return backend.createNode(parent, name, mode, dev);
|
|
24578
25696
|
},
|
|
@@ -24583,7 +25701,7 @@ function mountContainerFs(FS, opts) {
|
|
|
24583
25701
|
vfs.rename(from, to, cred);
|
|
24584
25702
|
opts.onWrite?.(to);
|
|
24585
25703
|
} catch (e) {
|
|
24586
|
-
|
|
25704
|
+
translate2(e);
|
|
24587
25705
|
}
|
|
24588
25706
|
oldNode.name = newName;
|
|
24589
25707
|
oldNode.parent = newDir;
|
|
@@ -24594,7 +25712,7 @@ function mountContainerFs(FS, opts) {
|
|
|
24594
25712
|
vfs.unlink(path, cred);
|
|
24595
25713
|
opts.onWrite?.(path);
|
|
24596
25714
|
} catch (e) {
|
|
24597
|
-
|
|
25715
|
+
translate2(e);
|
|
24598
25716
|
}
|
|
24599
25717
|
},
|
|
24600
25718
|
rmdir(parent, name) {
|
|
@@ -24603,7 +25721,7 @@ function mountContainerFs(FS, opts) {
|
|
|
24603
25721
|
vfs.rmdir(path, cred);
|
|
24604
25722
|
opts.onWrite?.(path);
|
|
24605
25723
|
} catch (e) {
|
|
24606
|
-
|
|
25724
|
+
translate2(e);
|
|
24607
25725
|
}
|
|
24608
25726
|
},
|
|
24609
25727
|
readdir(node2) {
|
|
@@ -24611,7 +25729,7 @@ function mountContainerFs(FS, opts) {
|
|
|
24611
25729
|
try {
|
|
24612
25730
|
return [".", "..", ...vfs.readdir(path, cred)];
|
|
24613
25731
|
} catch (e) {
|
|
24614
|
-
return
|
|
25732
|
+
return translate2(e);
|
|
24615
25733
|
}
|
|
24616
25734
|
},
|
|
24617
25735
|
symlink(parent, newName, oldPath) {
|
|
@@ -24620,7 +25738,7 @@ function mountContainerFs(FS, opts) {
|
|
|
24620
25738
|
vfs.symlink(oldPath, path, cred);
|
|
24621
25739
|
opts.onWrite?.(path);
|
|
24622
25740
|
} catch (e) {
|
|
24623
|
-
|
|
25741
|
+
translate2(e);
|
|
24624
25742
|
}
|
|
24625
25743
|
},
|
|
24626
25744
|
readlink(node2) {
|
|
@@ -24628,7 +25746,7 @@ function mountContainerFs(FS, opts) {
|
|
|
24628
25746
|
try {
|
|
24629
25747
|
return vfs.readlink(path, cred);
|
|
24630
25748
|
} catch (e) {
|
|
24631
|
-
return
|
|
25749
|
+
return translate2(e);
|
|
24632
25750
|
}
|
|
24633
25751
|
}
|
|
24634
25752
|
};
|
|
@@ -24640,7 +25758,7 @@ function mountContainerFs(FS, opts) {
|
|
|
24640
25758
|
stream.sbxBuffer = vfs.lexists(path) ? vfs.readFile(path, cred) : new Uint8Array(0);
|
|
24641
25759
|
stream.sbxDirty = false;
|
|
24642
25760
|
} catch (e) {
|
|
24643
|
-
|
|
25761
|
+
translate2(e);
|
|
24644
25762
|
}
|
|
24645
25763
|
},
|
|
24646
25764
|
close(stream) {
|
|
@@ -24650,7 +25768,7 @@ function mountContainerFs(FS, opts) {
|
|
|
24650
25768
|
vfs.writeFile(path, stream.sbxBuffer, { cred });
|
|
24651
25769
|
opts.onWrite?.(path);
|
|
24652
25770
|
} catch (e) {
|
|
24653
|
-
|
|
25771
|
+
translate2(e);
|
|
24654
25772
|
}
|
|
24655
25773
|
stream.sbxDirty = false;
|
|
24656
25774
|
},
|
|
@@ -24676,7 +25794,7 @@ function mountContainerFs(FS, opts) {
|
|
|
24676
25794
|
opts.onWrite?.(path);
|
|
24677
25795
|
stream.sbxDirty = false;
|
|
24678
25796
|
} catch (e) {
|
|
24679
|
-
|
|
25797
|
+
translate2(e);
|
|
24680
25798
|
}
|
|
24681
25799
|
return length;
|
|
24682
25800
|
},
|
|
@@ -34357,6 +35475,212 @@ ${BUNDLER_HINT}`),
|
|
|
34357
35475
|
|
|
34358
35476
|
// src/worker/local-runtime-pod.ts
|
|
34359
35477
|
init_path();
|
|
35478
|
+
|
|
35479
|
+
// src/net/outbound-tcp.ts
|
|
35480
|
+
var FIRST_SYNTHETIC = 2887581696;
|
|
35481
|
+
var SYNTHETIC_COUNT = 65534;
|
|
35482
|
+
function toDotted(value) {
|
|
35483
|
+
return [value >>> 24, value >>> 16 & 255, value >>> 8 & 255, value & 255].join(".");
|
|
35484
|
+
}
|
|
35485
|
+
var OutboundTcp = class {
|
|
35486
|
+
constructor(policy, dialer) {
|
|
35487
|
+
this.policy = policy;
|
|
35488
|
+
this.dialer = dialer;
|
|
35489
|
+
}
|
|
35490
|
+
policy;
|
|
35491
|
+
dialer;
|
|
35492
|
+
byName = /* @__PURE__ */ new Map();
|
|
35493
|
+
byAddress = /* @__PURE__ */ new Map();
|
|
35494
|
+
next = 0;
|
|
35495
|
+
/** Whether this address was handed out by `resolve`. */
|
|
35496
|
+
knows(address) {
|
|
35497
|
+
return this.byAddress.has(address);
|
|
35498
|
+
}
|
|
35499
|
+
hostnameFor(address) {
|
|
35500
|
+
return this.byAddress.get(address);
|
|
35501
|
+
}
|
|
35502
|
+
/**
|
|
35503
|
+
* The address for `hostname`, allocating one on first use.
|
|
35504
|
+
*
|
|
35505
|
+
* Refusal happens here as well as at connect, because a name that cannot be
|
|
35506
|
+
* reached should fail as a resolution failure -- which is what every client
|
|
35507
|
+
* reports as "unknown host" rather than as a mid-connection error.
|
|
35508
|
+
*/
|
|
35509
|
+
resolve(hostname) {
|
|
35510
|
+
const name = hostname.trim().toLowerCase();
|
|
35511
|
+
if (!name) throw new PosixError(Errno.EINVAL, "empty hostname");
|
|
35512
|
+
if (/^\d{1,3}(\.\d{1,3}){3}$/.test(name)) return name;
|
|
35513
|
+
if (isLoopbackHostname(name)) return "127.0.0.1";
|
|
35514
|
+
const known = this.byName.get(name);
|
|
35515
|
+
if (known) return known;
|
|
35516
|
+
if (!this.allowed(name)) {
|
|
35517
|
+
throw new PosixError(
|
|
35518
|
+
Errno.ENETUNREACH,
|
|
35519
|
+
`cannot resolve ${hostname}: outbound network access is not allowed for this container`
|
|
35520
|
+
);
|
|
35521
|
+
}
|
|
35522
|
+
if (this.next >= SYNTHETIC_COUNT) {
|
|
35523
|
+
throw new PosixError(Errno.ENOSPC, "no addresses left for resolved names");
|
|
35524
|
+
}
|
|
35525
|
+
const address = toDotted(FIRST_SYNTHETIC + this.next + 1);
|
|
35526
|
+
this.next += 1;
|
|
35527
|
+
this.byName.set(name, address);
|
|
35528
|
+
this.byAddress.set(address, name);
|
|
35529
|
+
return address;
|
|
35530
|
+
}
|
|
35531
|
+
/** Open a connection to a resolved address, or to a literal one. */
|
|
35532
|
+
async connect(address, port, local) {
|
|
35533
|
+
const hostname = this.byAddress.get(address) ?? address;
|
|
35534
|
+
if (!this.allowed(hostname)) {
|
|
35535
|
+
throw new PosixError(
|
|
35536
|
+
Errno.ENETUNREACH,
|
|
35537
|
+
`cannot reach ${hostname}:${port}: outbound network access is not allowed for this container`
|
|
35538
|
+
);
|
|
35539
|
+
}
|
|
35540
|
+
if (!this.dialer) {
|
|
35541
|
+
throw new PosixError(
|
|
35542
|
+
Errno.ENETUNREACH,
|
|
35543
|
+
`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`
|
|
35544
|
+
);
|
|
35545
|
+
}
|
|
35546
|
+
const host2 = await this.dialer(hostname, port);
|
|
35547
|
+
const guest = new VirtualTcpConnection(
|
|
35548
|
+
{ address: local.address, port: host2.localPort || local.port },
|
|
35549
|
+
{ address, port }
|
|
35550
|
+
);
|
|
35551
|
+
const bridge = new VirtualTcpConnection({ address, port }, { address: local.address, port: local.port });
|
|
35552
|
+
guest.pairWith(bridge);
|
|
35553
|
+
bridge.pairWith(guest);
|
|
35554
|
+
host2.onData((bytes2) => {
|
|
35555
|
+
try {
|
|
35556
|
+
bridge.write(bytes2);
|
|
35557
|
+
} catch {
|
|
35558
|
+
host2.close();
|
|
35559
|
+
}
|
|
35560
|
+
});
|
|
35561
|
+
host2.onClose(() => {
|
|
35562
|
+
bridge.shutdown(false, true);
|
|
35563
|
+
bridge.close();
|
|
35564
|
+
});
|
|
35565
|
+
void (async () => {
|
|
35566
|
+
for (; ; ) {
|
|
35567
|
+
if (bridge.readable) {
|
|
35568
|
+
let chunk;
|
|
35569
|
+
try {
|
|
35570
|
+
chunk = bridge.read(64 * 1024);
|
|
35571
|
+
} catch {
|
|
35572
|
+
break;
|
|
35573
|
+
}
|
|
35574
|
+
if (chunk.length === 0) {
|
|
35575
|
+
host2.end();
|
|
35576
|
+
break;
|
|
35577
|
+
}
|
|
35578
|
+
host2.write(chunk);
|
|
35579
|
+
continue;
|
|
35580
|
+
}
|
|
35581
|
+
await bridge.waitForChange();
|
|
35582
|
+
}
|
|
35583
|
+
})();
|
|
35584
|
+
return { connection: guest, host: host2 };
|
|
35585
|
+
}
|
|
35586
|
+
allowed(hostname) {
|
|
35587
|
+
const policy = this.policy();
|
|
35588
|
+
if (isLoopbackHostname(hostname)) return true;
|
|
35589
|
+
if (!policy.allowOutbound) return false;
|
|
35590
|
+
const hosts = policy.allowedHosts;
|
|
35591
|
+
if (!hosts) return true;
|
|
35592
|
+
return hosts.some((allowed) => hostname === allowed || hostname.endsWith(`.${allowed}`));
|
|
35593
|
+
}
|
|
35594
|
+
};
|
|
35595
|
+
|
|
35596
|
+
// src/net/node-tcp-dialer.ts
|
|
35597
|
+
init_binary();
|
|
35598
|
+
function canDialTcp() {
|
|
35599
|
+
return typeof process !== "undefined" && Boolean(process.versions?.node);
|
|
35600
|
+
}
|
|
35601
|
+
function createNodeTcpDialer() {
|
|
35602
|
+
return async (host2, port) => {
|
|
35603
|
+
const net = await nodeBuiltin("net");
|
|
35604
|
+
return await new Promise((resolve3, reject) => {
|
|
35605
|
+
const socket = net.connect({ host: host2, port });
|
|
35606
|
+
const pending = [];
|
|
35607
|
+
let onData = null;
|
|
35608
|
+
let onClose = null;
|
|
35609
|
+
let closed = false;
|
|
35610
|
+
socket.on("data", (chunk) => {
|
|
35611
|
+
const bytes2 = new Uint8Array(chunk);
|
|
35612
|
+
if (onData) onData(bytes2);
|
|
35613
|
+
else pending.push(bytes2);
|
|
35614
|
+
});
|
|
35615
|
+
const finish = () => {
|
|
35616
|
+
if (closed) return;
|
|
35617
|
+
closed = true;
|
|
35618
|
+
onClose?.();
|
|
35619
|
+
};
|
|
35620
|
+
socket.on("close", finish);
|
|
35621
|
+
socket.on("end", finish);
|
|
35622
|
+
socket.on("error", (error) => {
|
|
35623
|
+
if (!closed) {
|
|
35624
|
+
closed = true;
|
|
35625
|
+
onClose?.();
|
|
35626
|
+
}
|
|
35627
|
+
reject(translate(error, host2, port));
|
|
35628
|
+
});
|
|
35629
|
+
socket.on("connect", () => {
|
|
35630
|
+
socket.setNoDelay(true);
|
|
35631
|
+
resolve3({
|
|
35632
|
+
write(bytes2) {
|
|
35633
|
+
socket.write(bytes2);
|
|
35634
|
+
},
|
|
35635
|
+
end() {
|
|
35636
|
+
socket.end();
|
|
35637
|
+
},
|
|
35638
|
+
close() {
|
|
35639
|
+
socket.destroy();
|
|
35640
|
+
},
|
|
35641
|
+
onData(handler) {
|
|
35642
|
+
onData = handler;
|
|
35643
|
+
for (const chunk of pending.splice(0)) handler(chunk);
|
|
35644
|
+
},
|
|
35645
|
+
onClose(handler) {
|
|
35646
|
+
onClose = handler;
|
|
35647
|
+
if (closed) handler();
|
|
35648
|
+
},
|
|
35649
|
+
get remoteAddress() {
|
|
35650
|
+
return socket.remoteAddress ?? host2;
|
|
35651
|
+
},
|
|
35652
|
+
get remotePort() {
|
|
35653
|
+
return socket.remotePort ?? port;
|
|
35654
|
+
},
|
|
35655
|
+
get localPort() {
|
|
35656
|
+
return socket.localPort ?? 0;
|
|
35657
|
+
}
|
|
35658
|
+
});
|
|
35659
|
+
});
|
|
35660
|
+
});
|
|
35661
|
+
};
|
|
35662
|
+
}
|
|
35663
|
+
function translate(error, host2, port) {
|
|
35664
|
+
const where = `${host2}:${port}`;
|
|
35665
|
+
switch (error.code) {
|
|
35666
|
+
case "ECONNREFUSED":
|
|
35667
|
+
return new PosixError(Errno.ECONNREFUSED, `connection refused by ${where}`);
|
|
35668
|
+
case "ENOTFOUND":
|
|
35669
|
+
case "EAI_AGAIN":
|
|
35670
|
+
return new PosixError(Errno.ENETUNREACH, `cannot resolve ${host2}`);
|
|
35671
|
+
case "ETIMEDOUT":
|
|
35672
|
+
return new PosixError(Errno.ETIMEDOUT, `connection to ${where} timed out`);
|
|
35673
|
+
case "ECONNRESET":
|
|
35674
|
+
return new PosixError(Errno.ECONNRESET, `connection to ${where} was reset`);
|
|
35675
|
+
case "EHOSTUNREACH":
|
|
35676
|
+
case "ENETUNREACH":
|
|
35677
|
+
return new PosixError(Errno.ENETUNREACH, `cannot reach ${where}`);
|
|
35678
|
+
default:
|
|
35679
|
+
return new PosixError(Errno.ECONNREFUSED, `connection to ${where} failed: ${error.message}`);
|
|
35680
|
+
}
|
|
35681
|
+
}
|
|
35682
|
+
|
|
35683
|
+
// src/worker/local-runtime-pod.ts
|
|
34360
35684
|
var WASM_ALIASES = {
|
|
34361
35685
|
esbuild: "esbuild-wasm",
|
|
34362
35686
|
rollup: "@rollup/wasm-node"
|
|
@@ -34712,6 +36036,32 @@ var LocalRuntimePod = class _LocalRuntimePod {
|
|
|
34712
36036
|
constructor(options) {
|
|
34713
36037
|
ensureProcessGlobal();
|
|
34714
36038
|
this.sockets = new VirtualTcpNetwork((port) => this.router.activePortsIncludes(port));
|
|
36039
|
+
this.sockets.loopbackHttp = (port, connection) => {
|
|
36040
|
+
if (!this.router.activePortsIncludes(port)) return false;
|
|
36041
|
+
void serveHttpOverConnection(connection, async (request) => {
|
|
36042
|
+
const url = new URL(request.target, `http://127.0.0.1:${port}`);
|
|
36043
|
+
const answer = await this.router.request(port, {
|
|
36044
|
+
method: request.method,
|
|
36045
|
+
path: `${url.pathname}${url.search}`,
|
|
36046
|
+
headers: request.headers,
|
|
36047
|
+
...request.body.length > 0 ? { body: request.body } : {}
|
|
36048
|
+
});
|
|
36049
|
+
const payload = answer.body ?? new Uint8Array();
|
|
36050
|
+
return {
|
|
36051
|
+
status: Number(answer.statusCode ?? 200),
|
|
36052
|
+
statusText: String(answer.statusMessage ?? ""),
|
|
36053
|
+
headers: answer.headers ?? {},
|
|
36054
|
+
body: typeof payload === "string" ? new TextEncoder().encode(payload) : payload instanceof Uint8Array ? payload : new Uint8Array(payload)
|
|
36055
|
+
};
|
|
36056
|
+
});
|
|
36057
|
+
return true;
|
|
36058
|
+
};
|
|
36059
|
+
if (canDialTcp()) {
|
|
36060
|
+
this.sockets.outbound = new OutboundTcp(
|
|
36061
|
+
() => this.networkPolicy ?? { allowOutbound: false, allowedHosts: null },
|
|
36062
|
+
createNodeTcpDialer()
|
|
36063
|
+
);
|
|
36064
|
+
}
|
|
34715
36065
|
this.router = new VirtualHttpRouter(this.sockets);
|
|
34716
36066
|
this.workdir = options.workdir ?? "/";
|
|
34717
36067
|
this.env = { ...options.env };
|