Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,13 @@ history.

## Unreleased

0.44.0
- `fjs/protocol/json_rpc` `dispatch` looks up handlers by own property.
An `Object.prototype` method name arriving as `method` no longer throws
or emits a malformed response; it answers `-32601` like any other
unknown method
[#1514](https://github.com/functionalscript/functionalscript/pull/1514)

## 0.44.0

- **BREAKING CHANGES:** `fjs/ci/deno` no longer exports `coverageInclude`.
The Deno CI job runs `deno task cov`, so `deno.json` owns the coverage
Expand Down
11 changes: 7 additions & 4 deletions fjs/protocol/json_rpc/module.f.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@
* @module
*
* @import { Unknown } from '../../media/json/types.ts'
* @import { Id, RpcError, Handler, Handlers, Response } from './types.ts'
* @import { Id, RpcError, Handlers, Response } from './types.ts'
*/

import { at } from '../../types/object/module.f.mjs'
import { number, string, or, option } from '../../types/rtti/module.f.mjs'
import { validate } from '../../types/rtti/validate/module.f.mjs'
import { unknown } from '../../media/json/rtti/module.f.mjs'
Expand Down Expand Up @@ -100,9 +101,11 @@ export const dispatch = handlers => value => {
if (id === undefined) {
return null
}
/** @type {Handler | undefined} */
const handler = handlers[method]
if (handler === undefined) {
// `at`, not `handlers[method]`: `method` is untrusted wire data, and a
// bracket lookup on a plain object resolves inherited `Object.prototype`
// names (`constructor`, `toString`, …) to callables.
const handler = at(method)(handlers)
if (handler === null) {
return errorResponseOf(id)(methodNotFound)
}
const [t2, result] = handler(params)
Expand Down
18 changes: 18 additions & 0 deletions fjs/protocol/json_rpc/proof.f.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ const handlers = {
}
const d = dispatch(handlers)

/** A handler table that does own the `Object.prototype` name `toString`. */
const dOwnPrototypeName = dispatch({ toString: () => ok('own') })

export const proof = {
schema: {
request: {
Expand Down Expand Up @@ -72,6 +75,21 @@ export const proof = {
const r = d({ jsonrpc: '2.0', method: 'nope', id: 4 })
assert(r !== null && 'error' in r && r.error.code === -32601)
},
// `method` is untrusted wire data: an inherited `Object.prototype`
// name must not resolve to a callable.
inheritedToString: () => {
const r = d({ jsonrpc: '2.0', method: 'toString', id: 4 })
assert(r !== null && 'error' in r && r.error.code === -32601)
},
inheritedConstructor: () => {
const r = d({ jsonrpc: '2.0', method: 'constructor', id: 4 })
assert(r !== null && 'error' in r && r.error.code === -32601)
},
// ... while an *own* property of that name is an ordinary method.
ownPrototypeName: () => {
const r = dOwnPrototypeName({ jsonrpc: '2.0', method: 'toString', id: 4 })
assert(r !== null && 'result' in r && r.result === 'own')
},
invalidRequest: () => {
const r = d({ jsonrpc: '1.0', method: 'ping', id: 5 })
assert(r !== null && 'error' in r && r.error.code === -32600 && r.id === null)
Expand Down
53 changes: 0 additions & 53 deletions fjs/protocol/json_rpc/todo/dispatch-at-lookup.md

This file was deleted.

10 changes: 5 additions & 5 deletions fjs/protocol/json_rpc/todo/effectful-dispatch-skeleton.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,20 @@ The JSON-RPC request preamble — decode the envelope, answer a malformed one
with `Invalid Request` (`id: null`), and split notifications
(`id === undefined`) from requests — is spelled out twice.

Pure `dispatch` (`fjs/protocol/json_rpc/module.f.mjs:94-112`):
Pure `dispatch` (`fjs/protocol/json_rpc/module.f.mjs:95-110`):

```ts
```js
const [t, message] = decodeRequest(value)
if (t === 'error') { return errorResponseOf(null)(invalidRequest) }
const { id, method, params } = message
if (id === undefined) { return null }
const handler: Handler | undefined = handlers[method]
if (handler === undefined) { return errorResponseOf(id)(methodNotFound) }
const handler = at(method)(handlers)
if (handler === null) { return errorResponseOf(id)(methodNotFound) }
```

Effectful `mcpStep` (`fjs/protocol/mcp/module.f.mjs:264-287`):

```ts
```js
const [t, message] = decodeRequest(value)
if (t === 'error') { return pure(_errResponse(null)(invalidRequest)) }
const { id, method, params } = message
Expand Down
Loading