diff --git a/enterprise/e2e/html/hurl/mcp-2025-11-25-evaluate_schema.all.hurl b/enterprise/e2e/html/hurl/mcp-2025-11-25-evaluate_schema.all.hurl index a85ffacb..d0dd253c 100644 --- a/enterprise/e2e/html/hurl/mcp-2025-11-25-evaluate_schema.all.hurl +++ b/enterprise/e2e/html/hurl/mcp-2025-11-25-evaluate_schema.all.hurl @@ -300,6 +300,43 @@ HTTP 200 [Asserts] jsonpath "$.valid" == true +# Schema URI with a fragment is rejected +POST {{base}}/self/v1/mcp +MCP-Protocol-Version: 2025-11-25 +Content-Type: application/json +``` +{ + "jsonrpc": "2.0", + "id": 914, + "method": "tools/call", + "params": { + "name": "evaluate_schema", + "arguments": { "schema": "{{base}}/test/object#meta", "stringifiedInstance": "\"hi\"" } + } +} +``` +HTTP 200 +Content-Type: application/json +Access-Control-Allow-Origin: {{base}} +Link: ; rel="describedby" +[Captures] +last_response: body +schema_path: header "Link" regex "<([^>]+)>" +[Asserts] +jsonpath "$.jsonrpc" == "2.0" +jsonpath "$.id" == 914 +jsonpath "$.error.code" == -32602 +jsonpath "$.error.message" == "Invalid params" +jsonpath "$.result" not exists + +POST {{base}}/self/v1/api/schemas/evaluate{{schema_path}} +``` +{{last_response}} +``` +HTTP 200 +[Asserts] +jsonpath "$.valid" == true + POST {{base}}/self/v1/mcp MCP-Protocol-Version: 2025-11-25 Content-Type: application/json diff --git a/enterprise/e2e/html/hurl/mcp-2025-11-25-get_schema_dependencies.all.hurl b/enterprise/e2e/html/hurl/mcp-2025-11-25-get_schema_dependencies.all.hurl index 65c66517..9cad8469 100644 --- a/enterprise/e2e/html/hurl/mcp-2025-11-25-get_schema_dependencies.all.hurl +++ b/enterprise/e2e/html/hurl/mcp-2025-11-25-get_schema_dependencies.all.hurl @@ -549,6 +549,43 @@ HTTP 200 [Asserts] jsonpath "$.valid" == true +# Schema URI with a fragment is rejected +POST {{base}}/self/v1/mcp +MCP-Protocol-Version: 2025-11-25 +Content-Type: application/json +``` +{ + "jsonrpc": "2.0", + "id": 215, + "method": "tools/call", + "params": { + "name": "get_schema_dependencies", + "arguments": { "schema": "{{base}}/test/object#meta" } + } +} +``` +HTTP 200 +Content-Type: application/json +Access-Control-Allow-Origin: {{base}} +Link: ; rel="describedby" +[Captures] +last_response: body +schema_path: header "Link" regex "<([^>]+)>" +[Asserts] +jsonpath "$.jsonrpc" == "2.0" +jsonpath "$.id" == 215 +jsonpath "$.error.code" == -32602 +jsonpath "$.error.message" == "Invalid params" +jsonpath "$.result" not exists + +POST {{base}}/self/v1/api/schemas/evaluate{{schema_path}} +``` +{{last_response}} +``` +HTTP 200 +[Asserts] +jsonpath "$.valid" == true + POST {{base}}/self/v1/mcp MCP-Protocol-Version: 2025-11-25 Content-Type: application/json diff --git a/enterprise/e2e/html/hurl/mcp-2025-11-25-get_schema_dependents.all.hurl b/enterprise/e2e/html/hurl/mcp-2025-11-25-get_schema_dependents.all.hurl index ac3aee7b..669a8580 100644 --- a/enterprise/e2e/html/hurl/mcp-2025-11-25-get_schema_dependents.all.hurl +++ b/enterprise/e2e/html/hurl/mcp-2025-11-25-get_schema_dependents.all.hurl @@ -439,6 +439,43 @@ HTTP 200 [Asserts] jsonpath "$.valid" == true +# Schema URI with a fragment is rejected +POST {{base}}/self/v1/mcp +MCP-Protocol-Version: 2025-11-25 +Content-Type: application/json +``` +{ + "jsonrpc": "2.0", + "id": 315, + "method": "tools/call", + "params": { + "name": "get_schema_dependents", + "arguments": { "schema": "{{base}}/test/object#meta" } + } +} +``` +HTTP 200 +Content-Type: application/json +Access-Control-Allow-Origin: {{base}} +Link: ; rel="describedby" +[Captures] +last_response: body +schema_path: header "Link" regex "<([^>]+)>" +[Asserts] +jsonpath "$.jsonrpc" == "2.0" +jsonpath "$.id" == 315 +jsonpath "$.error.code" == -32602 +jsonpath "$.error.message" == "Invalid params" +jsonpath "$.result" not exists + +POST {{base}}/self/v1/api/schemas/evaluate{{schema_path}} +``` +{{last_response}} +``` +HTTP 200 +[Asserts] +jsonpath "$.valid" == true + POST {{base}}/self/v1/mcp MCP-Protocol-Version: 2025-11-25 Content-Type: application/json diff --git a/enterprise/e2e/html/hurl/mcp-2025-11-25-get_schema_health.all.hurl b/enterprise/e2e/html/hurl/mcp-2025-11-25-get_schema_health.all.hurl index 483e9418..d97a6fb4 100644 --- a/enterprise/e2e/html/hurl/mcp-2025-11-25-get_schema_health.all.hurl +++ b/enterprise/e2e/html/hurl/mcp-2025-11-25-get_schema_health.all.hurl @@ -306,6 +306,43 @@ HTTP 200 [Asserts] jsonpath "$.valid" == true +# Schema URI with a fragment is rejected +POST {{base}}/self/v1/mcp +MCP-Protocol-Version: 2025-11-25 +Content-Type: application/json +``` +{ + "jsonrpc": "2.0", + "id": 415, + "method": "tools/call", + "params": { + "name": "get_schema_health", + "arguments": { "schema": "{{base}}/test/object#meta" } + } +} +``` +HTTP 200 +Content-Type: application/json +Access-Control-Allow-Origin: {{base}} +Link: ; rel="describedby" +[Captures] +last_response: body +schema_path: header "Link" regex "<([^>]+)>" +[Asserts] +jsonpath "$.jsonrpc" == "2.0" +jsonpath "$.id" == 415 +jsonpath "$.error.code" == -32602 +jsonpath "$.error.message" == "Invalid params" +jsonpath "$.result" not exists + +POST {{base}}/self/v1/api/schemas/evaluate{{schema_path}} +``` +{{last_response}} +``` +HTTP 200 +[Asserts] +jsonpath "$.valid" == true + POST {{base}}/self/v1/mcp MCP-Protocol-Version: 2025-11-25 Content-Type: application/json diff --git a/enterprise/e2e/html/hurl/mcp-2025-11-25-get_schema_locations.all.hurl b/enterprise/e2e/html/hurl/mcp-2025-11-25-get_schema_locations.all.hurl index 2622c55c..684e6ad2 100644 --- a/enterprise/e2e/html/hurl/mcp-2025-11-25-get_schema_locations.all.hurl +++ b/enterprise/e2e/html/hurl/mcp-2025-11-25-get_schema_locations.all.hurl @@ -311,6 +311,43 @@ HTTP 200 [Asserts] jsonpath "$.valid" == true +# Schema URI with a fragment is rejected +POST {{base}}/self/v1/mcp +MCP-Protocol-Version: 2025-11-25 +Content-Type: application/json +``` +{ + "jsonrpc": "2.0", + "id": 515, + "method": "tools/call", + "params": { + "name": "get_schema_locations", + "arguments": { "schema": "{{base}}/test/object#meta" } + } +} +``` +HTTP 200 +Content-Type: application/json +Access-Control-Allow-Origin: {{base}} +Link: ; rel="describedby" +[Captures] +last_response: body +schema_path: header "Link" regex "<([^>]+)>" +[Asserts] +jsonpath "$.jsonrpc" == "2.0" +jsonpath "$.id" == 515 +jsonpath "$.error.code" == -32602 +jsonpath "$.error.message" == "Invalid params" +jsonpath "$.result" not exists + +POST {{base}}/self/v1/api/schemas/evaluate{{schema_path}} +``` +{{last_response}} +``` +HTTP 200 +[Asserts] +jsonpath "$.valid" == true + POST {{base}}/self/v1/mcp MCP-Protocol-Version: 2025-11-25 Content-Type: application/json diff --git a/enterprise/e2e/html/hurl/mcp-2025-11-25-get_schema_metadata.all.hurl b/enterprise/e2e/html/hurl/mcp-2025-11-25-get_schema_metadata.all.hurl index fc33e18d..b3f9bc82 100644 --- a/enterprise/e2e/html/hurl/mcp-2025-11-25-get_schema_metadata.all.hurl +++ b/enterprise/e2e/html/hurl/mcp-2025-11-25-get_schema_metadata.all.hurl @@ -393,6 +393,43 @@ HTTP 200 [Asserts] jsonpath "$.valid" == true +# Schema URI with a fragment is rejected +POST {{base}}/self/v1/mcp +MCP-Protocol-Version: 2025-11-25 +Content-Type: application/json +``` +{ + "jsonrpc": "2.0", + "id": 815, + "method": "tools/call", + "params": { + "name": "get_schema_metadata", + "arguments": { "schema": "{{base}}/test/object#meta" } + } +} +``` +HTTP 200 +Content-Type: application/json +Access-Control-Allow-Origin: {{base}} +Link: ; rel="describedby" +[Captures] +last_response: body +schema_path: header "Link" regex "<([^>]+)>" +[Asserts] +jsonpath "$.jsonrpc" == "2.0" +jsonpath "$.id" == 815 +jsonpath "$.error.code" == -32602 +jsonpath "$.error.message" == "Invalid params" +jsonpath "$.result" not exists + +POST {{base}}/self/v1/api/schemas/evaluate{{schema_path}} +``` +{{last_response}} +``` +HTTP 200 +[Asserts] +jsonpath "$.valid" == true + POST {{base}}/self/v1/mcp MCP-Protocol-Version: 2025-11-25 Content-Type: application/json diff --git a/enterprise/e2e/html/hurl/mcp-2025-11-25-get_schema_positions.all.hurl b/enterprise/e2e/html/hurl/mcp-2025-11-25-get_schema_positions.all.hurl index 51ff8bf8..a87b7c3e 100644 --- a/enterprise/e2e/html/hurl/mcp-2025-11-25-get_schema_positions.all.hurl +++ b/enterprise/e2e/html/hurl/mcp-2025-11-25-get_schema_positions.all.hurl @@ -309,6 +309,43 @@ HTTP 200 [Asserts] jsonpath "$.valid" == true +# Schema URI with a fragment is rejected +POST {{base}}/self/v1/mcp +MCP-Protocol-Version: 2025-11-25 +Content-Type: application/json +``` +{ + "jsonrpc": "2.0", + "id": 615, + "method": "tools/call", + "params": { + "name": "get_schema_positions", + "arguments": { "schema": "{{base}}/test/object#meta" } + } +} +``` +HTTP 200 +Content-Type: application/json +Access-Control-Allow-Origin: {{base}} +Link: ; rel="describedby" +[Captures] +last_response: body +schema_path: header "Link" regex "<([^>]+)>" +[Asserts] +jsonpath "$.jsonrpc" == "2.0" +jsonpath "$.id" == 615 +jsonpath "$.error.code" == -32602 +jsonpath "$.error.message" == "Invalid params" +jsonpath "$.result" not exists + +POST {{base}}/self/v1/api/schemas/evaluate{{schema_path}} +``` +{{last_response}} +``` +HTTP 200 +[Asserts] +jsonpath "$.valid" == true + POST {{base}}/self/v1/mcp MCP-Protocol-Version: 2025-11-25 Content-Type: application/json diff --git a/enterprise/e2e/html/hurl/mcp-2025-11-25-get_schema_stats.all.hurl b/enterprise/e2e/html/hurl/mcp-2025-11-25-get_schema_stats.all.hurl index 37a5d8e5..9cd46aba 100644 --- a/enterprise/e2e/html/hurl/mcp-2025-11-25-get_schema_stats.all.hurl +++ b/enterprise/e2e/html/hurl/mcp-2025-11-25-get_schema_stats.all.hurl @@ -305,6 +305,43 @@ HTTP 200 [Asserts] jsonpath "$.valid" == true +# Schema URI with a fragment is rejected +POST {{base}}/self/v1/mcp +MCP-Protocol-Version: 2025-11-25 +Content-Type: application/json +``` +{ + "jsonrpc": "2.0", + "id": 715, + "method": "tools/call", + "params": { + "name": "get_schema_stats", + "arguments": { "schema": "{{base}}/test/object#meta" } + } +} +``` +HTTP 200 +Content-Type: application/json +Access-Control-Allow-Origin: {{base}} +Link: ; rel="describedby" +[Captures] +last_response: body +schema_path: header "Link" regex "<([^>]+)>" +[Asserts] +jsonpath "$.jsonrpc" == "2.0" +jsonpath "$.id" == 715 +jsonpath "$.error.code" == -32602 +jsonpath "$.error.message" == "Invalid params" +jsonpath "$.result" not exists + +POST {{base}}/self/v1/api/schemas/evaluate{{schema_path}} +``` +{{last_response}} +``` +HTTP 200 +[Asserts] +jsonpath "$.valid" == true + POST {{base}}/self/v1/mcp MCP-Protocol-Version: 2025-11-25 Content-Type: application/json diff --git a/enterprise/e2e/html/hurl/mcp-2025-11-25-trace_schema_evaluation.all.hurl b/enterprise/e2e/html/hurl/mcp-2025-11-25-trace_schema_evaluation.all.hurl index ec64b29b..fba043d5 100644 --- a/enterprise/e2e/html/hurl/mcp-2025-11-25-trace_schema_evaluation.all.hurl +++ b/enterprise/e2e/html/hurl/mcp-2025-11-25-trace_schema_evaluation.all.hurl @@ -381,6 +381,43 @@ HTTP 200 [Asserts] jsonpath "$.valid" == true +# Schema URI with a fragment is rejected +POST {{base}}/self/v1/mcp +MCP-Protocol-Version: 2025-11-25 +Content-Type: application/json +``` +{ + "jsonrpc": "2.0", + "id": 1014, + "method": "tools/call", + "params": { + "name": "trace_schema_evaluation", + "arguments": { "schema": "{{base}}/test/object#meta", "stringifiedInstance": "\"hi\"" } + } +} +``` +HTTP 200 +Content-Type: application/json +Access-Control-Allow-Origin: {{base}} +Link: ; rel="describedby" +[Captures] +last_response: body +schema_path: header "Link" regex "<([^>]+)>" +[Asserts] +jsonpath "$.jsonrpc" == "2.0" +jsonpath "$.id" == 1014 +jsonpath "$.error.code" == -32602 +jsonpath "$.error.message" == "Invalid params" +jsonpath "$.result" not exists + +POST {{base}}/self/v1/api/schemas/evaluate{{schema_path}} +``` +{{last_response}} +``` +HTTP 200 +[Asserts] +jsonpath "$.valid" == true + POST {{base}}/self/v1/mcp MCP-Protocol-Version: 2025-11-25 Content-Type: application/json diff --git a/enterprise/e2e/html/hurl/schemas-fragment.all.hurl b/enterprise/e2e/html/hurl/schemas-fragment.all.hurl new file mode 100644 index 00000000..9335e23f --- /dev/null +++ b/enterprise/e2e/html/hurl/schemas-fragment.all.hurl @@ -0,0 +1,125 @@ +# The REST endpoints reject schema paths that embed a URI fragment. + +GET {{base}}/self/v1/api/schemas/metadata/test/object%23meta +HTTP 400 +Content-Type: application/problem+json +Access-Control-Allow-Origin: * +Link: ; rel="describedby" +[Asserts] +jsonpath "$.title" == "sourcemeta:one/invalid-uri" +jsonpath "$.status" == 400 +jsonpath "$.detail" == "The schema URI must not contain a fragment" + +GET {{base}}/self/v1/api/schemas/locations/test/object%23meta +HTTP 400 +Content-Type: application/problem+json +Access-Control-Allow-Origin: * +Link: ; rel="describedby" +[Asserts] +jsonpath "$.title" == "sourcemeta:one/invalid-uri" +jsonpath "$.status" == 400 +jsonpath "$.detail" == "The schema URI must not contain a fragment" + +GET {{base}}/self/v1/api/schemas/positions/test/object%23meta +HTTP 400 +Content-Type: application/problem+json +Access-Control-Allow-Origin: * +Link: ; rel="describedby" +[Asserts] +jsonpath "$.title" == "sourcemeta:one/invalid-uri" +jsonpath "$.status" == 400 +jsonpath "$.detail" == "The schema URI must not contain a fragment" + +GET {{base}}/self/v1/api/schemas/stats/test/object%23meta +HTTP 400 +Content-Type: application/problem+json +Access-Control-Allow-Origin: * +Link: ; rel="describedby" +[Asserts] +jsonpath "$.title" == "sourcemeta:one/invalid-uri" +jsonpath "$.status" == 400 +jsonpath "$.detail" == "The schema URI must not contain a fragment" + +GET {{base}}/self/v1/api/schemas/health/test/object%23meta +HTTP 400 +Content-Type: application/problem+json +Access-Control-Allow-Origin: * +Link: ; rel="describedby" +[Asserts] +jsonpath "$.title" == "sourcemeta:one/invalid-uri" +jsonpath "$.status" == 400 +jsonpath "$.detail" == "The schema URI must not contain a fragment" + +GET {{base}}/self/v1/api/schemas/dependencies/test/object%23meta +HTTP 400 +Content-Type: application/problem+json +Access-Control-Allow-Origin: * +Link: ; rel="describedby" +[Asserts] +jsonpath "$.title" == "sourcemeta:one/invalid-uri" +jsonpath "$.status" == 400 +jsonpath "$.detail" == "The schema URI must not contain a fragment" + +GET {{base}}/self/v1/api/schemas/dependents/test/object%23meta +HTTP 400 +Content-Type: application/problem+json +Access-Control-Allow-Origin: * +Link: ; rel="describedby" +[Asserts] +jsonpath "$.title" == "sourcemeta:one/invalid-uri" +jsonpath "$.status" == 400 +jsonpath "$.detail" == "The schema URI must not contain a fragment" + +POST {{base}}/self/v1/api/schemas/evaluate/test/object%23meta +Content-Type: application/json +``` +"hi" +``` +HTTP 400 +Content-Type: application/problem+json +Access-Control-Allow-Origin: * +Link: ; rel="describedby" +[Asserts] +jsonpath "$.title" == "sourcemeta:one/invalid-uri" +jsonpath "$.status" == 400 +jsonpath "$.detail" == "The schema URI must not contain a fragment" + +POST {{base}}/self/v1/api/schemas/trace/test/object%23meta +Content-Type: application/json +``` +"hi" +``` +HTTP 400 +Content-Type: application/problem+json +Access-Control-Allow-Origin: * +Link: ; rel="describedby" +[Asserts] +jsonpath "$.title" == "sourcemeta:one/invalid-uri" +jsonpath "$.status" == 400 +jsonpath "$.detail" == "The schema URI must not contain a fragment" + +GET {{base}}/test/object%23meta +HTTP 400 +Content-Type: application/problem+json +Access-Control-Allow-Origin: * +Link: ; rel="describedby" +[Asserts] +jsonpath "$.title" == "sourcemeta:one/invalid-uri" +jsonpath "$.status" == 400 +jsonpath "$.detail" == "The schema URI must not contain a fragment" + +# A CORS preflight to a fragment-bearing path still returns the full set of +# preflight headers, so the browser does not see this as a CORS failure. +OPTIONS {{base}}/self/v1/api/schemas/evaluate/test/object%23meta +HTTP 204 +Access-Control-Allow-Origin: * +Access-Control-Allow-Methods: POST, OPTIONS +Access-Control-Allow-Headers: Content-Type +Access-Control-Max-Age: 3600 + +OPTIONS {{base}}/self/v1/api/schemas/trace/test/object%23meta +HTTP 204 +Access-Control-Allow-Origin: * +Access-Control-Allow-Methods: POST, OPTIONS +Access-Control-Allow-Headers: Content-Type +Access-Control-Max-Age: 3600 diff --git a/enterprise/e2e/path/hurl/mcp-2025-11-25-get_schema_dependencies.all.hurl b/enterprise/e2e/path/hurl/mcp-2025-11-25-get_schema_dependencies.all.hurl index 328b9290..0f16fa88 100644 --- a/enterprise/e2e/path/hurl/mcp-2025-11-25-get_schema_dependencies.all.hurl +++ b/enterprise/e2e/path/hurl/mcp-2025-11-25-get_schema_dependencies.all.hurl @@ -303,3 +303,40 @@ POST {{base}}/v1/catalog/self/v1/api/schemas/evaluate{{schema_path}} HTTP 200 [Asserts] jsonpath "$.valid" == true + +# Schema URI with a fragment is rejected +POST {{base}}/v1/catalog/self/v1/mcp +MCP-Protocol-Version: 2025-11-25 +Content-Type: application/json +``` +{ + "jsonrpc": "2.0", + "id": 1500, + "method": "tools/call", + "params": { + "name": "get_schema_dependencies", + "arguments": { "schema": "{{base}}/v1/catalog/test/object#meta" } + } +} +``` +HTTP 200 +Content-Type: application/json +Access-Control-Allow-Origin: {{base}} +Link: ; rel="describedby" +[Captures] +last_response: body +schema_path: header "Link" regex "]+)>" +[Asserts] +jsonpath "$.jsonrpc" == "2.0" +jsonpath "$.id" == 1500 +jsonpath "$.error.code" == -32602 +jsonpath "$.error.message" == "Invalid params" +jsonpath "$.result" not exists + +POST {{base}}/v1/catalog/self/v1/api/schemas/evaluate{{schema_path}} +``` +{{last_response}} +``` +HTTP 200 +[Asserts] +jsonpath "$.valid" == true diff --git a/enterprise/e2e/path/hurl/mcp-2025-11-25-get_schema_dependents.all.hurl b/enterprise/e2e/path/hurl/mcp-2025-11-25-get_schema_dependents.all.hurl index 2423d202..2b01870f 100644 --- a/enterprise/e2e/path/hurl/mcp-2025-11-25-get_schema_dependents.all.hurl +++ b/enterprise/e2e/path/hurl/mcp-2025-11-25-get_schema_dependents.all.hurl @@ -173,3 +173,40 @@ POST {{base}}/v1/catalog/self/v1/api/schemas/evaluate{{schema_path}} HTTP 200 [Asserts] jsonpath "$.valid" == true + +# Schema URI with a fragment is rejected +POST {{base}}/v1/catalog/self/v1/mcp +MCP-Protocol-Version: 2025-11-25 +Content-Type: application/json +``` +{ + "jsonrpc": "2.0", + "id": 1600, + "method": "tools/call", + "params": { + "name": "get_schema_dependents", + "arguments": { "schema": "{{base}}/v1/catalog/test/object#meta" } + } +} +``` +HTTP 200 +Content-Type: application/json +Access-Control-Allow-Origin: {{base}} +Link: ; rel="describedby" +[Captures] +last_response: body +schema_path: header "Link" regex "]+)>" +[Asserts] +jsonpath "$.jsonrpc" == "2.0" +jsonpath "$.id" == 1600 +jsonpath "$.error.code" == -32602 +jsonpath "$.error.message" == "Invalid params" +jsonpath "$.result" not exists + +POST {{base}}/v1/catalog/self/v1/api/schemas/evaluate{{schema_path}} +``` +{{last_response}} +``` +HTTP 200 +[Asserts] +jsonpath "$.valid" == true diff --git a/src/actions/action_dependency_tree_v1.h b/src/actions/action_dependency_tree_v1.h index 21f407d8..06a079ea 100644 --- a/src/actions/action_dependency_tree_v1.h +++ b/src/actions/action_dependency_tree_v1.h @@ -64,6 +64,14 @@ class ActionDependencyTree_v1 : public sourcemeta::one::RouterAction { return; } + if (matches.front().find('#') != std::string_view::npos || + matches.front().find("%23") != std::string_view::npos) { + sourcemeta::one::json_error( + request, response, sourcemeta::one::STATUS_BAD_REQUEST, "invalid-uri", + "The schema URI must not contain a fragment", this->error_schema_); + return; + } + const auto schemas_root{this->base() / "schemas"}; auto absolute_path{schemas_root / matches.front() / "%"}; absolute_path /= this->metapack_; @@ -89,7 +97,8 @@ class ActionDependencyTree_v1 : public sourcemeta::one::RouterAction { return sourcemeta::core::jsonrpc_make_error_invalid_params(request_id); } - if (!sourcemeta::core::URI::is_uri(arguments.at("schema").to_string())) { + if (!sourcemeta::core::URI::is_uri(arguments.at("schema").to_string()) || + arguments.at("schema").to_string().find('#') != std::string::npos) { return sourcemeta::core::jsonrpc_make_error_invalid_params(request_id); } diff --git a/src/actions/action_jsonschema_evaluate_v1.h b/src/actions/action_jsonschema_evaluate_v1.h index 2ba4ce8a..cb1783b4 100644 --- a/src/actions/action_jsonschema_evaluate_v1.h +++ b/src/actions/action_jsonschema_evaluate_v1.h @@ -76,7 +76,8 @@ class ActionJSONSchemaEvaluate_v1 : public sourcemeta::one::RouterAction { return sourcemeta::core::jsonrpc_make_error_invalid_params(request_id); } - if (!sourcemeta::core::URI::is_uri(arguments.at("schema").to_string())) { + if (!sourcemeta::core::URI::is_uri(arguments.at("schema").to_string()) || + arguments.at("schema").to_string().find('#') != std::string::npos) { return sourcemeta::core::jsonrpc_make_error_invalid_params(request_id); } @@ -136,6 +137,14 @@ class ActionJSONSchemaEvaluate_v1 : public sourcemeta::one::RouterAction { return; } + if (path.find('#') != std::string_view::npos || + path.find("%23") != std::string_view::npos) { + sourcemeta::one::json_error( + request, response, sourcemeta::one::STATUS_BAD_REQUEST, "invalid-uri", + "The schema URI must not contain a fragment", error_schema); + return; + } + if (request.method() != "post") { sourcemeta::one::json_error( request, response, sourcemeta::one::STATUS_METHOD_NOT_ALLOWED, diff --git a/src/actions/action_jsonschema_serve_v1.h b/src/actions/action_jsonschema_serve_v1.h index a7f891ec..44096d21 100644 --- a/src/actions/action_jsonschema_serve_v1.h +++ b/src/actions/action_jsonschema_serve_v1.h @@ -39,6 +39,14 @@ class ActionJSONSchemaServe_v1 : public sourcemeta::one::RouterAction { sourcemeta::one::HTTPRequest &request, sourcemeta::one::HTTPResponse &response, std::string_view error_schema) -> void { + if (schema_path.find('#') != std::string_view::npos || + schema_path.find("%23") != std::string_view::npos) { + sourcemeta::one::json_error( + request, response, sourcemeta::one::STATUS_BAD_REQUEST, "invalid-uri", + "The schema URI must not contain a fragment", error_schema); + return; + } + // Because Visual Studio Code famously does not support `$id` or `id` // See // https://github.com/microsoft/vscode-json-languageservice/issues/224 diff --git a/src/actions/action_jsonschema_trace_v1.h b/src/actions/action_jsonschema_trace_v1.h index 2eb0cbc2..ced9cf9f 100644 --- a/src/actions/action_jsonschema_trace_v1.h +++ b/src/actions/action_jsonschema_trace_v1.h @@ -78,7 +78,8 @@ class ActionJSONSchemaTrace_v1 : public sourcemeta::one::RouterAction { return sourcemeta::core::jsonrpc_make_error_invalid_params(request_id); } - if (!sourcemeta::core::URI::is_uri(arguments.at("schema").to_string())) { + if (!sourcemeta::core::URI::is_uri(arguments.at("schema").to_string()) || + arguments.at("schema").to_string().find('#') != std::string::npos) { return sourcemeta::core::jsonrpc_make_error_invalid_params(request_id); } diff --git a/src/actions/action_serve_explorer_artifact_v1.h b/src/actions/action_serve_explorer_artifact_v1.h index d09e8e75..548fa4a5 100644 --- a/src/actions/action_serve_explorer_artifact_v1.h +++ b/src/actions/action_serve_explorer_artifact_v1.h @@ -51,6 +51,15 @@ class ActionServeExplorerArtifact_v1 : public sourcemeta::one::RouterAction { auto rest(const std::span matches, sourcemeta::one::HTTPRequest &request, sourcemeta::one::HTTPResponse &response) -> void override { + if (!matches.empty() && + (matches.front().find('#') != std::string_view::npos || + matches.front().find("%23") != std::string_view::npos)) { + sourcemeta::one::json_error( + request, response, sourcemeta::one::STATUS_BAD_REQUEST, "invalid-uri", + "The schema URI must not contain a fragment", this->error_schema_); + return; + } + auto absolute_path{this->base() / "explorer"}; if (!matches.empty() && !matches.front().empty()) { absolute_path /= matches.front(); @@ -70,7 +79,8 @@ class ActionServeExplorerArtifact_v1 : public sourcemeta::one::RouterAction { return sourcemeta::core::jsonrpc_make_error_invalid_params(request_id); } - if (!sourcemeta::core::URI::is_uri(arguments.at("schema").to_string())) { + if (!sourcemeta::core::URI::is_uri(arguments.at("schema").to_string()) || + arguments.at("schema").to_string().find('#') != std::string::npos) { return sourcemeta::core::jsonrpc_make_error_invalid_params(request_id); } diff --git a/src/actions/action_serve_schema_artifact_v1.h b/src/actions/action_serve_schema_artifact_v1.h index 1ad808f2..642133d8 100644 --- a/src/actions/action_serve_schema_artifact_v1.h +++ b/src/actions/action_serve_schema_artifact_v1.h @@ -60,6 +60,14 @@ class ActionServeSchemaArtifact_v1 : public sourcemeta::one::RouterAction { return; } + if (matches.front().find('#') != std::string_view::npos || + matches.front().find("%23") != std::string_view::npos) { + sourcemeta::one::json_error( + request, response, sourcemeta::one::STATUS_BAD_REQUEST, "invalid-uri", + "The schema URI must not contain a fragment", this->error_schema_); + return; + } + auto absolute_path{this->base() / "schemas" / matches.front() / "%"}; absolute_path /= std::string{this->artifact_} + ".metapack"; ActionServeMetapackFile_v1::serve(absolute_path, sourcemeta::one::STATUS_OK, @@ -75,7 +83,8 @@ class ActionServeSchemaArtifact_v1 : public sourcemeta::one::RouterAction { return sourcemeta::core::jsonrpc_make_error_invalid_params(request_id); } - if (!sourcemeta::core::URI::is_uri(arguments.at("schema").to_string())) { + if (!sourcemeta::core::URI::is_uri(arguments.at("schema").to_string()) || + arguments.at("schema").to_string().find('#') != std::string::npos) { return sourcemeta::core::jsonrpc_make_error_invalid_params(request_id); }