Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions apps/xftp-server/static/xftp-web-bundle/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 35 additions & 1 deletion tests/XFTPWebTests.hs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
module XFTPWebTests (xftpWebTests) where

import Control.Concurrent (forkIO, newEmptyMVar, putMVar, takeMVar)
import Control.Monad (replicateM, when)
import Control.Monad (forM_, replicateM, when)
import Crypto.Error (throwCryptoError)
import qualified Crypto.PubKey.Curve25519 as X25519
import qualified Crypto.PubKey.Ed25519 as Ed25519
Expand Down Expand Up @@ -170,6 +170,7 @@ jsOut expr = "process.stdout.write(Buffer.from(" <> expr <> "));"

xftpWebTests :: IO () -> Spec
xftpWebTests dbCleanup = do
xftpWebSourceHygieneTests
distExists <- runIO $ doesDirectoryExist (xftpWebDir <> "/dist")
if distExists
then do
Expand All @@ -193,6 +194,19 @@ xftpWebTests dbCleanup = do
it "skipped (run 'cd xftp-web && npm install && npm run build' first)" $
pendingWith "TS project not compiled"

xftpWebSourceHygieneTests :: Spec
xftpWebSourceHygieneTests = describe "source hygiene" $
it "gates XFTP web downloads on first-replica servers" $ do
let expectations =
[ ("xftp-web/src/protocol/address.ts", "export function getDownloadServers"),
("xftp-web/web/download.ts", "getDownloadServers(fd)"),
("apps/xftp-server/static/xftp-web-bundle/index.js", "function getDownloadServers"),
("apps/xftp-server/static/xftp-web-bundle/index.js", "getDownloadServers(fd)")
]
forM_ expectations $ \(path, marker) -> do
contents <- B.readFile path
contents `shouldSatisfy` B.isInfixOf marker

-- ── protocol/encoding ──────────────────────────────────────────────

tsEncodingTests :: Spec
Expand Down Expand Up @@ -2828,6 +2842,26 @@ tsAddressTests = describe "protocol/address" $ do
<> jsOut "new TextEncoder().encode(s.host + ':' + s.port)"
result `shouldBe` "host1.com:5000"

it "getDownloadServers returns only first replicas" $ do
result <-
callNode $
impAddr
<> "const kh = 'LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=';\
\const fd = {chunks: [\
\ {replicas: [\
\ {server: `xftp://${kh}@first.example:443`},\
\ {server: `xftp://${kh}@allowed.example:443`}\
\ ]},\
\ {replicas: [\
\ {server: `xftp://${kh}@second.example:443`},\
\ {server: `xftp://${kh}@allowed.example:443`}\
\ ]}\
\]};\
\const allHosts = Addr.getDescriptionServers(fd).map(s => s.host).join(',');\
\const downloadHosts = Addr.getDownloadServers(fd).map(s => s.host).join(',');"
<> jsOut "new TextEncoder().encode(allHosts + '|' + downloadHosts)"
result `shouldBe` "first.example,allowed.example,second.example|first.example,second.example"

-- ── integration ───────────────────────────────────────────────────

tsIntegrationTests :: IO () -> Spec
Expand Down
23 changes: 19 additions & 4 deletions xftp-web/src/protocol/address.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,30 @@ export function getDescriptionServers(fd: {chunks: {replicas: {server: string}[]
const servers: XFTPServer[] = []
for (const chunk of fd.chunks) {
for (const replica of chunk.replicas) {
if (!seen.has(replica.server)) {
seen.add(replica.server)
servers.push(parseXFTPServer(replica.server))
}
addServer(servers, seen, replica.server)
}
}
return servers
}

// Extract unique XFTP servers that downloadFileRaw will actually contact.
export function getDownloadServers(fd: {chunks: {replicas: {server: string}[]}[]}): XFTPServer[] {
const seen = new Set<string>()
const servers: XFTPServer[] = []
for (const chunk of fd.chunks) {
const replica = chunk.replicas[0]
if (replica) addServer(servers, seen, replica.server)
}
return servers
}

function addServer(servers: XFTPServer[], seen: Set<string>, address: string) {
if (!seen.has(address)) {
seen.add(address)
servers.push(parseXFTPServer(address))
}
}

// Build an HTTPS origin from an XFTP server address.
export function serverOrigin(server: XFTPServer): string {
return server.port === "443" ? `https://${server.host}` : `https://${server.host}:${server.port}`
Expand Down
4 changes: 2 additions & 2 deletions xftp-web/web/download.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
newXFTPAgent, closeXFTPAgent,
decodeDescriptionURI, downloadFileRaw
} from '../src/agent.js'
import {getDescriptionServers} from '../src/protocol/address.js'
import {getDownloadServers} from '../src/protocol/address.js'
import {XFTPPermanentError} from '../src/client.js'

const DECRYPT_WEIGHT = 0.15
Expand All @@ -22,7 +22,7 @@ export function initDownload(app: HTMLElement, hash: string) {
return
}

const wrongServer = !getDescriptionServers(fd).map(s => s.host).includes(window.location.hostname)
const wrongServer = !getDownloadServers(fd).map(s => s.host).includes(window.location.hostname)

const size = fd.redirect ? fd.redirect.size : fd.size
app.innerHTML = `
Expand Down