Merge pull request #43 from ma15803216102/fix/html-preview-windows-drive-path
fix(html): Windows 盘符路径解码去掉前导斜杠,修复 html 预览被 isWithin 拒绝
This commit is contained in:
+10
-1
@@ -66,5 +66,14 @@ export function decodeHtmlUrl(pathname: string): HtmlDecodeResult {
|
|||||||
if (sessionId === undefined || sessionId === '' || pathSegments.length === 0 || pathSegments.some(segment => segment === '')) {
|
if (sessionId === undefined || sessionId === '' || pathSegments.length === 0 || pathSegments.some(segment => segment === '')) {
|
||||||
return { ok: false, status: 400, message: 'sessionId and file path are required' }
|
return { ok: false, status: 400, message: 'sessionId and file path are required' }
|
||||||
}
|
}
|
||||||
return { ok: true, ref: { sessionId, path: `/${pathSegments.join('/')}` } }
|
// A Windows drive segment ('D:') is the FIRST path segment of an encoded
|
||||||
|
// drive path. Rejoining it with a leading slash would yield '/D:/work/...'
|
||||||
|
// which node's path.resolve() mangles into 'D:\D:\work\...' on Windows —
|
||||||
|
// the html route's isWithin(cwd) fence would then reject every drive path.
|
||||||
|
// Keep the drive form slash-free so requireAbsolute() resolves it verbatim.
|
||||||
|
const first = pathSegments[0] ?? ''
|
||||||
|
const path = /^[A-Za-z]:$/.test(first)
|
||||||
|
? pathSegments.join('/')
|
||||||
|
: `/${pathSegments.join('/')}`
|
||||||
|
return { ok: true, ref: { sessionId, path } }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
* the session scope intact (the WHY of the path-encoding design).
|
* the session scope intact (the WHY of the path-encoding design).
|
||||||
*/
|
*/
|
||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
|
import { resolve } from 'node:path'
|
||||||
import { decodeHtmlUrl, encodeHtmlUrl } from '../src/html-route.ts'
|
import { decodeHtmlUrl, encodeHtmlUrl } from '../src/html-route.ts'
|
||||||
|
|
||||||
describe('encodeHtmlUrl', () => {
|
describe('encodeHtmlUrl', () => {
|
||||||
@@ -38,11 +39,25 @@ describe('decodeHtmlUrl', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('decodes a Windows round-trip', () => {
|
it('decodes a Windows round-trip (drive path WITHOUT a leading slash, so node resolve() keeps the drive)', () => {
|
||||||
const url = encodeHtmlUrl('sess-1', 'C:\\Users\\me\\a.html')
|
const url = encodeHtmlUrl('sess-1', 'C:\\Users\\me\\a.html')
|
||||||
expect(decodeHtmlUrl(url)).toEqual({
|
expect(decodeHtmlUrl(url)).toEqual({
|
||||||
ok: true,
|
ok: true,
|
||||||
ref: { sessionId: 'sess-1', path: '/C:/Users/me/a.html' },
|
ref: { sessionId: 'sess-1', path: 'C:/Users/me/a.html' },
|
||||||
|
})
|
||||||
|
// The host runs requireAbsolute() over the decoded path: with the old
|
||||||
|
// '/C:/...' form node's resolve() produced 'C:\C:\Users\...' (drive
|
||||||
|
// doubled) on Windows and the isWithin(cwd) fence rejected every drive
|
||||||
|
// path. The slash-free form must resolve back to the original path.
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
expect(resolve('C:/Users/me/a.html')).toBe('C:\\Users\\me\\a.html')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('decodes a lowercase-drive Windows path without a leading slash', () => {
|
||||||
|
expect(decodeHtmlUrl('/sidebar/html/s/d%3A/work/x.html')).toEqual({
|
||||||
|
ok: true,
|
||||||
|
ref: { sessionId: 's', path: 'd:/work/x.html' },
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user