feat: publish sanitized custom.97 source
This commit is contained in:
1022 files changed
+69903
-19207
No files matched your search
@@ -3,9 +3,12 @@ package jsplugin
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
@@ -14,6 +17,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
"unsafe"
|
||||
"weak"
|
||||
|
||||
"github.com/QuantumNous/new-api/common"
|
||||
"github.com/QuantumNous/new-api/relaykit/relayconvert/kitutil"
|
||||
@@ -107,6 +112,72 @@ export function invalid(kind) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEngineRawJSONArgumentsMatchDecodedArguments(t *testing.T) {
|
||||
engine, err := Compile(`
|
||||
export function describe(...values) {
|
||||
const seen = [];
|
||||
const stack = values.reverse().map((v) => ({v}));
|
||||
while (stack.length > 0) {
|
||||
const {v, key, close} = stack.pop();
|
||||
if (close) {
|
||||
seen.push(close);
|
||||
continue;
|
||||
}
|
||||
if (key !== undefined) seen.push(key);
|
||||
if (v === null || typeof v !== "object") {
|
||||
seen.push(typeof v + ":" + (Object.is(v, -0) ? "-0" : JSON.stringify(v)));
|
||||
continue;
|
||||
}
|
||||
const array = Array.isArray(v);
|
||||
seen.push(array ? "[" : Object.getPrototypeOf(v) === Object.prototype ? "{" : "{?");
|
||||
stack.push({close: array ? "]" : "}"});
|
||||
const keys = Object.keys(v);
|
||||
for (let i = keys.length - 1; i >= 0; i--) {
|
||||
stack.push({v: v[keys[i]], key: array ? undefined : JSON.stringify(keys[i])});
|
||||
}
|
||||
}
|
||||
return seen.join(" ");
|
||||
}`, Options{})
|
||||
require.NoError(t, err)
|
||||
// Hosts pass text written by common.Marshal, where both paths agree on
|
||||
// key order, numbers and strings.
|
||||
values := map[string]any{
|
||||
"object": map[string]any{
|
||||
"__proto__": map[string]any{"polluted": true},
|
||||
"10": "ten", "2": "two", "a": []any{}, "b": map[string]any{},
|
||||
"numbers": []any{0, math.Copysign(0, -1), 1e21, int64(123456789012345678), 1.5e-7, -12, math.MaxInt64, math.SmallestNonzeroFloat64},
|
||||
"strings": []any{"", "<>&
", "é中😀", "\u0000\t\"\\", "a\xffb"},
|
||||
"nested": map[string]any{"z": nil, "y": []any{true, false, map[string]any{"x": []any{[]any{1}}}}},
|
||||
},
|
||||
"array": []any{map[string]any{"id": "x", "b": 1, "a": 2}, nil, "s"},
|
||||
"string": "plain",
|
||||
"number": 42.5,
|
||||
"null": nil,
|
||||
"deep": json.RawMessage(strings.Repeat("[", 600) + strings.Repeat("]", 600)),
|
||||
}
|
||||
for name, value := range values {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
text, err := common.Marshal(value)
|
||||
require.NoError(t, err)
|
||||
var decoded any
|
||||
require.NoError(t, common.Unmarshal(text, &decoded))
|
||||
want, err := engine.Call(t.Context(), "describe", decoded)
|
||||
require.NoError(t, err)
|
||||
got, err := engine.Call(t.Context(), "describe", RawJSON(text))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, want, got)
|
||||
nested, err := engine.Call(t.Context(), "describe", map[string]any{"value": RawJSON(text)})
|
||||
require.NoError(t, err)
|
||||
wantNested, err := engine.Call(t.Context(), "describe", map[string]any{"value": decoded})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, wantNested, nested)
|
||||
})
|
||||
}
|
||||
|
||||
_, err = engine.Call(t.Context(), "describe", "first", RawJSON(`{"a":`))
|
||||
require.EqualError(t, err, "plugin @ hook describe argument 2: unexpected end of JSON input")
|
||||
}
|
||||
|
||||
func TestJSONStateChangesAndEncodedLimits(t *testing.T) {
|
||||
const changes = `[
|
||||
[{"op":"set","path":[],"value":{"text":"<","parts":[],"nested":{"value":null}}}],
|
||||
@@ -388,6 +459,8 @@ func TestCompileRejectsAsynchronousAndImportedPlugins(t *testing.T) {
|
||||
"static import": `import value from "dependency"; export function run() { return value; }`,
|
||||
"dynamic import": `export function run() { return import("dependency"); }`,
|
||||
"top-level await": `const value = await work(); export function run() { return value; }`,
|
||||
"export from": `export { value as run } from "dependency";`,
|
||||
"export star": `export * from "dependency"; export function run() {}`,
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
_, err := Compile(source, Options{Key: "invalid"})
|
||||
@@ -398,6 +471,18 @@ func TestCompileRejectsAsynchronousAndImportedPlugins(t *testing.T) {
|
||||
|
||||
_, err := Compile(`export function run() { return "import async await"; }`, Options{Key: "valid"})
|
||||
require.NoError(t, err)
|
||||
|
||||
for name, body := range map[string]string{
|
||||
"eval": `return eval("import('dependency')");`,
|
||||
"Function constructor": `return Function("return async function () {}")();`,
|
||||
} {
|
||||
engine, err := Compile(`export function run() { `+body+` }`, Options{Key: "dynamic"})
|
||||
require.NoError(t, err, name)
|
||||
_, err = engine.Call(context.Background(), "run")
|
||||
assert.ErrorContains(t, err, "EvalError", name)
|
||||
}
|
||||
_, err = Compile(`eval("1"); export function run() {}`, Options{Key: "dynamic"})
|
||||
assert.ErrorContains(t, err, "EvalError")
|
||||
}
|
||||
|
||||
func TestCompileIgnoresSourceMapDirectives(t *testing.T) {
|
||||
@@ -445,6 +530,22 @@ func TestEngineDoesNotReinitializeIdleRuntimeAfterGC(t *testing.T) {
|
||||
assert.Equal(t, int64(1), initialized.Load())
|
||||
}
|
||||
|
||||
func TestEngineIdleRuntimeReleasesLastCallArguments(t *testing.T) {
|
||||
engine, err := Compile(`export function run(body){return body.image.length;}`, Options{Concurrency: 1})
|
||||
require.NoError(t, err)
|
||||
image := strings.Repeat("A", 1<<20)
|
||||
released := weak.Make(unsafe.StringData(image))
|
||||
value, err := engine.Call(t.Context(), "run", map[string]any{"image": image})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(1<<20), value)
|
||||
runtime.GC()
|
||||
runtime.GC()
|
||||
assert.Nil(t, released.Value(), "an idle pooled runtime must not keep the last request body alive")
|
||||
value, err = engine.Call(t.Context(), "run", map[string]any{"image": "AB"})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(2), value)
|
||||
}
|
||||
|
||||
func TestEngineBoundsConcurrentCallsAndCancelsAnOccupiedRuntime(t *testing.T) {
|
||||
entered := make(chan struct{}, 2)
|
||||
release := make(chan struct{})
|
||||
@@ -475,6 +576,193 @@ func TestEngineBoundsConcurrentCallsAndCancelsAnOccupiedRuntime(t *testing.T) {
|
||||
require.NoError(t, <-healthy)
|
||||
}
|
||||
|
||||
func TestEngineFetchContract(t *testing.T) {
|
||||
engine, err := Compile(`
|
||||
export function run(request) { return utils.fetch(request); }
|
||||
export function fetchBytes(url) { return Array.from(new Uint8Array(utils.fetch({url: url, responseType: "bytes"}).body)); }
|
||||
export function fetchFiveTimes() {
|
||||
const failures = [];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
try { utils.fetch({url: "https://api.example/json"}); } catch (e) { failures.push(e.message); }
|
||||
}
|
||||
return failures;
|
||||
}`, Options{Key: "fetch"})
|
||||
require.NoError(t, err)
|
||||
var requests []FetchRequest
|
||||
ctx := WithFetcher(t.Context(), func(_ context.Context, request FetchRequest) (FetchResponse, error) {
|
||||
requests = append(requests, request)
|
||||
switch request.URL {
|
||||
case "https://api.example/json":
|
||||
return FetchResponse{Status: http.StatusOK, Headers: map[string]string{"Content-Type": "application/json"}, Body: []byte(`{"file":{"id":7}}`)}, nil
|
||||
case "https://api.example/text":
|
||||
return FetchResponse{Status: http.StatusNotFound, Body: []byte("not found")}, nil
|
||||
case "https://api.example/bytes":
|
||||
return FetchResponse{Status: http.StatusPartialContent, Body: []byte{0, 0x80, 0xff, '{'}}, nil
|
||||
case "https://api.example/large":
|
||||
return FetchResponse{Status: http.StatusOK, Body: bytes.Repeat([]byte("a"), MaxFetchBodyBytes+1)}, nil
|
||||
case "https://api.example/down?token=secret":
|
||||
return FetchResponse{}, &url.Error{Op: "Get", URL: request.URL, Err: errors.New("connection refused")}
|
||||
}
|
||||
return FetchResponse{Status: http.StatusOK, Body: []byte(`{"ignored":true}`)}, nil
|
||||
})
|
||||
|
||||
_, err = engine.Call(t.Context(), "run", map[string]any{"url": "https://api.example/json"})
|
||||
assert.ErrorContains(t, err, "utils.fetch is not available in run")
|
||||
|
||||
value, err := engine.Call(ctx, "run", map[string]any{"url": "https://api.example/json", "headers": map[string]any{"Authorization": "Bearer key"}})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, map[string]any{
|
||||
"status": int64(http.StatusOK),
|
||||
"headers": map[string]any{"Content-Type": "application/json"},
|
||||
"body": map[string]any{"file": map[string]any{"id": int64(7)}},
|
||||
}, value)
|
||||
assert.Equal(t, []FetchRequest{{Hook: "run", URL: "https://api.example/json", Method: http.MethodGet, Headers: map[string]string{"Authorization": "Bearer key"}}}, requests)
|
||||
|
||||
value, err = engine.Call(ctx, "run", map[string]any{"url": "https://api.example/text"})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, map[string]any{"status": int64(http.StatusNotFound), "headers": map[string]any{}, "body": "not found"}, value)
|
||||
|
||||
value, err = engine.Call(ctx, "run", map[string]any{"url": "https://api.example/head", "method": "head"})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "", value.(map[string]any)["body"])
|
||||
assert.Equal(t, http.MethodHead, requests[len(requests)-1].Method)
|
||||
|
||||
// Bytes that are neither JSON nor UTF-8 reach the plugin unchanged.
|
||||
value, err = engine.Call(ctx, "fetchBytes", "https://api.example/bytes")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []any{int64(0), int64(0x80), int64(0xff), int64('{')}, value)
|
||||
|
||||
sent := len(requests)
|
||||
for name, request := range map[string]map[string]any{
|
||||
"unsupported member \"body\"": {"url": "https://api.example/json", "body": "x"},
|
||||
"only GET and HEAD": {"url": "https://api.example/json", "method": "POST"},
|
||||
`responseType must be "json" or "bytes"`: {"url": "https://api.example/json", "responseType": "text"},
|
||||
"request header \"Host\" is not": {"url": "https://api.example/json?token=secret", "headers": map[string]any{"Host": "other.example"}},
|
||||
"absolute http(s) URL": {"url": "/v1/files/retrieve"},
|
||||
"exceeds 65536 bytes": {"url": "https://api.example/" + strings.Repeat("a", 64<<10)},
|
||||
} {
|
||||
_, err = engine.Call(ctx, "run", request)
|
||||
require.Error(t, err, name)
|
||||
assert.Contains(t, err.Error(), name)
|
||||
assert.NotContains(t, err.Error(), "secret")
|
||||
}
|
||||
assert.Len(t, requests, sent, "rejected requests must not reach the fetcher")
|
||||
|
||||
_, err = engine.Call(ctx, "run", map[string]any{"url": "https://api.example/large"})
|
||||
assert.ErrorContains(t, err, "utils.fetch response from api.example exceeds 1048576 bytes")
|
||||
|
||||
_, err = engine.Call(ctx, "run", map[string]any{"url": "https://api.example/down?token=secret"})
|
||||
var hookErr *HookError
|
||||
require.ErrorAs(t, err, &hookErr)
|
||||
assert.Equal(t, "utils.fetch to api.example failed: connection refused", hookErr.Message)
|
||||
|
||||
value, err = engine.Call(ctx, "fetchFiveTimes")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []any{"utils.fetch allows at most 4 requests per hook call"}, value)
|
||||
}
|
||||
|
||||
func TestEngineFetchLendsItsExecutionSlot(t *testing.T) {
|
||||
engine, err := Compile(`
|
||||
export function fetchOnce() { return utils.fetch({url: "https://api.example/slow"}).body; }
|
||||
export function plain() { return 42; }`, Options{Key: "fetch-slot", Concurrency: 1})
|
||||
require.NoError(t, err)
|
||||
entered := make(chan struct{})
|
||||
release := make(chan struct{})
|
||||
ctx := WithFetcher(t.Context(), func(context.Context, FetchRequest) (FetchResponse, error) {
|
||||
close(entered)
|
||||
<-release
|
||||
return FetchResponse{Status: http.StatusOK, Body: []byte(`"done"`)}, nil
|
||||
})
|
||||
fetched := make(chan any, 1)
|
||||
go func() {
|
||||
value, err := engine.Call(ctx, "fetchOnce")
|
||||
assert.NoError(t, err)
|
||||
fetched <- value
|
||||
}()
|
||||
<-entered
|
||||
|
||||
// The only slot is lent while the fetch waits; without that this call
|
||||
// would wait for its deadline.
|
||||
waitContext, cancel := context.WithTimeout(t.Context(), 5*time.Second)
|
||||
defer cancel()
|
||||
value, err := engine.Call(waitContext, "plain")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(42), value)
|
||||
|
||||
close(release)
|
||||
assert.Equal(t, "done", <-fetched)
|
||||
}
|
||||
|
||||
func TestEngineFetchWaitDoesNotCountTowardCallTimeout(t *testing.T) {
|
||||
engine, err := Compile(`export function run() { return utils.fetch({url: "https://api.example/slow"}).status; }`, Options{
|
||||
Key: "fetch-clock", Timeout: 20 * time.Millisecond,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
entered := make(chan struct{})
|
||||
release := make(chan struct{})
|
||||
ctx := WithFetcher(t.Context(), func(context.Context, FetchRequest) (FetchResponse, error) {
|
||||
close(entered)
|
||||
<-release
|
||||
return FetchResponse{Status: http.StatusNoContent}, nil
|
||||
})
|
||||
result := make(chan error, 1)
|
||||
var value any
|
||||
go func() {
|
||||
var err error
|
||||
value, err = engine.Call(ctx, "run")
|
||||
result <- err
|
||||
}()
|
||||
<-entered
|
||||
// Five times the JavaScript timeout: the clock is stopped while waiting.
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
close(release)
|
||||
require.NoError(t, <-result)
|
||||
assert.Equal(t, int64(http.StatusNoContent), value)
|
||||
}
|
||||
|
||||
func TestEngineInterruptsAFetchThatCannotResume(t *testing.T) {
|
||||
engine, err := Compile(`
|
||||
let calls = 0;
|
||||
export function run(fetch) {
|
||||
calls++;
|
||||
if (!fetch) return calls;
|
||||
try { utils.fetch({url: "https://api.example/slow"}); } catch (e) { return "caught: " + e.message; }
|
||||
return "resumed";
|
||||
}`, Options{Key: "fetch-resume", Concurrency: 1})
|
||||
require.NoError(t, err)
|
||||
entered := make(chan struct{})
|
||||
release := make(chan struct{})
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
defer cancel()
|
||||
ctx = WithFetcher(ctx, func(context.Context, FetchRequest) (FetchResponse, error) {
|
||||
close(entered)
|
||||
<-release
|
||||
return FetchResponse{Status: http.StatusOK}, nil
|
||||
})
|
||||
result := make(chan error, 1)
|
||||
go func() {
|
||||
_, err := engine.Call(ctx, "run", true)
|
||||
result <- err
|
||||
}()
|
||||
<-entered
|
||||
// Another call takes the lent slot and the caller leaves before the fetch
|
||||
// returns, so the slot cannot be taken back.
|
||||
engine.semaphore <- struct{}{}
|
||||
cancel()
|
||||
close(release)
|
||||
err = <-result
|
||||
<-engine.semaphore
|
||||
|
||||
require.ErrorIs(t, err, context.Canceled)
|
||||
var hookErr *HookError
|
||||
assert.False(t, errors.As(err, &hookErr), "JavaScript must not catch a failed resume")
|
||||
value, err := engine.Call(t.Context(), "run", false)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(1), value, "an interrupted runtime must be discarded")
|
||||
assert.Empty(t, engine.semaphore)
|
||||
assert.Empty(t, engine.parked)
|
||||
}
|
||||
|
||||
func TestEnginePreservesMutableExportAndMemberBindings(t *testing.T) {
|
||||
engine, err := Compile(`
|
||||
export let run = function(){return 1;};
|
||||
@@ -495,6 +783,30 @@ export function replace(){run=function(){return 2;};native.render=function(){ret
|
||||
assert.Equal(t, int64(3), value)
|
||||
}
|
||||
|
||||
func TestEngineHasExportUsesBindingsAsLoaded(t *testing.T) {
|
||||
engine, err := Compile(`
|
||||
export let later;
|
||||
export const unset = undefined;
|
||||
export function run(){return 1;}
|
||||
export function assign(){later=function(){return 2;};}
|
||||
`, Options{Concurrency: 1})
|
||||
require.NoError(t, err)
|
||||
|
||||
engine.semaphore <- struct{}{}
|
||||
assert.True(t, engine.HasExport("run"))
|
||||
assert.False(t, engine.HasExport("later"))
|
||||
assert.False(t, engine.HasExport("unset"))
|
||||
assert.False(t, engine.HasExport("missing"))
|
||||
<-engine.semaphore
|
||||
|
||||
_, err = engine.Call(t.Context(), "assign")
|
||||
require.NoError(t, err)
|
||||
assert.False(t, engine.HasExport("later"))
|
||||
value, err := engine.Call(t.Context(), "later")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(2), value)
|
||||
}
|
||||
|
||||
func TestEngineHookErrorExtractsSanitizedJSMessage(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
|
||||
Reference in new issue
Block a user