Scope the factorioprints API passthrough to the host that serves the API - #291
Conversation
wormeyman
left a comment
There was a problem hiding this comment.
The reasoning holds and both claims check out. I confirmed them separately: www.factorioprints.xyz answers the API at 200 with JSON, and factorioprints.com/api/blueprintData/... answers 6.4 KB of SPA index.html at 200.
Two things before it goes in, both about coverage rather than the code.
The change un-tests the clause it does not touch. The condition has two halves and only one of them is pinned now. I simulated each single-clause mutation against the three factorioprints tests:
| mutation | caught? |
|---|---|
| revert the whole change | caught, by your new test |
| drop the host check | caught |
drop pathParts[0] === 'api' |
survives all three |
drop .replace(/^www\./, '') |
survives all three |
On the base branch, deleting the path check failed factorioprints reads blueprintString off the firebase record, because the arm then passed everything through. The new host clause excludes .com on its own, so nothing holds the path clause up any more.
Nothing covers the www. strip. www.factorioprints appears under tests/ only at tests/corsproxy.test.ts:78, which asserts allowlist membership and never drives the host through bpString.ts. So the behaviour your allowlist line exists to support is asserted nowhere in the editor, and a www. API link would quietly take the firebase rewrite instead.
Two cases close both, and each one catches exactly one of the surviving mutations:
https://factorioprints.xyz/view/abc987, expect the firebase rewritehttps://www.factorioprints.xyz/api/blueprintData/xyz321/position/1.0, expect passthrough
Three smaller notes.
bpString.ts:302-303 still says this is "the same fork as the factorio.school arm below, and for the same reason". It is not any more, since one arm checks the host and the other does not. Worth a sentence saying which is which.
The comment at :310-311 says the fetch "fails outright". It does not. https://facorio-blueprints.firebaseio.com/blueprints/blueprintData.json answers 200 with the body null, so data.blueprintString throws a TypeError and the user gets "Blueprint string could not be loaded." That is worth fixing in the PR body too: both paths fail, so this swaps one error message for another rather than making a link work. The wins are the scoping itself and the www.xyz allowlist entry.
The commit body is empty. GitHub seeds the squash description from it, so the reasoning you put in the code comments will not reach the merged commit.
On window.__fbe_test: correct, and I checked it against a built bundle rather than the source. Worth knowing the repo already records it. The comment at packages/website/src/index.ts:435 says the hook is assigned unconditionally and sits on window in production, and exportGuardResult was rewritten for exactly that reason, since the earlier version performed a real clipboard write and PNG download for any script that called it. So it is a known state rather than a surprise. Filed as #292. Keeping it out of this PR was the right call.
The arm is reached by first hostname label alone, so the /api/ passthrough added in FactoryGameFan#276 fired for factorioprints.com as well as factorioprints.xyz. Only .xyz is the API; .com is the site, and answers its own index.html at status 200 for an /api/ path, which reaches decode as a page of HTML. This is not a link that starts working - a .com API link fails either way. The firebase rewrite it now keeps fetches blueprints/blueprintData.json, which answers 200 with a body of null, so data.blueprintString throws. What the scoping buys is that .com keeps the behaviour it had before FactoryGameFan#276 rather than quietly acquiring a new one. www.factorioprints.xyz serves the same API, so it joins the allowlist and the host check strips a leading www. Four spec cases now cover the arm, one per clause: .com/view/ for the arm itself, .com/api/ for the host check, .xyz/view/ for the path check, and www.xyz/api/ for the www strip. Each new one was checked against the mutation it exists for.
89bda29 to
d59a175
Compare
|
All four addressed. The mutation table was the useful part — I had not checked that the clause I did not touch was still held up by anything, and it wasn't. Coverage. Both cases added, and I ran your two surviving mutations against them rather than assuming:
Each mutation fails exactly one case and leaves the other three green, so the four cases now map one-to-one onto the arm and its two clauses. "fails outright". You are right, and I had not checked it: 200 with The "same fork" comment. Rewritten — it now says this arm is the narrower one, that factorio.school keys on the path alone while this one checks the host too, and why. Commit body. Written, covering the scoping, the null-record detail, the On #292: thanks for filing, and for the pointer to the |
wormeyman
left a comment
There was a problem hiding this comment.
All four addressed, and I re-measured rather than reading the table.
The mutation claims hold. I ran all four single-clause mutations against your head on real servers, one at a time:
| mutation | red |
|---|---|
| drop the host check | a factorioprints.com API url still takes the firebase rewrite |
drop pathParts[0] === 'api' |
a factorioprints.xyz /view/ url still reads the firebase record |
drop .replace(/^www\./, '') |
a www.factorioprints.xyz API url is passed through unchanged |
| drop the passthrough | the two /api/ cases |
Each clause mutation fails exactly one case and leaves the other three green, so the arm and its two clauses are each held up by something now.
The three network claims check out. blueprints/blueprintData.json is 200 with a 4-byte null, www.factorioprints.xyz/api/... is 200 JSON, and factorioprints.com/api/... is 200 text/html at 6409 bytes.
vp check clean, vp test 221 passed, blueprint-sources.spec.ts 15/15 locally.
One thing the PR undersells. factorioprints.xyz serves the site as well as the API - factorioprints.xyz/view/-Kn2afLokZdBO-uHcIAF answers 200 HTML and the key resolves in the same firebase record. So your .xyz /view/ case pins a URL a user can really paste, not only a mutation the code would otherwise not notice.
Filed rather than blocking: #293. The rewritten comment ended up disagreeing with itself. :307-309 says rewriting an API link "would drop the position and open the book"; :317-319 says that same rewrite 200s with null and throws. The second is right - pathParts[1] of an API path is blueprintData, so it fetches blueprints/blueprintData.json and never reaches the book. The old comment carried that detail and only its "fails outright" tail was wrong, so the rewrite dropped the accurate half along with it. One sentence. The issue also picks up the www. strip now being written at both :287 and :323, held up by two different tests.
Merging.
3b40c8e
into
FactoryGameFan:wormeyman-space-age-support
#294) The comment's first paragraph said rewriting an /api/blueprintData/<sha>/ position/<i> link to firebase "would drop the position and open the book"; its second paragraph said the same rewrite 200s with a null body and throws. Only the second is true - `pathParts[1]` of an API path is the literal `blueprintData`, so the rewrite fetches blueprints/blueprintData.json, a record that does not exist. Put the `pathParts[1]` explanation back (the pre-#291 comment had it) and drop the book consequence, so both paragraphs describe the same failure. Also hoist the `www.` strip into one `const host` read by both the switch discriminant and the factorioprints host check, instead of two copies that nothing keeps in agreement. Pure refactor, no behaviour change. While in there, note on `case 'factorio'` that it keys on the path alone because no other factorio.<tld> is known to reach it, not because that is inherently safe - #291 contrasted the two arms without saying so. Closes #293 Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Follow-up to #276, addressing the review it got there.
What this actually buys. Not a link that starts working — a
factorioprints.com/api/...link fails either way, and the honest framing is that this decides which failure. Passed through, it fetches the site's own index.html and dies in pako asincorrect header check. Sent down the firebase rewrite, it fetchesblueprints/blueprintData.json, which answers 200 with a body ofnull, anddata.blueprintStringthrows. What the change buys is that.comkeeps the behaviour it had before #276 rather than quietly acquiring a new one, and that the passthrough says out loud which host it is for.The finding. The
factorioprintsarm is reached by first hostname label alone, so the/api/passthrough #276 added fires forfactorioprints.comas well asfactorioprints.xyz. Only.xyzis the API;factorioprints.comis the site, and answers its ownindex.htmlat status 200 for an/api/path. Against the deployed editor:The nit.
www.factorioprints.xyzjoins the allowlist, and the host check strips a leadingwww.so both spellings pass through. Not hypothetical — that host serves the same API:Tests
Four cases now cover the arm, one per clause of the condition:
factorioprints.com/view/<key>→ firebase (pre-existing)factorioprints.com/api/...→ firebasefactorioprints.xyz/view/<key>→ firebasepathParts[0] === 'api'www.factorioprints.xyz/api/...→ passthrough.replace(/^www\./, '')Each of the two new ones was checked red against the mutation it is there for.
tests/corsproxy.test.tsalso gainswww.factorioprints.xyzin the pass-through host list.vp check,vp testand the fullblueprint-sourcesspec pass locally.