@markup-carve/carve-lsp 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -0
- package/dist/analyze.d.ts +17 -1
- package/dist/analyze.js +53 -1
- package/dist/analyze.js.map +1 -1
- package/dist/analyze.test.js +84 -0
- package/dist/analyze.test.js.map +1 -1
- package/dist/definition.js +22 -2
- package/dist/definition.js.map +1 -1
- package/dist/hover.js +23 -8
- package/dist/hover.js.map +1 -1
- package/dist/include-directive.d.ts +61 -0
- package/dist/include-directive.js +158 -0
- package/dist/include-directive.js.map +1 -0
- package/dist/include-directive.test.d.ts +1 -0
- package/dist/include-directive.test.js +83 -0
- package/dist/include-directive.test.js.map +1 -0
- package/dist/include-path.d.ts +63 -0
- package/dist/include-path.js +135 -0
- package/dist/include-path.js.map +1 -0
- package/dist/include-path.test.d.ts +1 -0
- package/dist/include-path.test.js +189 -0
- package/dist/include-path.test.js.map +1 -0
- package/dist/include-security-conformance.test.d.ts +1 -0
- package/dist/include-security-conformance.test.js +100 -0
- package/dist/include-security-conformance.test.js.map +1 -0
- package/dist/include-settings.d.ts +50 -0
- package/dist/include-settings.js +135 -0
- package/dist/include-settings.js.map +1 -0
- package/dist/include-settings.test.d.ts +1 -0
- package/dist/include-settings.test.js +191 -0
- package/dist/include-settings.test.js.map +1 -0
- package/dist/includes.d.ts +78 -0
- package/dist/includes.js +158 -0
- package/dist/includes.js.map +1 -0
- package/dist/includes.test.d.ts +1 -0
- package/dist/includes.test.js +202 -0
- package/dist/includes.test.js.map +1 -0
- package/dist/inline-text.d.ts +24 -0
- package/dist/inline-text.js +34 -0
- package/dist/inline-text.js.map +1 -0
- package/dist/inline-text.test.d.ts +1 -0
- package/dist/inline-text.test.js +32 -0
- package/dist/inline-text.test.js.map +1 -0
- package/dist/position-encoding.test.d.ts +1 -0
- package/dist/position-encoding.test.js +143 -0
- package/dist/position-encoding.test.js.map +1 -0
- package/dist/position.d.ts +82 -0
- package/dist/position.js +137 -0
- package/dist/position.js.map +1 -0
- package/dist/position.test.d.ts +1 -0
- package/dist/position.test.js +61 -0
- package/dist/position.test.js.map +1 -0
- package/dist/references.js +3 -0
- package/dist/references.js.map +1 -1
- package/dist/semantic.d.ts +14 -0
- package/dist/semantic.js +98 -13
- package/dist/semantic.js.map +1 -1
- package/dist/semantic.test.d.ts +1 -0
- package/dist/semantic.test.js +93 -0
- package/dist/semantic.test.js.map +1 -0
- package/dist/server.js +50 -23
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recognition of processor-level `{{ … }}` include directives (PART 6 grammar,
|
|
3
|
+
* PART 9 §19 rule I1).
|
|
4
|
+
*
|
|
5
|
+
* Kept separate from {@link ./includes.js} so consumers that only need to
|
|
6
|
+
* RECOGNIZE a directive do not pull in the expander's file-system imports.
|
|
7
|
+
*
|
|
8
|
+
* The regular expressions and `parseDirective` mirror `src/include-directive.ts`
|
|
9
|
+
* in the carve-js engine deliberately: one rule, one spelling. What is added
|
|
10
|
+
* here is source-level scanning. The engine recognizes directives on
|
|
11
|
+
* reassembled inline runs of a parsed AST; this server has no include-aware
|
|
12
|
+
* engine to parse with (the pinned `@markup-carve/carve` predates the pass), so
|
|
13
|
+
* it scans the raw document text and shields verbatim regions itself.
|
|
14
|
+
*/
|
|
15
|
+
export const DIRECTIVE_SCAN_RE = /\{\{\s+(?:"((?:\\.|[^"\\])*)"|\u201c([^\u201d]*)\u201d|([^#@}\s"\u201c]+))((?:\s+#[A-Za-z_][\w-]*)?)(.*?)\s+\}\}/g;
|
|
16
|
+
export const DIRECTIVE_FULL_RE = /^\{\{\s+(?:"((?:\\.|[^"\\])*)"|\u201c([^\u201d]*)\u201d|([^#@}\s"\u201c]+))((?:\s+#[A-Za-z_][\w-]*)?)(.*?)\s+\}\}$/;
|
|
17
|
+
const OPTION_RE = /^@([A-Za-z_][\w-]*):([^#@}\s]+)$/;
|
|
18
|
+
function unescapeQuotedPath(value) {
|
|
19
|
+
return value.replace(/\\(["\\])/g, '$1');
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Parse one candidate directive token. Returns null when the token is not a
|
|
23
|
+
* well-formed directive per I1 (a bad shape, or an unrecognized or malformed
|
|
24
|
+
* option), in which case it stays ordinary text and `onInvalidOption` fires so
|
|
25
|
+
* the caller can raise `include-unknown-option`.
|
|
26
|
+
*/
|
|
27
|
+
export function parseDirective(raw, onInvalidOption) {
|
|
28
|
+
const m = DIRECTIVE_FULL_RE.exec(raw);
|
|
29
|
+
if (!m)
|
|
30
|
+
return null;
|
|
31
|
+
const includePath = m[1] !== undefined ? unescapeQuotedPath(m[1]) : (m[2] ?? m[3]);
|
|
32
|
+
if (includePath === '')
|
|
33
|
+
return null;
|
|
34
|
+
const sectionPart = m[4]?.trim();
|
|
35
|
+
const section = sectionPart ? sectionPart.slice(1) : undefined;
|
|
36
|
+
let lines;
|
|
37
|
+
let shift = 0;
|
|
38
|
+
const rest = m[5]?.trim();
|
|
39
|
+
if (rest) {
|
|
40
|
+
for (const part of rest.split(/\s+/)) {
|
|
41
|
+
const opt = OPTION_RE.exec(part);
|
|
42
|
+
const invalid = () => {
|
|
43
|
+
// I1: an unrecognized (or malformed) option makes the directive
|
|
44
|
+
// unresolvable - warning plus literal text, never a silent drop.
|
|
45
|
+
if (part.startsWith('@'))
|
|
46
|
+
onInvalidOption?.(part);
|
|
47
|
+
return null;
|
|
48
|
+
};
|
|
49
|
+
if (!opt)
|
|
50
|
+
return invalid();
|
|
51
|
+
const [, key, value] = opt;
|
|
52
|
+
if (key === 'lines') {
|
|
53
|
+
const lm = /^([1-9]\d*)-([1-9]\d*)$/.exec(value);
|
|
54
|
+
if (!lm)
|
|
55
|
+
return invalid();
|
|
56
|
+
lines = { start: Number(lm[1]), end: Number(lm[2]) };
|
|
57
|
+
if (lines.end < lines.start)
|
|
58
|
+
return invalid();
|
|
59
|
+
}
|
|
60
|
+
else if (key === 'shift') {
|
|
61
|
+
// I8: a signed integer or the literal "auto", never both forms.
|
|
62
|
+
if (value === 'auto')
|
|
63
|
+
shift = 'auto';
|
|
64
|
+
else if (!/^[+-]?\d+$/.test(value))
|
|
65
|
+
return invalid();
|
|
66
|
+
else
|
|
67
|
+
shift = Number(value);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
return invalid();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const directive = { raw, path: includePath, shift };
|
|
75
|
+
if (section !== undefined)
|
|
76
|
+
directive.section = section;
|
|
77
|
+
if (lines !== undefined)
|
|
78
|
+
directive.lines = lines;
|
|
79
|
+
return directive;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Regions of `source` whose content is verbatim and therefore shielded from
|
|
83
|
+
* include recognition (I9): fenced blocks of every flavour, raw blocks
|
|
84
|
+
* (```` ```=html ````) included, and inline code spans.
|
|
85
|
+
*
|
|
86
|
+
* Approximate by design, and deliberately so: it is a line-and-backtick scan
|
|
87
|
+
* rather than a parse, because the pinned engine cannot tell us where a
|
|
88
|
+
* directive sits relative to a fence. It errs toward shielding - an ambiguous
|
|
89
|
+
* region is treated as verbatim and the directive stays literal, which is the
|
|
90
|
+
* safe direction for a file-read capability.
|
|
91
|
+
*/
|
|
92
|
+
export function verbatimSpans(source) {
|
|
93
|
+
const spans = [];
|
|
94
|
+
let offset = 0;
|
|
95
|
+
let fence = null;
|
|
96
|
+
for (const line of source.split('\n')) {
|
|
97
|
+
const lineStart = offset;
|
|
98
|
+
const lineEnd = offset + line.length;
|
|
99
|
+
offset = lineEnd + 1;
|
|
100
|
+
if (fence) {
|
|
101
|
+
const close = /^ {0,3}(`{3,}|~{3,})[ \t]*$/.exec(line);
|
|
102
|
+
if (close && close[1][0] === fence.char && close[1].length >= fence.length) {
|
|
103
|
+
spans.push({ start: fence.start, end: lineEnd });
|
|
104
|
+
fence = null;
|
|
105
|
+
}
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const open = /^ {0,3}(`{3,}|~{3,})(.*)$/.exec(line);
|
|
109
|
+
// A backtick fence's info string may not itself contain a backtick; a
|
|
110
|
+
// tilde fence's may. Same rule the engines apply.
|
|
111
|
+
if (open && !(open[1][0] === '`' && open[2].includes('`'))) {
|
|
112
|
+
fence = { char: open[1][0], length: open[1].length, start: lineStart };
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
// Inline code spans, matched run-for-run within the line.
|
|
116
|
+
const runs = /`+/g;
|
|
117
|
+
let openRun = null;
|
|
118
|
+
for (let m = runs.exec(line); m; m = runs.exec(line)) {
|
|
119
|
+
if (!openRun) {
|
|
120
|
+
openRun = { index: m.index, length: m[0].length };
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (m[0].length === openRun.length) {
|
|
124
|
+
spans.push({ start: lineStart + openRun.index, end: lineStart + m.index + m[0].length });
|
|
125
|
+
openRun = null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// An unterminated fence shields everything to the end of the document.
|
|
130
|
+
if (fence)
|
|
131
|
+
spans.push({ start: fence.start, end: source.length });
|
|
132
|
+
return spans;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Locate the well-formed directives in `source`, in source order, skipping any
|
|
136
|
+
* that begin inside a verbatim region (I9).
|
|
137
|
+
*
|
|
138
|
+
* `onInvalidOption` fires for a token of directive shape whose options do not
|
|
139
|
+
* parse, so the caller can raise `include-unknown-option` on it.
|
|
140
|
+
*/
|
|
141
|
+
export function findDirectives(source, onInvalidOption) {
|
|
142
|
+
const shielded = verbatimSpans(source);
|
|
143
|
+
const inVerbatim = (index) => shielded.some((span) => index >= span.start && index < span.end);
|
|
144
|
+
const re = new RegExp(DIRECTIVE_SCAN_RE.source, 'g');
|
|
145
|
+
const found = [];
|
|
146
|
+
for (let m = re.exec(source); m; m = re.exec(source)) {
|
|
147
|
+
const start = m.index;
|
|
148
|
+
const end = start + m[0].length;
|
|
149
|
+
if (inVerbatim(start))
|
|
150
|
+
continue;
|
|
151
|
+
const parsed = parseDirective(m[0], (part) => onInvalidOption?.(part, start, end));
|
|
152
|
+
if (!parsed)
|
|
153
|
+
continue;
|
|
154
|
+
found.push({ ...parsed, start, end });
|
|
155
|
+
}
|
|
156
|
+
return found;
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=include-directive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"include-directive.js","sourceRoot":"","sources":["../src/include-directive.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
import { findDirectives, parseDirective, verbatimSpans } from './include-directive.js';
|
|
4
|
+
// Expectations here follow the cross-engine include-conformance vectors named
|
|
5
|
+
// in each test (markup-carve/carve tests/include-conformance/vectors). Those
|
|
6
|
+
// vectors currently live only on the unmerged spec branch for
|
|
7
|
+
// markup-carve/carve#291, so they are cited rather than vendored.
|
|
8
|
+
test('a bare directive is recognized with its path', () => {
|
|
9
|
+
const [d] = findDirectives('{{ child.crv }}');
|
|
10
|
+
assert.ok(d);
|
|
11
|
+
assert.equal(d.path, 'child.crv');
|
|
12
|
+
assert.equal(d.start, 0);
|
|
13
|
+
assert.equal(d.end, 15);
|
|
14
|
+
});
|
|
15
|
+
test('a quoted path carrying a space is recognized (i01-quoted-path-roundtrip)', () => {
|
|
16
|
+
const [d] = findDirectives('{{ "my chapter.crv" }}');
|
|
17
|
+
assert.ok(d);
|
|
18
|
+
assert.equal(d.path, 'my chapter.crv');
|
|
19
|
+
});
|
|
20
|
+
test('a section selector is separated from the path', () => {
|
|
21
|
+
const [d] = findDirectives('{{ c.crv #Intro }}');
|
|
22
|
+
assert.ok(d);
|
|
23
|
+
assert.equal(d.path, 'c.crv');
|
|
24
|
+
assert.equal(d.section, 'Intro');
|
|
25
|
+
});
|
|
26
|
+
test('empty and pathless brace pairs stay literal (i01-*-literal)', () => {
|
|
27
|
+
// Each of these is ordinary text under the PART 6 production: the path token
|
|
28
|
+
// is mandatory and may not begin with `#` or `@`.
|
|
29
|
+
for (const source of ['{{}}', '{{ }}', '{{ #sec }}', '{{ @shift:1 }}']) {
|
|
30
|
+
assert.deepEqual(findDirectives(source), [], source);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
test('an unknown option makes the directive literal and reports the option (i01-unknown-option-literal)', () => {
|
|
34
|
+
const seen = [];
|
|
35
|
+
const found = findDirectives('{{ child @nope:1 }}', (part) => seen.push(part));
|
|
36
|
+
assert.deepEqual(found, []);
|
|
37
|
+
assert.deepEqual(seen, ['@nope:1']);
|
|
38
|
+
});
|
|
39
|
+
test('a malformed shift value is an unknown option (i01-shift-malformed-value)', () => {
|
|
40
|
+
const seen = [];
|
|
41
|
+
assert.deepEqual(findDirectives('{{ child @shift:x }}', (part) => seen.push(part)), []);
|
|
42
|
+
assert.deepEqual(seen, ['@shift:x']);
|
|
43
|
+
});
|
|
44
|
+
test('several directives in one run are all found (i09a-multi-directive-one-run)', () => {
|
|
45
|
+
const found = findDirectives('start {{ a.crv }} mid {{ b.crv }} end');
|
|
46
|
+
assert.deepEqual(found.map((d) => d.path), ['a.crv', 'b.crv']);
|
|
47
|
+
});
|
|
48
|
+
test('adjacent directives are both found', () => {
|
|
49
|
+
const found = findDirectives('{{ a.crv }}{{ d.crv }}');
|
|
50
|
+
assert.deepEqual(found.map((d) => d.path), ['a.crv', 'd.crv']);
|
|
51
|
+
});
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Verbatim shielding (I9)
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
test('a directive inside a fence or a code span is shielded (i09-verbatim-fence-and-code-span)', () => {
|
|
56
|
+
const source = '```txt\n{{ child }}\n```\n\nUse `{{ child }}`.';
|
|
57
|
+
assert.deepEqual(findDirectives(source), []);
|
|
58
|
+
});
|
|
59
|
+
test('a raw block shields its directive (i09-verbatim-raw-block)', () => {
|
|
60
|
+
assert.deepEqual(findDirectives('```=html\n{{ child }}\n```'), []);
|
|
61
|
+
});
|
|
62
|
+
test('a fence shields only itself; a directive after it still resolves (i09-fence-info-shields-plain-expands)', () => {
|
|
63
|
+
const source = '```js\n{{ child }}\n```\n\n{{ child }}';
|
|
64
|
+
const found = findDirectives(source);
|
|
65
|
+
assert.equal(found.length, 1);
|
|
66
|
+
// The surviving one is the directive AFTER the closing fence.
|
|
67
|
+
assert.equal(found[0].start, source.lastIndexOf('{{ child }}'));
|
|
68
|
+
});
|
|
69
|
+
test('a tilde fence shields too', () => {
|
|
70
|
+
assert.deepEqual(findDirectives('~~~\n{{ child }}\n~~~'), []);
|
|
71
|
+
});
|
|
72
|
+
test('an unterminated fence shields to the end of the document', () => {
|
|
73
|
+
assert.deepEqual(findDirectives('```\n{{ child }}\n'), []);
|
|
74
|
+
});
|
|
75
|
+
test('verbatimSpans covers the whole fence including its markers', () => {
|
|
76
|
+
const source = '```\nx\n```\n';
|
|
77
|
+
assert.deepEqual(verbatimSpans(source), [{ start: 0, end: 9 }]);
|
|
78
|
+
});
|
|
79
|
+
test('parseDirective rejects a token that is not a directive', () => {
|
|
80
|
+
assert.equal(parseDirective('{{ oops'), null);
|
|
81
|
+
assert.equal(parseDirective('{{ }}'), null);
|
|
82
|
+
});
|
|
83
|
+
//# sourceMappingURL=include-directive.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"include-directive.test.js","sourceRoot":"","sources":["../src/include-directive.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Why a resolver refused a target. Reported on {@link IncludeWarning.detail},
|
|
3
|
+
* never folded into the diagnostic message: §19 I7 requires a
|
|
4
|
+
* processor-generated message naming the failure class, and the
|
|
5
|
+
* include-conformance goldens (`i10-fs-*`) spell every containment denial as
|
|
6
|
+
* the single rule `include-unresolved` so that a denial cannot be used to probe
|
|
7
|
+
* host layout. The class is kept available for tooling and tests.
|
|
8
|
+
*/
|
|
9
|
+
export type IncludeDenial = 'remote-not-allowed' | 'absolute-denied' | 'outside-root' | 'not-found' | 'not-a-file';
|
|
10
|
+
export interface IncludeContext {
|
|
11
|
+
/** Identity of the including document, supplied by the host when known. */
|
|
12
|
+
sourcePath?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Include chain, root first: the canonical id of each ancestor. Used for
|
|
15
|
+
* relative resolution (a nested include resolves against its actual parent
|
|
16
|
+
* directory) and for the cycle guard.
|
|
17
|
+
*/
|
|
18
|
+
stack: string[];
|
|
19
|
+
/** Zero-based include depth of the directive being resolved. */
|
|
20
|
+
depth: number;
|
|
21
|
+
}
|
|
22
|
+
export type IncludeResolved = {
|
|
23
|
+
ok: true;
|
|
24
|
+
id: string;
|
|
25
|
+
source: string;
|
|
26
|
+
bytes: number;
|
|
27
|
+
} | {
|
|
28
|
+
ok: false;
|
|
29
|
+
id: string;
|
|
30
|
+
denial: IncludeDenial;
|
|
31
|
+
};
|
|
32
|
+
export type IncludeResolver = (includePath: string, ctx: IncludeContext) => IncludeResolved;
|
|
33
|
+
export interface FileSystemResolverOptions {
|
|
34
|
+
/** Allow absolute include paths, still subject to root containment. Default false. */
|
|
35
|
+
allowAbsolute?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Hosts a remote include may name. Empty (the default) means no remote
|
|
38
|
+
* include is permitted at all.
|
|
39
|
+
*
|
|
40
|
+
* Note that this server never fetches over the network under any setting:
|
|
41
|
+
* an allowlisted host is still refused, because there is no fetcher. The
|
|
42
|
+
* option exists so that "allowlist explicitly configured" is a real,
|
|
43
|
+
* inspectable condition rather than an implicit one, and so a host that
|
|
44
|
+
* later gains a fetcher has the gate already in place.
|
|
45
|
+
*/
|
|
46
|
+
allowedRemoteHosts?: string[];
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Filesystem resolver with canonical root containment, for a workspace the
|
|
50
|
+
* client reports as trusted.
|
|
51
|
+
*
|
|
52
|
+
* Canonicalize-then-contain: the candidate is resolved to its real path
|
|
53
|
+
* (symlinks followed) and only then compared against the real root.
|
|
54
|
+
*
|
|
55
|
+
* Deliberately NOT a lexical ban on `..`, which is both too strict and too
|
|
56
|
+
* weak. Too strict: `../shared/glossary.crv` from `chapters/ch1.crv` is a
|
|
57
|
+
* normal book layout whose canonical target is inside the root, and the
|
|
58
|
+
* conformance vector `i10-fs-dotdot-inside-root-allowed` requires it to
|
|
59
|
+
* resolve. Too weak: a symlink inside the root pointing out of it, or an
|
|
60
|
+
* absolute path, escapes without containing `..` at all
|
|
61
|
+
* (`i10-fs-symlink-dir-escape-denied`). Canonical containment subsumes both.
|
|
62
|
+
*/
|
|
63
|
+
export declare function fileSystemResolver(includeRoot: string, opts?: FileSystemResolverOptions): IncludeResolver;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The single place an include path becomes a file read.
|
|
3
|
+
*
|
|
4
|
+
* PART 9 §19 puts four MUSTs on this function, and they are here rather than
|
|
5
|
+
* spread over the callers on purpose: a guard applied on one path and missed on
|
|
6
|
+
* another is the defect this repository's siblings keep hitting. Nothing else
|
|
7
|
+
* in this package joins, canonicalizes or opens an include target.
|
|
8
|
+
*
|
|
9
|
+
* - MUST resolve include paths only to files under the configured project root
|
|
10
|
+
* AFTER symlink resolution (rejecting `..`-traversal and absolute paths that
|
|
11
|
+
* escape the root)
|
|
12
|
+
* - MUST NOT fetch remote URLs unless an allowlist is explicitly configured
|
|
13
|
+
*
|
|
14
|
+
* The remaining two MUSTs (recursion depth, total expanded byte size) are
|
|
15
|
+
* properties of the include GRAPH rather than of one path, so they live in
|
|
16
|
+
* {@link ./includes.js} where the walk is.
|
|
17
|
+
*/
|
|
18
|
+
import { closeSync, fstatSync, openSync, readSync, realpathSync } from 'node:fs';
|
|
19
|
+
import path from 'node:path';
|
|
20
|
+
/** A scheme with an authority (`https://…`), or a protocol-relative `//host/…`. */
|
|
21
|
+
const REMOTE_RE = /^(?:[A-Za-z][A-Za-z0-9+.-]*:)?\/\//;
|
|
22
|
+
function remoteHost(spec) {
|
|
23
|
+
const withScheme = spec.startsWith('//') ? `https:${spec}` : spec;
|
|
24
|
+
try {
|
|
25
|
+
return new URL(withScheme).hostname;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return '';
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Filesystem resolver with canonical root containment, for a workspace the
|
|
33
|
+
* client reports as trusted.
|
|
34
|
+
*
|
|
35
|
+
* Canonicalize-then-contain: the candidate is resolved to its real path
|
|
36
|
+
* (symlinks followed) and only then compared against the real root.
|
|
37
|
+
*
|
|
38
|
+
* Deliberately NOT a lexical ban on `..`, which is both too strict and too
|
|
39
|
+
* weak. Too strict: `../shared/glossary.crv` from `chapters/ch1.crv` is a
|
|
40
|
+
* normal book layout whose canonical target is inside the root, and the
|
|
41
|
+
* conformance vector `i10-fs-dotdot-inside-root-allowed` requires it to
|
|
42
|
+
* resolve. Too weak: a symlink inside the root pointing out of it, or an
|
|
43
|
+
* absolute path, escapes without containing `..` at all
|
|
44
|
+
* (`i10-fs-symlink-dir-escape-denied`). Canonical containment subsumes both.
|
|
45
|
+
*/
|
|
46
|
+
export function fileSystemResolver(includeRoot, opts = {}) {
|
|
47
|
+
const rootReal = realpathSync(includeRoot);
|
|
48
|
+
const allowedRemoteHosts = opts.allowedRemoteHosts ?? [];
|
|
49
|
+
const contains = (candidate) => {
|
|
50
|
+
const rel = path.relative(rootReal, candidate);
|
|
51
|
+
if (rel === '')
|
|
52
|
+
return true;
|
|
53
|
+
if (!rel || path.isAbsolute(rel))
|
|
54
|
+
return false;
|
|
55
|
+
// Segment-wise, so a directory legitimately named "..foo" is not read as
|
|
56
|
+
// an escape the way a startsWith('..') prefix test would.
|
|
57
|
+
return rel.split(path.sep)[0] !== '..';
|
|
58
|
+
};
|
|
59
|
+
return (includePath, ctx) => {
|
|
60
|
+
// GUARD 1 (§19: MUST NOT fetch remote URLs unless an allowlist is
|
|
61
|
+
// explicitly configured). Checked before any path join, so a URL can never
|
|
62
|
+
// be mistaken for a relative path and read off disk as `<root>/https:/…`.
|
|
63
|
+
if (REMOTE_RE.test(includePath)) {
|
|
64
|
+
const host = remoteHost(includePath);
|
|
65
|
+
if (!allowedRemoteHosts.includes(host)) {
|
|
66
|
+
return { ok: false, id: includePath, denial: 'remote-not-allowed' };
|
|
67
|
+
}
|
|
68
|
+
// Allowlisted, and still refused: this server has no fetcher. Reported
|
|
69
|
+
// as a denial rather than silently, so the author sees why.
|
|
70
|
+
return { ok: false, id: includePath, denial: 'remote-not-allowed' };
|
|
71
|
+
}
|
|
72
|
+
// GUARD 2 (§19: absolute paths). Denied outright by default; when the host
|
|
73
|
+
// opts in, an absolute path still has to survive GUARD 3.
|
|
74
|
+
if (!opts.allowAbsolute && path.isAbsolute(includePath)) {
|
|
75
|
+
return { ok: false, id: includePath, denial: 'absolute-denied' };
|
|
76
|
+
}
|
|
77
|
+
// The stack carries the canonical path of each ancestor, so a nested
|
|
78
|
+
// relative include resolves against its actual parent directory, not the
|
|
79
|
+
// root.
|
|
80
|
+
const parent = ctx.stack[ctx.stack.length - 1];
|
|
81
|
+
const base = parent ? path.dirname(path.resolve(rootReal, parent)) : rootReal;
|
|
82
|
+
const joined = path.isAbsolute(includePath)
|
|
83
|
+
? includePath
|
|
84
|
+
: path.resolve(base, includePath);
|
|
85
|
+
let real;
|
|
86
|
+
try {
|
|
87
|
+
// Symlink resolution happens HERE, before containment, which is what
|
|
88
|
+
// makes a symlinked escape visible to GUARD 3 at all.
|
|
89
|
+
real = realpathSync(joined);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return { ok: false, id: includePath, denial: 'not-found' };
|
|
93
|
+
}
|
|
94
|
+
// GUARD 3 (§19: only files under the configured project root AFTER symlink
|
|
95
|
+
// resolution). Catches `..` that leaves the root, a symlink that points out
|
|
96
|
+
// of it, and an absolute path outside it.
|
|
97
|
+
if (!contains(real)) {
|
|
98
|
+
return { ok: false, id: includePath, denial: 'outside-root' };
|
|
99
|
+
}
|
|
100
|
+
let fd;
|
|
101
|
+
try {
|
|
102
|
+
fd = openSync(real, 'r');
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return { ok: false, id: includePath, denial: 'not-found' };
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
const stat = fstatSync(fd);
|
|
109
|
+
// Regular files only. A FIFO or a character device would block the read
|
|
110
|
+
// or stream without end, and neither is a document.
|
|
111
|
+
if (!stat.isFile())
|
|
112
|
+
return { ok: false, id: real, denial: 'not-a-file' };
|
|
113
|
+
const buffer = Buffer.allocUnsafe(stat.size);
|
|
114
|
+
let read = 0;
|
|
115
|
+
while (read < stat.size) {
|
|
116
|
+
const n = readSync(fd, buffer, read, stat.size - read, read);
|
|
117
|
+
if (n <= 0)
|
|
118
|
+
break;
|
|
119
|
+
read += n;
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
ok: true,
|
|
123
|
+
id: real,
|
|
124
|
+
source: buffer.subarray(0, read).toString('utf8'),
|
|
125
|
+
// Encoded bytes, not JavaScript characters, so the budget counts what
|
|
126
|
+
// the include-bomb actually costs.
|
|
127
|
+
bytes: read,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
finally {
|
|
131
|
+
closeSync(fd);
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=include-path.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"include-path.js","sourceRoot":"","sources":["../src/include-path.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|