Worker fetch() mangles Location Header url on redirect from origin

I am having a problem. I have a 307 redirect from Origin that works fine in a user Chrome 80-something. 307’s location header is the next GET method, works fine and gets me the second page.

In a CFW, the SAME byte identical 307 redirect from Origin to a CFW, causes a 404 (wrong URL) on second page, since something inside the CFW framework is mangling the URL before the second fetch.

I’ve isolated the problem to a test example.

addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request))
})

/**
 * Fetch and log a given request object
 * @param {Request} request
 */
async function handleRequest(request) {
  request = new Request('https://nghttp2.org/httpbin/redirect-to?status_code=307&url=https://httpbin.org/anything/http://example.com/')
  const response = await fetch(request)
  return response
}

In chrome the page body says

  "json": null, 
  "method": "GET", 
  "origin": "24.111.111.111", 
  "url": "https://httpbin.org/anything/http://example.com/"
}

which is correct, but if I fetch the page through the very basic CFW above (both IPs below are CF ASNs) I get

  "json": null, 
  "method": "GET", 
  "origin": "2a06:98c0:3600::103, 172.70.114.48", 
  "url": "https://httpbin.org/anything/http:/example.com/"
}

good “https://httpbin.org/anything/http://example.com/
vs
bad “https://httpbin.org/anything/http:/example.com/

how did CF loose a character in my URL in their backend HTTP gateway? bug? what other characters disappear from CF WAF backend URLs if “/” disappears? Was the backend URL fetcher ever fuzzed by CF?

A very liberal, defacto, reading of the HTTP 1.1 spec says a URL can have any one or more bytes of any value, except 0x20/space, where space is the URL terminator.

A more conservative, defacto, reading says a URL is terminated (or must end by having a forbidden char) by

ASCII whitespace is U+0009 TAB, U+000A LF, U+000C FF, U+000D CR, or U+0020 SPACE.

Base64-ing the GET API argument seems like the wrong way out. I’d expect the URL can contain any bytes except 0x00 and raw white space.

Im not sure what to do other than write a regexp for “:/”->"://" on my API endpoint origin before submitting the URL to the (not mine) framework URL parser in my origin software. My testing shows a CFW origin backend will replace all instances of 2 or more “/” in a row with ONE “/”, anywhere in the URL. // → /, /// → /, //// → /.

The slash disappearing is a known bug in the URL implementation right now. There is work being done to make sure all implementations are up-to-spec so I do expect this to be fixed soon:tm: