Skip to content

Commit 7c70c5b

Browse files
committed
fix: added encoding on absolutePointer before decodeURIComponent
1 parent ca9197b commit 7c70c5b

3 files changed

Lines changed: 46 additions & 4 deletions

File tree

.changeset/solid-lands-repair.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@redocly/openapi-core": patch
3+
---
4+
5+
Fixed an issue where JSON Pointers containing special characters (like `%`) were not properly URI-encoded when used as URI identifiers, causing validation errors with properties containing percent signs or other special characters.

packages/core/src/__tests__/ref-utils.test.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import outdent from 'outdent';
22
import { parseYamlToDocument } from '../../__tests__/utils.js';
3-
import { parseRef, refBaseName } from '../ref-utils.js';
3+
import { escapePointer, parseRef, refBaseName, unescapePointerFromURI } from '../ref-utils.js';
44
import { lintDocument } from '../lint.js';
55
import { createConfig } from '../config/index.js';
66
import { BaseResolver } from '../resolve.js';
@@ -139,4 +139,37 @@ describe('ref-utils', () => {
139139
expect(refBaseName('abcdefg')).toStrictEqual('abcdefg');
140140
});
141141
});
142+
143+
describe('escapePointer', () => {
144+
it('should escape a pointer correctly', () => {
145+
expect(escapePointer('scope/complex~name')).toStrictEqual('scope~1complex~0name');
146+
});
147+
148+
it('should escape a pointer with a number correctly', () => {
149+
expect(escapePointer(123)).toStrictEqual(123);
150+
});
151+
152+
it('should escape a pointer with a string correctly', () => {
153+
expect(escapePointer('scope/complex~name')).toStrictEqual('scope~1complex~0name');
154+
});
155+
156+
it('should not URI-encode special characters when escaping pointers', () => {
157+
// escapePointer should only handle JSON Pointer escaping, not URI encoding
158+
expect(escapePointer('activity_level_%')).toStrictEqual('activity_level_%');
159+
});
160+
});
161+
162+
describe('unescapePointerFromURI', () => {
163+
it('should unescape a pointer with a percent sign correctly', () => {
164+
expect(unescapePointerFromURI('activity_level_%25')).toStrictEqual('activity_level_%');
165+
});
166+
167+
it('should unescape a pointer with a number correctly', () => {
168+
expect(unescapePointerFromURI('123')).toStrictEqual('123');
169+
});
170+
171+
it('should unescape a pointer correctly', () => {
172+
expect(unescapePointerFromURI('scope~1complex~0name')).toStrictEqual('scope/complex~name');
173+
});
174+
});
142175
});

packages/core/src/ref-utils.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,16 @@ export class Location {
3636
}
3737

3838
get absolutePointer() {
39-
return this.source.absoluteRef + (this.pointer === '#/' ? '' : this.pointer);
39+
return this.source.absoluteRef + (this.pointer === '#/' ? '' : encodeURI(this.pointer));
4040
}
4141
}
4242

4343
export function unescapePointer(fragment: string): string {
44-
return decodeURIComponent(fragment.replace(/~1/g, '/').replace(/~0/g, '~'));
44+
return fragment.replace(/~1/g, '/').replace(/~0/g, '~');
45+
}
46+
47+
export function unescapePointerFromURI(fragment: string): string {
48+
return unescapePointer(decodeURIComponent(fragment));
4549
}
4650

4751
export function escapePointer<T extends string | number>(fragment: T): T {
@@ -58,7 +62,7 @@ export function parseRef(ref: string): { uri: string | null; pointer: string[] }
5862
}
5963

6064
export function parsePointer(pointer: string) {
61-
return pointer.split('/').map(unescapePointer).filter(isTruthy);
65+
return pointer.split('/').map(unescapePointerFromURI).filter(isTruthy);
6266
}
6367

6468
export function pointerBaseName(pointer: string) {

0 commit comments

Comments
 (0)