Monorepo for wisp.place. A static site hosting service built on top of the AT Protocol. wisp.place
at redirects 5.9 kB view raw
1import { describe, test, expect } from 'bun:test' 2import { sanitizePath, extractBlobCid } from './utils' 3import { CID } from 'multiformats' 4 5describe('sanitizePath', () => { 6 test('allows normal file paths', () => { 7 expect(sanitizePath('index.html')).toBe('index.html') 8 expect(sanitizePath('css/styles.css')).toBe('css/styles.css') 9 expect(sanitizePath('images/logo.png')).toBe('images/logo.png') 10 expect(sanitizePath('js/app.js')).toBe('js/app.js') 11 }) 12 13 test('allows deeply nested paths', () => { 14 expect(sanitizePath('assets/images/icons/favicon.ico')).toBe('assets/images/icons/favicon.ico') 15 expect(sanitizePath('a/b/c/d/e/f.txt')).toBe('a/b/c/d/e/f.txt') 16 }) 17 18 test('removes leading slashes', () => { 19 expect(sanitizePath('/index.html')).toBe('index.html') 20 expect(sanitizePath('//index.html')).toBe('index.html') 21 expect(sanitizePath('///index.html')).toBe('index.html') 22 expect(sanitizePath('/css/styles.css')).toBe('css/styles.css') 23 }) 24 25 test('blocks parent directory traversal', () => { 26 expect(sanitizePath('../etc/passwd')).toBe('etc/passwd') 27 expect(sanitizePath('../../etc/passwd')).toBe('etc/passwd') 28 expect(sanitizePath('../../../etc/passwd')).toBe('etc/passwd') 29 expect(sanitizePath('css/../../../etc/passwd')).toBe('css/etc/passwd') 30 }) 31 32 test('blocks directory traversal in middle of path', () => { 33 expect(sanitizePath('images/../../../etc/passwd')).toBe('images/etc/passwd') 34 // Note: sanitizePath only filters out ".." segments, doesn't resolve paths 35 expect(sanitizePath('a/b/../c')).toBe('a/b/c') 36 expect(sanitizePath('a/../b/../c')).toBe('a/b/c') 37 }) 38 39 test('removes current directory references', () => { 40 expect(sanitizePath('./index.html')).toBe('index.html') 41 expect(sanitizePath('././index.html')).toBe('index.html') 42 expect(sanitizePath('css/./styles.css')).toBe('css/styles.css') 43 expect(sanitizePath('./css/./styles.css')).toBe('css/styles.css') 44 }) 45 46 test('removes empty path segments', () => { 47 expect(sanitizePath('css//styles.css')).toBe('css/styles.css') 48 expect(sanitizePath('css///styles.css')).toBe('css/styles.css') 49 expect(sanitizePath('a//b//c')).toBe('a/b/c') 50 }) 51 52 test('blocks null bytes', () => { 53 // Null bytes cause the entire segment to be filtered out 54 expect(sanitizePath('index.html\0.txt')).toBe('') 55 expect(sanitizePath('test\0')).toBe('') 56 // Null byte in middle segment 57 expect(sanitizePath('css/bad\0name/styles.css')).toBe('css/styles.css') 58 }) 59 60 test('handles mixed attacks', () => { 61 expect(sanitizePath('/../../../etc/passwd')).toBe('etc/passwd') 62 expect(sanitizePath('/./././../etc/passwd')).toBe('etc/passwd') 63 expect(sanitizePath('//../../.\0./etc/passwd')).toBe('etc/passwd') 64 }) 65 66 test('handles edge cases', () => { 67 expect(sanitizePath('')).toBe('') 68 expect(sanitizePath('/')).toBe('') 69 expect(sanitizePath('//')).toBe('') 70 expect(sanitizePath('.')).toBe('') 71 expect(sanitizePath('..')).toBe('') 72 expect(sanitizePath('../..')).toBe('') 73 }) 74 75 test('preserves valid special characters in filenames', () => { 76 expect(sanitizePath('file-name.html')).toBe('file-name.html') 77 expect(sanitizePath('file_name.html')).toBe('file_name.html') 78 expect(sanitizePath('file.name.html')).toBe('file.name.html') 79 expect(sanitizePath('file (1).html')).toBe('file (1).html') 80 expect(sanitizePath('file@2x.png')).toBe('file@2x.png') 81 }) 82 83 test('handles Unicode characters', () => { 84 expect(sanitizePath('文件.html')).toBe('文件.html') 85 expect(sanitizePath('файл.html')).toBe('файл.html') 86 expect(sanitizePath('ファイル.html')).toBe('ファイル.html') 87 }) 88}) 89 90describe('extractBlobCid', () => { 91 const TEST_CID = 'bafkreid7ybejd5s2vv2j7d4aajjlmdgazguemcnuliiyfn6coxpwp2mi6y' 92 93 test('extracts CID from IPLD link', () => { 94 const blobRef = { $link: TEST_CID } 95 expect(extractBlobCid(blobRef)).toBe(TEST_CID) 96 }) 97 98 test('extracts CID from typed BlobRef with CID object', () => { 99 const cid = CID.parse(TEST_CID) 100 const blobRef = { ref: cid } 101 const result = extractBlobCid(blobRef) 102 expect(result).toBe(TEST_CID) 103 }) 104 105 test('extracts CID from typed BlobRef with IPLD link', () => { 106 const blobRef = { 107 ref: { $link: TEST_CID } 108 } 109 expect(extractBlobCid(blobRef)).toBe(TEST_CID) 110 }) 111 112 test('extracts CID from untyped BlobRef', () => { 113 const blobRef = { cid: TEST_CID } 114 expect(extractBlobCid(blobRef)).toBe(TEST_CID) 115 }) 116 117 test('returns null for invalid blob ref', () => { 118 expect(extractBlobCid(null)).toBe(null) 119 expect(extractBlobCid(undefined)).toBe(null) 120 expect(extractBlobCid({})).toBe(null) 121 expect(extractBlobCid('not-an-object')).toBe(null) 122 expect(extractBlobCid(123)).toBe(null) 123 }) 124 125 test('returns null for malformed objects', () => { 126 expect(extractBlobCid({ wrongKey: 'value' })).toBe(null) 127 expect(extractBlobCid({ ref: 'not-a-cid' })).toBe(null) 128 expect(extractBlobCid({ ref: {} })).toBe(null) 129 }) 130 131 test('handles nested structures from AT Proto API', () => { 132 // Real structure from AT Proto 133 const blobRef = { 134 $type: 'blob', 135 ref: CID.parse(TEST_CID), 136 mimeType: 'text/html', 137 size: 1234 138 } 139 expect(extractBlobCid(blobRef)).toBe(TEST_CID) 140 }) 141 142 test('handles BlobRef with additional properties', () => { 143 const blobRef = { 144 ref: { $link: TEST_CID }, 145 mimeType: 'image/png', 146 size: 5678, 147 someOtherField: 'value' 148 } 149 expect(extractBlobCid(blobRef)).toBe(TEST_CID) 150 }) 151 152 test('prioritizes checking IPLD link first', () => { 153 // Direct $link takes precedence 154 const directLink = { $link: TEST_CID } 155 expect(extractBlobCid(directLink)).toBe(TEST_CID) 156 }) 157 158 test('handles CID v0 format', () => { 159 const cidV0 = 'QmZ4tDuvesekSs4qM5ZBKpXiZGun7S2CYtEZRB3DYXkjGx' 160 const blobRef = { $link: cidV0 } 161 expect(extractBlobCid(blobRef)).toBe(cidV0) 162 }) 163 164 test('handles CID v1 format', () => { 165 const cidV1 = 'bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi' 166 const blobRef = { $link: cidV1 } 167 expect(extractBlobCid(blobRef)).toBe(cidV1) 168 }) 169})