Monorepo for wisp.place. A static site hosting service built on top of the AT Protocol. wisp.place
86
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix extension less file serving

+38 -12
+38 -12
apps/hosting-service/src/lib/file-serving.ts
··· 28 28 type FileStorageResult = StorageResult<Uint8Array>; 29 29 30 30 /** 31 + * Check if the last segment of a path looks like it has a file extension. 32 + * e.g. "style.css" → true, "about" → false, "wisp-cli-x86_64-linux" → false, 33 + * "dir.name/file" → false, "dir/file.tar.gz" → true 34 + */ 35 + function hasFileExtension(path: string): boolean { 36 + const basename = path.split('/').pop() || ''; 37 + return /\.[a-zA-Z0-9]+$/.test(basename); 38 + } 39 + 40 + /** 31 41 * Helper to retrieve a file with metadata from tiered storage 32 42 * Logs which tier the file was served from 33 43 */ ··· 312 322 requestPath = requestPath.slice(0, -1); 313 323 } 314 324 315 - // For directory-like paths (empty or no extension), try index files FIRST (fast) 316 - // Only do expensive directory listing if needed for directory listing feature 317 - if (!requestPath || !requestPath.includes('.')) { 325 + // For directory-like paths (empty or no file extension in basename), try index files 326 + if (!requestPath || !hasFileExtension(requestPath)) { 327 + // For non-empty extensionless paths, try as a direct file first (e.g. binary downloads) 328 + if (requestPath) { 329 + const directResult = await span(trace, `storage:${requestPath}`, () => getFileWithMetadata(did, rkey, requestPath)); 330 + if (directResult) { 331 + return buildResponseFromStorageResult(directResult, requestPath, settings, requestHeaders); 332 + } 333 + await markExpectedMiss(requestPath); 334 + } 335 + 318 336 for (const indexFile of indexFiles) { 319 337 const indexPath = requestPath ? `${requestPath}/${indexFile}` : indexFile; 320 338 const result = await span(trace, `storage:${indexPath}`, () => getFileWithMetadata(did, rkey, indexPath)); ··· 342 360 // Fall through to normal file serving / 404 handling 343 361 } 344 362 345 - // Not a directory, try to serve as a file 363 + // Try to serve as a file 346 364 const fileRequestPath: string = requestPath || indexFiles[0] || 'index.html'; 347 365 348 366 // Retrieve from tiered storage ··· 354 372 await markExpectedMiss(fileRequestPath); 355 373 356 374 // Try index files for directory-like paths 357 - if (!fileRequestPath.includes('.')) { 375 + if (!hasFileExtension(fileRequestPath)) { 358 376 for (const indexFileName of indexFiles) { 359 377 const indexPath = fileRequestPath ? `${fileRequestPath}/${indexFileName}` : indexFileName; 360 378 const indexResult = await span(trace, `storage:${indexPath}`, () => getFileWithMetadata(did, rkey, indexPath)); ··· 366 384 } 367 385 368 386 // Try clean URLs: /about -> /about.html 369 - if (settings?.cleanUrls && !fileRequestPath.includes('.')) { 387 + if (settings?.cleanUrls && !hasFileExtension(fileRequestPath)) { 370 388 const htmlPath = `${fileRequestPath}.html`; 371 389 if (await storageExists(did, rkey, htmlPath)) { 372 390 return serveFileInternal(did, rkey, htmlPath, settings, requestHeaders, trace); ··· 615 633 requestPath = requestPath.slice(0, -1); 616 634 } 617 635 618 - // For directory-like paths (empty or no extension), try index files FIRST (fast) 619 - // Only do expensive directory listing if needed for directory listing feature 620 - if (!requestPath || !requestPath.includes('.')) { 636 + // For directory-like paths (empty or no file extension in basename), try index files 637 + if (!requestPath || !hasFileExtension(requestPath)) { 638 + // For non-empty extensionless paths, try as a direct file first (e.g. binary downloads) 639 + if (requestPath) { 640 + const directResult = await span(trace, `storage:${requestPath}`, () => getFileForRequest(did, rkey, requestPath, true)); 641 + if (directResult) { 642 + return buildResponseFromStorageResult(directResult.result, requestPath, settings, requestHeaders); 643 + } 644 + await markExpectedMiss(requestPath); 645 + } 646 + 621 647 for (const indexFile of indexFiles) { 622 648 const indexPath = requestPath ? `${requestPath}/${indexFile}` : indexFile; 623 649 const fileResult = await span(trace, `storage:${indexPath}`, () => getFileForRequest(did, rkey, indexPath, true)); ··· 645 671 // Fall through to normal file serving / 404 handling 646 672 } 647 673 648 - // Not a directory, try to serve as a file 674 + // Try to serve as a file 649 675 const fileRequestPath: string = requestPath || indexFiles[0] || 'index.html'; 650 676 651 677 const fileResult = await span(trace, `storage:${fileRequestPath}`, () => getFileForRequest(did, rkey, fileRequestPath, true)); ··· 655 681 await markExpectedMiss(fileRequestPath); 656 682 657 683 // Try index files for directory-like paths 658 - if (!fileRequestPath.includes('.')) { 684 + if (!hasFileExtension(fileRequestPath)) { 659 685 for (const indexFileName of indexFiles) { 660 686 const indexPath = fileRequestPath ? `${fileRequestPath}/${indexFileName}` : indexFileName; 661 687 const indexResult = await span(trace, `storage:${indexPath}`, () => getFileForRequest(did, rkey, indexPath, true)); ··· 667 693 } 668 694 669 695 // Try clean URLs: /about -> /about.html 670 - if (settings?.cleanUrls && !fileRequestPath.includes('.')) { 696 + if (settings?.cleanUrls && !hasFileExtension(fileRequestPath)) { 671 697 const htmlPath = `${fileRequestPath}.html`; 672 698 if (await storageExists(did, rkey, htmlPath)) { 673 699 return serveFileInternalWithRewrite(did, rkey, htmlPath, basePath, settings, requestHeaders, trace);