Skip to content

Commit

Permalink
Merge pull request #5 from golemcloud/fetch-rework
Browse files Browse the repository at this point in the history
Update from upstream - Fetch reworks
  • Loading branch information
noise64 authored Aug 1, 2024
2 parents 9fb33d5 + 228a6d9 commit 97ba944
Show file tree
Hide file tree
Showing 262 changed files with 37,924 additions and 30,216 deletions.
42 changes: 37 additions & 5 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,11 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
with:
submodules: recursive

- name: Install Rust 1.68.2
- name: Install Rust 1.77.1
run: |
rustup toolchain install 1.68.2
rustup target add wasm32-wasi --toolchain 1.68.2
rustup toolchain install 1.77.1
rustup target add wasm32-wasi --toolchain 1.77.1
- uses: actions/setup-node@v2
with:
Expand All @@ -45,3 +43,37 @@ jobs:
- name: StarlingMonkey E2E & Integration Tests
run: |
CTEST_OUTPUT_ON_FAILURE=1 ctest --test-dir cmake-build-debug -j4
wpt:
name: Web Platform Tests
strategy:
matrix:
build: [release]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: recursive

- name: Install Rust 1.77.1
run: |
rustup toolchain install 1.77.1
rustup target add wasm32-wasi --toolchain 1.77.1
- uses: actions/setup-node@v2
with:
node-version: 'lts/*'

- name: Build StarlingMonkey WPT
run: |
cmake -S . -B cmake-build-${{ matrix.build }} -DENABLE_WPT:BOOL=ON -DCMAKE_BUILD_TYPE=${{ matrix.build == 'release' && 'Release' || 'Debug' }}
cmake --build cmake-build-${{ matrix.build }} --parallel 4 --target wpt-runtime
- name: Prepare WPT hosts
run: |
cat deps/wpt-hosts | sudo tee -a /etc/hosts
- name: StarlingMonkey WPT Test
env:
CTEST_OUTPUT_ON_FAILURE: 1
run: ctest -R wpt --test-dir cmake-build-${{ matrix.build }} --verbose
Empty file added .gitmodules
Empty file.
34 changes: 23 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,33 +73,41 @@ cd cmake-build-release
./componentize.sh ../tests/smoke.js
```

## Web Platform Tests

## Thorough testing with the Web Platform Tests suite
To run the [Web Platform Tests](https://web-platform-tests.org/) suite, the WPT runner requires `Node.js` to be installed, and during build configuration the option `ENABLE_WPT:BOOL=ON` must be set.

StarlingMonkey includes a test runner for the [Web Platform Tests](https://web-platform-tests.org/) suite. The test runner is built as part of the `starling.wasm` runtime, and can be run using the `wpt-test` target.
```bash
cmake -S . -B cmake-build-debug -DENABLE_WPT:BOOL=ON -DCMAKE_BUILD_TYPE=Debug
cmake --build cmake-build-debug --parallel 8 --target wpt-runtime
cd cmake-build-debug
ctest -R wpt --verbose # Note: some of the tests run fairly slowly in debug builds, so be patient
```

The Web Platform Tests checkout can also be customized by setting the `WPT_ROOT=[path to your WPT checkout]` environment variable to the cmake command.

### Requirements
WPT tests can be filtered with the `WPT_FILTER=string` variable, for example:

The WPT runner requires `Node.js` to be installed, and during build configuration the option `ENABLE_WPT:BOOL=ON` must be set.
```bash
WPT_FILTER=fetch ctest -R wpt -v
```

When running the test, `WPT_ROOT` must be set to the path of a checkout of the WPT suite at revision `1014eae5e66f8f334610d5a1521756f7a2fb769f`:
Custom flags can also be passed to the test runner via `WPT_FLAGS="..."`, for example to update expectations use:

```bash
WPT_ROOT=[path to your WPT checkout] cmake -S . -B cmake-build-debug -DENABLE_WPT:BOOL=ON -DCMAKE_BUILD_TYPE=Debug
cmake --build cmake-build-debug --parallel 8 --target wpt-runtime
cd cmake-build-debug
ctest --verbose # Note: some of the tests run fairly slowly in debug builds, so be patient
WPT_FLAGS="--update-expectations" ctest -R wpt -v
```

## Configuring available builtins

StarlingMonkey supports enabling/disabling bundled builtins using CMake options. You can get a full list of bundled builtins by running the following shell command:

```shell
cmake -P [PATH_TO_STARLING_MONKEY]/cmake/builtins.cmake
```

Note that it's required to include builtins defining all exports defined by the used host API. Using the default WASI 0.2.0 host API, that means including the `fetch_event` builtin.


## Using StarlingMonkey as a CMake sub-project

StarlingMonkey can be used as a subproject in a larger CMake project.
Expand Down Expand Up @@ -145,6 +153,10 @@ If your builtin requires multiple `.cpp` files, you can pass all of them to `add

### Providing a custom host API implementation

The [host-apis](host-apis) directory contains implementations of the host API for different versions of WASI. Those can be selected by setting the `HOST_API` environment variable to the name of one of the directories. By default, the [wasi-0.2.0](host-apis/wasi-0.2.0) host API is used.
The [host-apis](host-apis) directory can contain implementations of the host API for different
versions of WASI—or in theory any other host interface. Those can be selected by setting the
`HOST_API` environment variable to the
name of one of the directories. Currently, only an implementation in terms of [wasi-0.2.0]
(host-apis/wasi-0.2.0) is provided, and used by default.

To provide a custom host API implementation, you can set `HOST_API` to the (absolute) path of a directory containing that implementation.
62 changes: 30 additions & 32 deletions builtins/web/base64.cpp
Original file line number Diff line number Diff line change
@@ -1,65 +1,66 @@
#include "base64.h"
#include "mozilla/Try.h"
#include "builtin.h"

DEF_ERR(InvalidCharacterError, JSEXN_RANGEERR, "String contains an invalid character", 0)

namespace builtins {
namespace web {
namespace base64 {

JS::Result<std::string> convertJSValueToByteString(JSContext *cx, JS::Handle<JS::Value> v) {
JS::Result<std::string> valueToJSByteString(JSContext *cx, JS::Handle<JS::Value> v) {
JS::RootedString s(cx);
if (v.isString()) {
s = v.toString();
} else {
s = JS::ToString(cx, v);
if (!s) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_CHARACTER_ERROR);
api::throw_error(cx, InvalidCharacterError);
return JS::Result<std::string>(JS::Error());
}
}

// Conversion from JavaScript string to ByteString is only valid if all
// characters < 256. This is always the case for Latin1 strings.
size_t length;
UniqueChars result = nullptr;
if (!JS::StringHasLatin1Chars(s)) {
// Creating an exception can GC, so we first scan the string for bad chars
// and report the error outside the AutoCheckCannotGC scope.
bool foundBadChar = false;
{
JS::AutoCheckCannotGC nogc;
const char16_t *chars = JS_GetTwoByteStringCharsAndLength(cx, nogc, s, &length);
if (!chars) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_CHARACTER_ERROR);
return JS::Result<std::string>(JS::Error());
}

for (size_t i = 0; i < length; i++) {
if (chars[i] > 255) {
foundBadChar = true;
break;
}
}
JS::AutoCheckCannotGC nogc(cx);
const char16_t *chars = JS_GetTwoByteStringCharsAndLength(cx, nogc, s, &length);
if (!chars) {
// Reset the nogc guard, since otherwise we can't throw errors.
nogc.reset();
api::throw_error(cx, InvalidCharacterError);
return JS::Result<std::string>(JS::Error());
}

if (foundBadChar) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_CHARACTER_ERROR);
return JS::Result<std::string>(JS::Error());
for (size_t i = 0; i < length; i++) {
if (chars[i] > 255) {
// Reset the nogc guard, since otherwise we can't throw errors.
nogc.reset();
api::throw_error(cx, InvalidCharacterError);
return JS::Result<std::string>(JS::Error());
}
}
auto latin1_z =
JS::LossyTwoByteCharsToNewLatin1CharsZ(cx, chars, length);
result = UniqueChars(reinterpret_cast<char*>(latin1_z.get()));
} else {
length = JS::GetStringLength(s);
result = JS_EncodeStringToLatin1(cx, s);
}

UniqueChars result = JS_EncodeStringToLatin1(cx, s);
if (!result) {
return JS::Result<std::string>(JS::Error());
}
std::string byteString(result.get(), length);
return byteString;
}

JS::Result<std::string> convertJSValueToByteString(JSContext *cx, std::string v) {
JS::Result<std::string> stringToJSByteString(JSContext *cx, std::string v) {
JS::RootedValue s(cx);
s.setString(JS_NewStringCopyN(cx, v.c_str(), v.length()));
return convertJSValueToByteString(cx, s);
return valueToJSByteString(cx, s);
}

// Maps an encoded character to a value in the Base64 alphabet, per
Expand Down Expand Up @@ -297,7 +298,7 @@ bool atob(JSContext *cx, unsigned argc, Value *vp) {
if (!args.requireAtLeast(cx, "atob", 1)) {
return false;
}
auto dataResult = convertJSValueToByteString(cx, args.get(0));
auto dataResult = valueToJSByteString(cx, args.get(0));
if (dataResult.isErr()) {
return false;
}
Expand All @@ -309,8 +310,7 @@ bool atob(JSContext *cx, unsigned argc, Value *vp) {
// 2. If decodedData is failure, then throw an "InvalidCharacterError"
// DOMException.
if (decoded_result.isErr()) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_CHARACTER_ERROR);
return false;
return api::throw_error(cx, InvalidCharacterError);
}
auto decoded = decoded_result.unwrap();
RootedString decodedData(cx, JS_NewStringCopyN(cx, decoded.c_str(), decoded.length()));
Expand Down Expand Up @@ -407,7 +407,7 @@ bool btoa(JSContext *cx, unsigned argc, Value *vp) {
// Note: We do not check if data contains any character whose code point is
// greater than U+00FF before calling convertJSValueToByteString as
// convertJSValueToByteString does the same check
auto byteStringResult = convertJSValueToByteString(cx, data);
auto byteStringResult = valueToJSByteString(cx, data);
if (byteStringResult.isErr()) {
return false;
}
Expand All @@ -417,9 +417,7 @@ bool btoa(JSContext *cx, unsigned argc, Value *vp) {

JSString *str = JS_NewStringCopyN(cx, result.c_str(), result.length());
if (!str) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_INVALID_CHARACTER_ERROR);

return false;
return api::throw_error(cx, InvalidCharacterError);
}

out.setString(str);
Expand Down
3 changes: 2 additions & 1 deletion builtins/web/base64.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ extern const char base64URLEncodeTable[65];
std::string forgivingBase64Encode(std::string_view data, const char *encodeTable);
JS::Result<std::string> forgivingBase64Decode(std::string_view data, const uint8_t *decodeTable);

JS::Result<std::string> convertJSValueToByteString(JSContext *cx, std::string v);
JS::Result<std::string> valueToJSByteString(JSContext *cx, HandleValue v);
JS::Result<std::string> stringToJSByteString(JSContext *cx, std::string v);

} // namespace base64
} // namespace web
Expand Down
43 changes: 20 additions & 23 deletions builtins/web/crypto/crypto-algorithm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "../base64.h"
#include "../dom-exception.h"
#include "crypto-algorithm.h"

#include "crypto-key-ec-components.h"
#include "crypto-key-rsa-components.h"
#include "encode.h"
Expand Down Expand Up @@ -166,7 +167,7 @@ std::unique_ptr<CryptoKeyRSAComponents> createRSAPublicKeyFromJWK(JSContext *cx,
if (modulus.starts_with('0')) {
modulus = modulus.erase(0, 1);
}
auto dataResult = base64::convertJSValueToByteString(cx, jwk->e.value());
auto dataResult = base64::stringToJSByteString(cx, jwk->e.value());
if (dataResult.isErr()) {
DOMException::raise(cx, "Data provided to an operation does not meet requirements",
"DataError");
Expand Down Expand Up @@ -258,7 +259,7 @@ std::unique_ptr<CryptoKeyRSAComponents> createRSAPrivateKeyFromJWK(JSContext *cx
if (modulus.starts_with('0')) {
modulus = modulus.erase(0, 1);
}
auto dataResult = base64::convertJSValueToByteString(cx, jwk->e.value());
auto dataResult = base64::stringToJSByteString(cx, jwk->e.value());
if (dataResult.isErr()) {
DOMException::raise(cx, "Data provided to an operation does not meet requirements",
"DataError");
Expand Down Expand Up @@ -895,12 +896,10 @@ JSObject *CryptoAlgorithmECDSA_Sign_Verify::sign(JSContext *cx, JS::HandleObject
// 7. Return the result of creating an ArrayBuffer containing result.
JS::RootedObject buffer(cx, JS::NewArrayBufferWithContents(cx, resultSize, result.get(), JS::NewArrayBufferOutOfMemory::CallerMustFreeMemory));
if (!buffer) {
// We can be here is the array buffer was too large -- if that was the case then a
// JSMSG_BAD_ARRAY_LENGTH will have been created. No other failure scenarios in this path will
// create a JS exception and so we need to create one.
// We can be here if the array buffer was too large -- if that was the case then a
// JSMSG_BAD_ARRAY_LENGTH will have been created. Otherwise we're probably out of memory.
if (!JS_IsExceptionPending(cx)) {
// TODO Rename error to InternalError
JS_ReportErrorLatin1(cx, "InternalError");
js::ReportOutOfMemory(cx);
}
return nullptr;
}
Expand Down Expand Up @@ -1049,12 +1048,10 @@ JSObject *CryptoAlgorithmRSASSA_PKCS1_v1_5_Sign_Verify::sign(JSContext *cx, JS::
// containing the bytes of signature.
JS::RootedObject buffer(cx, JS::NewArrayBufferWithContents(cx, signature_length, signature.get(), JS::NewArrayBufferOutOfMemory::CallerMustFreeMemory));
if (!buffer) {
// We can be here is the array buffer was too large -- if that was the case then a
// JSMSG_BAD_ARRAY_LENGTH will have been created. No other failure scenarios in this path will
// create a JS exception and so we need to create one.
// We can be here if the array buffer was too large -- if that was the case then a
// JSMSG_BAD_ARRAY_LENGTH will have been created. Otherwise we're probably out of memory.
if (!JS_IsExceptionPending(cx)) {
// TODO Rename error to InternalError
JS_ReportErrorLatin1(cx, "InternalError");
js::ReportOutOfMemory(cx);
}
return nullptr;
}
Expand Down Expand Up @@ -1201,8 +1198,8 @@ JSObject *CryptoAlgorithmHMAC_Import::importKey(JSContext *cx, CryptoKeyFormat f

// 2. If usages contains an entry which is not "sign" or "verify", then throw a SyntaxError.
if (!usages.canOnlySignOrVerify()) {
// TODO Rename error to SyntaxError
JS_ReportErrorLatin1(cx, "HMAC keys only support 'sign' and 'verify' operations");
DOMException::raise(cx, "HMAC keys only support 'sign' and 'verify' operations",
"SyntaxError");
return nullptr;
}

Expand Down Expand Up @@ -1231,7 +1228,7 @@ JSObject *CryptoAlgorithmHMAC_Import::importKey(JSContext *cx, CryptoKeyFormat f
// 6.3 Throw a DataError.
auto jwk = std::get<JsonWebKey *>(keyData);
if (!jwk) {
JS_ReportErrorLatin1(cx, "Supplied format is not a JSONWebKey");
DOMException::raise(cx, "Supplied keyData is not a JSONWebKey", "DataError");
return nullptr;
}
// 6.4 If the kty field of jwk is not "oct", then throw a DataError.
Expand Down Expand Up @@ -1451,7 +1448,7 @@ JSObject *CryptoAlgorithmECDSA_Import::importKey(JSContext *cx, CryptoKeyFormat
// Throw a DataError.
auto jwk = std::get<JsonWebKey *>(keyData);
if (!jwk) {
JS_ReportErrorLatin1(cx, "Supplied format is not a JSONWebKey");
DOMException::raise(cx, "Supplied keyData is not a JSONWebKey", "DataError");
return nullptr;
}
// 2.2. If the "d" field is present and usages contains a value which is not "sign",
Expand Down Expand Up @@ -1709,7 +1706,7 @@ JSObject *CryptoAlgorithmRSASSA_PKCS1_v1_5_Import::importKey(JSContext *cx, Cryp
// Throw a DataError.
auto jwk = std::get<JsonWebKey *>(keyData);
if (!jwk) {
JS_ReportErrorLatin1(cx, "Supplied format is not a JSONWebKey");
DOMException::raise(cx, "Supplied keyData is not a JSONWebKey", "DataError");
return nullptr;
}

Expand All @@ -1726,10 +1723,9 @@ JSObject *CryptoAlgorithmRSASSA_PKCS1_v1_5_Import::importKey(JSContext *cx, Cryp
isUsagesAllowed = usages.canOnlyVerify();
}
if (!isUsagesAllowed) {
// TODO Rename error to SyntaxError
JS_ReportErrorLatin1(cx,
"The JWK 'key_ops' member was inconsistent with that specified by the "
"Web Crypto call. The JWK usage must be a superset of those requested");
DOMException::raise(cx,
"The JWK 'key_ops' member was inconsistent with that specified by the "
"Web Crypto call. The JWK usage must be a superset of those requested", "DataError");
return nullptr;
}

Expand Down Expand Up @@ -1805,8 +1801,9 @@ JSObject *CryptoAlgorithmRSASSA_PKCS1_v1_5_Import::importKey(JSContext *cx, Cryp
}
}
if (!isMatched) {
JS_ReportErrorLatin1(
cx, "The JWK 'alg' member was inconsistent with that specified by the Web Crypto call");
DOMException::raise(cx,
"The JWK 'alg' member was inconsistent with that specified by the Web Crypto call",
"DataError");
return nullptr;
}

Expand Down
Loading

0 comments on commit 97ba944

Please sign in to comment.