fix encoded asset paths in hosting service
11 unmodified lines1213141516161718192035 unmodified lines56575859606162636465626667686970132 unmodified linesExpand all11 unmodified linesimport { cache } from './lib/cache-manager'import { getCustomDomain, getCustomDomainByHash, getWispDomain } from './lib/db'import { serveFromCache, serveFromCacheWithRewrite } from './lib/file-serving'import { privateNotFound, servePrivateSite } from './lib/private-serving'import { extractHeaders, isValidRkey } from './lib/request-utils'import { decodeRequestPathname, extractHeaders, isValidRkey } from './lib/request-utils'import { resolveDid } from './lib/utils'
const logger = createLogger('hosting-service')
35 unmodified lines
// Main site serving routeapp.get('/*', async (c) => { const url = new URL(c.req.url) const decodedPathname = decodeRequestPathname(url.pathname) if (decodedPathname === null) { return c.text('Invalid URL encoding', 400) } const hostname = c.req.header('host') || '' const hostnameWithoutPort = hostname.split(':')[0] || '' const rawPath = url.pathname.replace(/^\//, '') const rawPath = decodedPathname.replace(/^\//, '') const hasTrailingSlash = rawPath.endsWith('/') const path = hasTrailingSlash ? `${sanitizePath(rawPath)}/` : sanitizePath(rawPath)
logger.debug(`Request: host=${hostname} hostnameWithoutPort=${hostnameWithoutPort} path=${path}`, { BASE_HOST })132 unmodified linesExpand all183 unmodified linesExpand all18418518618718818919019119219319419519619719819920020120220368 unmodified lines183 unmodified linesExpand all expect(await response.text()).toBe('direct markdown') expect(storageGetWithMetadataKeys.filter((key) => key === `${DID}/${RKEY}/direct.md`)).toHaveLength(1) })
test('serves decoded file names containing spaces', async () => { const path = '486x486bb 3.webp' storeFile(path, 'image bytes', 'image/webp') siteFileCids = { [path]: 'image-cid' }
const response = await serveFileInternal(DID, RKEY, path)
expect(response.status).toBe(200) expect(await response.text()).toBe('image bytes') expect(storageGetWithMetadataKeys).toContain(`${DID}/${RKEY}/${path}`) })
test('skips manifest-absent storage probes before clean URL html fallback', async () => { storeFile('modelapp.html', '<html>model app</html>') siteFileCids = { 'modelapp.html': 'modelapp-cid',68 unmodified lines1233456721 unmodified lines293031323334353637383940414243import { describe, expect, test } from 'bun:test'import type { Record as WispSettings } from '@wispplace/lexicons/types/place/wisp/settings'import { applyCustomHeaders } from './request-utils'import { applyCustomHeaders, decodeRequestPathname } from './request-utils'
const settings = { $type: 'place.wisp.settings', directoryListing: false,21 unmodified lines
expect(headers['Service-Worker-Allowed']).toBe('/') })})
describe('decodeRequestPathname', () => { test('decodes percent-encoded file names before storage lookup', () => { expect(decodeRequestPathname('/486x486bb%203.webp')).toBe('/486x486bb 3.webp') expect(decodeRequestPathname('/caf%C3%A9/photo.webp')).toBe('/café/photo.webp') })
test('rejects malformed percent encoding', () => { expect(decodeRequestPathname('/invalid%2')).toBeNull() })})4 unmodified lines56789101112131415161718192085 unmodified lines4 unmodified linesimport type { Record as WispSettings } from '@wispplace/lexicons/types/place/wisp/settings'
const SHARED_ORIGIN_BLOCKED_HEADERS = new Set(['service-worker-allowed'])
export function decodeRequestPathname(pathname: string): string | null { try { return decodeURIComponent(pathname) } catch { return null }}
interface CustomHeaderOptions { sharedOrigin?: boolean}
85 unmodified lines