diff --git a/common/json-schema-to-grammar.cpp b/common/json-schema-to-grammar.cpp index 03460aa3d..61882f319 100644 --- a/common/json-schema-to-grammar.cpp +++ b/common/json-schema-to-grammar.cpp @@ -838,10 +838,13 @@ public: auto it = _external_refs.find(url); if (it != _external_refs.end()) { target = it->second; - } else { + } else if (url.rfind("https://", 0) == 0) { // Fetch the referenced schema and resolve its refs target = _fetch_json(url); _external_refs[url] = target; + } else { + _errors.push_back("Error resolving ref " + ref + ": unsupported url scheme"); + return {json(), "", false}; } } if (parts.size() == 1) { diff --git a/examples/json_schema_to_grammar.py b/examples/json_schema_to_grammar.py index 5b97ba50f..9a407910a 100755 --- a/examples/json_schema_to_grammar.py +++ b/examples/json_schema_to_grammar.py @@ -235,9 +235,8 @@ ESCAPED_IN_REGEXPS_BUT_NOT_IN_LITERALS = set('^$.[]()|{}*+?') class SchemaConverter: - def __init__(self, *, prop_order, allow_fetch, dotall, raw_pattern): + def __init__(self, *, prop_order, dotall, raw_pattern): self._prop_order = prop_order - self._allow_fetch = allow_fetch self._dotall = dotall self._raw_pattern = raw_pattern self._rules = { @@ -507,7 +506,7 @@ class SchemaConverter: def _resolve_ref(self, ref: str): parts = ref.split('#') - assert len(parts) == 2, f'Unsupported ref: {ref}' + assert len(parts) <= 2, f'Unsupported ref: {ref}' url = parts[0] target = None is_local = not url @@ -518,13 +517,18 @@ class SchemaConverter: target = self._external_refs.get(url) if target is None: # Fetch the referenced schema and resolve its refs - target = self._fetch_json(url) + assert url.startswith("https://"), f"Error resolving ref {ref}: unsupported url scheme" + import requests + target = requests.get(url).json() self._external_refs[url] = target - tokens = parts[1].split('/') - for sel in tokens[1:]: - assert target is not None and sel in target, f'Error resolving ref {ref}: {sel} not in {target}' - target = target[sel] + if len(parts) == 1: + return self.ResolvedRef(target, '', is_local) + else: + tokens = parts[1].split('/') + for sel in tokens[1:]: + assert target is not None and sel in target, f'Error resolving ref {ref}: {sel} not in {target}' + target = target[sel] return self.ResolvedRef(target, tokens[-1] if tokens else '', is_local) @@ -760,11 +764,6 @@ def main(args_in = None): given precedence over optional properties. ''' ) - parser.add_argument( - '--allow-fetch', - action='store_true', - default=False, - help='Whether to allow fetching referenced schemas over HTTPS') parser.add_argument( '--dotall', action='store_true', @@ -792,7 +791,6 @@ def main(args_in = None): schema = json.load(f) converter = SchemaConverter( prop_order={name: idx for idx, name in enumerate(args.prop_order)}, - allow_fetch=args.allow_fetch, dotall=args.dotall, raw_pattern=args.raw_pattern) converter.visit(schema, '') diff --git a/examples/server/public/json-schema-to-grammar.mjs b/examples/server/public/json-schema-to-grammar.mjs index b796f8dcd..6418c6372 100644 --- a/examples/server/public/json-schema-to-grammar.mjs +++ b/examples/server/public/json-schema-to-grammar.mjs @@ -264,7 +264,6 @@ const ESCAPED_IN_REGEXPS_BUT_NOT_IN_LITERALS = new Set('^$.[]()|{}*+?'); export class SchemaConverter { constructor(options) { this._propOrder = options.prop_order || {}; - this._allowFetch = options.allow_fetch || false; this._dotall = options.dotall || false; this._rules = {'space': SPACE_RULE}; this._refs = {}; @@ -558,6 +557,9 @@ export class SchemaConverter { target = this._externalRefs.get(url); if (target === undefined) { // Fetch the referenced schema and resolve its refs + if (!url.startsWith('https://')) { + throw new Error(`Error resolving ref ${ref}: unsupported url scheme`); + } target = this._fetchJson(url); this._externalRefs.set(url, target); } diff --git a/tests/run-json-schema-to-grammar.mjs b/tests/run-json-schema-to-grammar.mjs index 8d020a02e..a50e3e683 100644 --- a/tests/run-json-schema-to-grammar.mjs +++ b/tests/run-json-schema-to-grammar.mjs @@ -2,8 +2,12 @@ import { readFileSync } from "fs" import { SchemaConverter } from "../examples/server/public/json-schema-to-grammar.mjs" const [, , file] = process.argv -const url = `file://${file}` -const schema = JSON.parse(readFileSync(file, "utf8")); +let schema; +if (file.startsWith('https://')) { + schema = await (await fetch(file)).json() +} else { + schema = JSON.parse(readFileSync(file, "utf8")); +} const converter = new SchemaConverter({}) converter.visit(schema, '') console.log(converter.formatGrammar())