mirror of
https://github.com/stablyai/orca.git
synced 2026-10-17 16:02:55 +00:00
Keep repo icon href probes inside repo
This commit is contained in:
@@ -69,6 +69,25 @@ describe('detectRepoIcon', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('does not resolve declared icon hrefs outside the repo', async () => {
|
||||
const rootPath = await makeTempRepoDir()
|
||||
const repoPath = join(rootPath, 'repo')
|
||||
await mkdir(repoPath)
|
||||
await writeFile(join(repoPath, 'index.html'), '<link rel="icon" href="../outside.png">')
|
||||
await writeFile(join(rootPath, 'outside.png'), Buffer.from(PNG_1X1_BASE64, 'base64'))
|
||||
await writeFile(
|
||||
join(repoPath, 'package.json'),
|
||||
JSON.stringify({ homepage: 'https://app.example.com/docs' })
|
||||
)
|
||||
|
||||
await expect(detectRepoIcon({ repoPath, kind: 'folder' })).resolves.toEqual({
|
||||
type: 'image',
|
||||
src: 'https://www.google.com/s2/favicons?domain=app.example.com&sz=64',
|
||||
source: 'favicon',
|
||||
label: 'Website favicon'
|
||||
})
|
||||
})
|
||||
|
||||
it('falls back to the GitHub owner avatar for GitHub repos', async () => {
|
||||
const repoPath = await makeTempRepoDir()
|
||||
await gitExecFileAsync(['init'], { cwd: repoPath })
|
||||
|
||||
@@ -75,9 +75,35 @@ function extractIconHref(source: string): string | null {
|
||||
return source.match(LINK_ICON_HTML_RE)?.[1] ?? source.match(LINK_ICON_OBJECT_RE)?.[1] ?? null
|
||||
}
|
||||
|
||||
function normalizeIconRelativePath(relativePath: string): string | null {
|
||||
const normalized = relativePath.replace(/\\/g, '/').trim()
|
||||
if (
|
||||
!normalized ||
|
||||
normalized.startsWith('/') ||
|
||||
normalized.includes('\0') ||
|
||||
/^[A-Za-z][A-Za-z0-9+.-]*:/.test(normalized)
|
||||
) {
|
||||
return null
|
||||
}
|
||||
const segments = normalized.split('/').filter((segment) => segment && segment !== '.')
|
||||
if (segments.length === 0 || segments.some((segment) => segment === '..')) {
|
||||
return null
|
||||
}
|
||||
return segments.join('/')
|
||||
}
|
||||
|
||||
function iconHrefCandidates(href: string): string[] {
|
||||
const clean = href.replace(/^\/+/, '')
|
||||
return [`public/${clean}`, clean]
|
||||
const candidates: string[] = []
|
||||
for (const candidate of [`public/${clean}`, clean]) {
|
||||
// Why: icon hrefs are repo-controlled input; never let detection probe above
|
||||
// the selected repo root while looking for a best-effort visual.
|
||||
const normalized = normalizeIconRelativePath(candidate)
|
||||
if (normalized && !candidates.includes(normalized)) {
|
||||
candidates.push(normalized)
|
||||
}
|
||||
}
|
||||
return candidates
|
||||
}
|
||||
|
||||
async function readLocalPngIcon(repoPath: string, relativePath: string): Promise<RepoIcon | null> {
|
||||
|
||||
Reference in New Issue
Block a user