fix(i18n): encode exact link paths safely
This commit is contained in:
parent
0a1a1b9379
commit
ae9b69287f
2 changed files with 12 additions and 6 deletions
|
|
@ -27,8 +27,8 @@ function fixture(): string {
|
||||||
writeFileSync(join(root, 'docs/guide.zh.md'), '# 指南\n')
|
writeFileSync(join(root, 'docs/guide.zh.md'), '# 指南\n')
|
||||||
writeFileSync(join(root, 'docs/reference.md'), '# Overview\n')
|
writeFileSync(join(root, 'docs/reference.md'), '# Overview\n')
|
||||||
writeFileSync(join(root, 'docs/reference.zh.md'), '# 概览\n')
|
writeFileSync(join(root, 'docs/reference.zh.md'), '# 概览\n')
|
||||||
writeFileSync(join(root, 'docs/a#b.md'), '# Reserved\n')
|
writeFileSync(join(root, 'docs/a)#?b.md'), '# Reserved\n')
|
||||||
writeFileSync(join(root, 'docs/a#b.zh.md'), '# 保留字符\n')
|
writeFileSync(join(root, 'docs/a)#?b.zh.md'), '# 保留字符\n')
|
||||||
writeFileSync(join(root, 'docs/unpaired.md'), '# Only\n')
|
writeFileSync(join(root, 'docs/unpaired.md'), '# Only\n')
|
||||||
writeFileSync(join(root, 'docs/section/index.md'), '# Section\n')
|
writeFileSync(join(root, 'docs/section/index.md'), '# Section\n')
|
||||||
writeFileSync(join(root, 'docs/section/index.zh.md'), '# 章节\n')
|
writeFileSync(join(root, 'docs/section/index.zh.md'), '# 章节\n')
|
||||||
|
|
@ -87,11 +87,11 @@ describe('translation link locale validation', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('keeps URL-reserved filename bytes escaped in an encoded exact path', () => {
|
it('encodes each exact path segment with only RFC 3986 unreserved characters', () => {
|
||||||
const root = fixture()
|
const root = fixture()
|
||||||
const input = '[保留](a%23b%2Emd?view=full#section)\n'
|
const input = '[保留](a%29%23%3Fb%2Emd?view=full#section)\n'
|
||||||
expect(rewriteTranslationLinkLocales(input, linkContext(root, 'docs/guide.zh.md'))).toEqual({
|
expect(rewriteTranslationLinkLocales(input, linkContext(root, 'docs/guide.zh.md'))).toEqual({
|
||||||
content: '[保留](a%23b.zh.md?view=full#section)\n',
|
content: '[保留](a%29%23%3Fb.zh.md?view=full#section)\n',
|
||||||
rewritten: 1,
|
rewritten: 1,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -143,13 +143,19 @@ function translationPairTarget(targetPath: string, context: TranslationLinkConte
|
||||||
return { source, zh }
|
return { source, zh }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function encodePathSegment(segment: string): string {
|
||||||
|
return encodeURIComponent(segment).replace(/[!'()*]/g, character => (
|
||||||
|
`%${character.charCodeAt(0).toString(16).toUpperCase()}`
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
function relativeExpectedPath(
|
function relativeExpectedPath(
|
||||||
context: TranslationLinkContext,
|
context: TranslationLinkContext,
|
||||||
expectedPath: string,
|
expectedPath: string,
|
||||||
rawPath: string,
|
rawPath: string,
|
||||||
): string {
|
): string {
|
||||||
const relative = posix.relative(posix.dirname(context.sourcePath), expectedPath)
|
const relative = posix.relative(posix.dirname(context.sourcePath), expectedPath)
|
||||||
const encoded = relative.split('/').map(segment => encodeURIComponent(segment)).join('/')
|
const encoded = relative.split('/').map(encodePathSegment).join('/')
|
||||||
return rawPath.startsWith('./') && !encoded.startsWith('.') ? `./${encoded}` : encoded
|
return rawPath.startsWith('./') && !encoded.startsWith('.') ? `./${encoded}` : encoded
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue