123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310 |
- <!doctype html><!-- This is a valid HTML5 document. -->
- <!-- Screen readers, SEO, extensions and so on. -->
- <html lang="fr">
- <!-- Has to be within the first 1024 bytes, hence before the `title` element
- See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
- <meta charset="utf-8">
- <!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
- <!-- The viewport meta is quite crowded and we are responsible for that.
- See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
- <meta name="viewport" content="width=device-width,initial-scale=1">
- <!-- Required to make a valid HTML5 document. -->
- <title>HTTPWTF (archive) — David Larlet</title>
- <meta name="description" content="Publication mise en cache pour en conserver une trace.">
- <!-- That good ol' feed, subscribe :). -->
- <link rel="alternate" type="application/atom+xml" title="Feed" href="/david/log/">
- <!-- Generated from https://realfavicongenerator.net/ such a mess. -->
- <link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons2/apple-touch-icon.png">
- <link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons2/favicon-32x32.png">
- <link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons2/favicon-16x16.png">
- <link rel="manifest" href="/static/david/icons2/site.webmanifest">
- <link rel="mask-icon" href="/static/david/icons2/safari-pinned-tab.svg" color="#07486c">
- <link rel="shortcut icon" href="/static/david/icons2/favicon.ico">
- <meta name="msapplication-TileColor" content="#f7f7f7">
- <meta name="msapplication-config" content="/static/david/icons2/browserconfig.xml">
- <meta name="theme-color" content="#f7f7f7" media="(prefers-color-scheme: light)">
- <meta name="theme-color" content="#272727" media="(prefers-color-scheme: dark)">
- <!-- Documented, feel free to shoot an email. -->
- <link rel="stylesheet" href="/static/david/css/style_2021-01-20.css">
- <!-- See https://www.zachleat.com/web/comprehensive-webfonts/ for the trade-off. -->
- <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
- <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
- <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
- <link rel="preload" href="/static/david/css/fonts/triplicate_t3_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
- <link rel="preload" href="/static/david/css/fonts/triplicate_t3_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
- <link rel="preload" href="/static/david/css/fonts/triplicate_t3_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
- <script>
- function toggleTheme(themeName) {
- document.documentElement.classList.toggle(
- 'forced-dark',
- themeName === 'dark'
- )
- document.documentElement.classList.toggle(
- 'forced-light',
- themeName === 'light'
- )
- }
- const selectedTheme = localStorage.getItem('theme')
- if (selectedTheme !== 'undefined') {
- toggleTheme(selectedTheme)
- }
- </script>
-
- <meta name="robots" content="noindex, nofollow">
- <meta content="origin-when-cross-origin" name="referrer">
- <!-- Canonical URL for SEO purposes -->
- <link rel="canonical" href="https://httptoolkit.tech/blog/http-wtf/">
-
- <body class="remarkdown h1-underline h2-underline h3-underline em-underscore hr-center ul-star pre-tick" data-instant-intensity="viewport-all">
-
-
- <article>
- <header>
- <h1>HTTPWTF</h1>
- </header>
- <nav>
- <p class="center">
- <a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
- <use xlink:href="/static/david/icons2/symbol-defs.svg#icon-home"></use>
- </svg> Accueil</a> •
- <a href="https://httptoolkit.tech/blog/http-wtf/" title="Lien vers le contenu original">Source originale</a>
- </p>
- </nav>
- <hr>
- <p>HTTP is fundamental to modern development, from frontend to backend to mobile. But like any widespread mature standard, it's got some funky skeletons in the closet.</p>
- <p>Some of these skeletons are little-known but genuinely useful features, some of them are legacy oddities relied on by billions of connections daily, and some of them really shouldn't exist at all. Let's look behind the curtain:</p>
- <h2 id="no-cache-means-do-cache"><a href="#no-cache-means-do-cache" aria-label="no cache means do cache permalink" class="anchor"><svg aria-hidden="true" focusable="false" version="1.1" viewbox="0 0 16 16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>No-cache means "do cache"</h2>
- <p>Caching has never been easy, but HTTP cache headers can be particularly confusing. The worst examples of this are <code class="language-text">no-cache</code> and <code class="language-text">private</code>. What does the below response header do?</p>
- <div class="gatsby-highlight" data-language="text">
- <pre class="language-text"><code class="language-text">Cache-Control: private, no-cache</code></pre>
- </div>
- <p>It looks like this means "don't store this response anywhere", right?</p>
- <p><em>Hahaha</em> no.</p>
- <p>In reality, this means "please store this response in all browser caches, but revalidate it when using it". In fact, this makes responses <em>more</em> cacheable, because this applies even to responses that wouldn't normally be cacheable by default.</p>
- <p>Specifically, <code class="language-text">no-cache</code> means that your content is explicitly cacheable, but whenever a browser or CDN wants to use it, they should send a request using <code class="language-text">If-Match</code> or <code class="language-text">If-Modified-Since</code> to ask the server whether the cache is still up to date first. Meanwhile <code class="language-text">private</code> means that this content is cacheable, but only in end-client browsers, not CDNs or proxies.</p>
- <p>If you were trying to disable caching because the response contains security or privacy sensitive data that shouldn't be stored elsewhere, you're now in big trouble. In reality, you probably wanted <code class="language-text">no-store</code>.</p>
- <p>If you send a response including a <code class="language-text">Cache-Control: no-store</code> header, nobody will ever cache the response, and it'll come fresh from the server every time. The only edge case is if you send that when a client already has a cached response, which this won't remove. If you want to do that and clear existing caches too, add <code class="language-text">max-age=0</code>.</p>
- <p>Twitter notably <a href="https://hacks.mozilla.org/2020/04/twitter-direct-message-caching-and-firefox/">hit this issue</a>. They used <code class="language-text">Pragma: no-cache</code> (a legacy version of the same header) when they should have used <code class="language-text">Cache-Control: no-store</code>, and accidentally persisted every user's private direct messages in their browser caches. That's not a big problem on your own computer, but if you share a computer or you use Twitter on a public computer somewhere, you've now left all your private messages conveniently unencrypted & readable on the hard drive. Oops.</p>
- <h2 id="http-trailers"><a href="#http-trailers" aria-label="http trailers permalink" class="anchor"><svg aria-hidden="true" focusable="false" version="1.1" viewbox="0 0 16 16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>HTTP Trailers</h2>
- <p>You're probably aware of HTTP headers. An HTTP message starts with a first line that contains the method & URL (for requests) or status code & message (for responses) and then it has a series of key/value pairs for metadata, called headers, and then it has a body.</p>
- <p>Did you know you can also send trailers, to append metadata <em>after</em> a message body?</p>
- <p>These are not widely used, but they're fully standardized and in theory everything should support them, or at least ignore them. They can be useful if you have metadata that isn't easily available initially, and you don't want need to wait for it before you send the body.</p>
- <p>They are used in some API protocols like gRPC, and they're primarily valuable for metadata about the overall response itself, for example you can use trailers to <a href="https://www.fastly.com/blog/supercharging-server-timing-http-trailers">include Server-Timing metadata</a> to give the client performance metrics about server processing during a request, appended after the response is fully completed. They're especially useful for long responses, e.g. to include final status metadata after a long-running HTTP stream.</p>
- <p>It's still rare that you'll need this, but it's pretty cool that it works when you do. There's a few requirements:</p>
- <ul>
- <li>For server response trailers, the client must advertise support for this, with a <code class="language-text">TE: trailers</code> header on the initial request.</li>
- <li>The initial headers should specify the trailer fields that will be used later, with <code class="language-text">Trailer: <field names></code>.</li>
- <li>Some headers are never allowed as trailers, including <code class="language-text">Content-Length</code>, <code class="language-text">Cache-Control</code>, <code class="language-text">Authorization</code>, <code class="language-text">Host</code> and similar standard headers, which are often required initially to parse, authenticate or route requests.</li>
- </ul>
- <p>To send trailers in HTTP/1.1, you'll also need to use chunked encoding. HTTP/2 meanwhile uses separate frames for the body & headers, so this isn't necessary.</p>
- <p>A full HTTP/1.1 response with trailers might look like this:</p>
- <div class="gatsby-highlight" data-language="text">
- <pre class="language-text"><code class="language-text">HTTP/1.1 200 OK
- Transfer-Encoding: chunked
- Trailer: My-Trailer-Field
-
- [...chunked response body...]
-
- My-Trailer-Field: some-extra-metadata</code></pre>
- </div>
- <h2 id="http-1xx-codes"><a href="#http-1xx-codes" aria-label="http 1xx codes permalink" class="anchor"><svg aria-hidden="true" focusable="false" version="1.1" viewbox="0 0 16 16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>HTTP 1XX codes</h2>
- <p>Did you know that an HTTP request can receive multiple response status codes? A server can send an unlimited number of 1XX codes before a final status (200, 404, or whatever it may be). These act as interim responses, and can all include their own independent headers.</p>
- <p>There's a few different 1XX codes available: 100, 101, 102, and 103. They're not widely used, but in some niche use cases they have some cool powers:</p>
- <h3 id="http-100"><a href="#http-100" aria-label="http 100 permalink" class="anchor"><svg aria-hidden="true" focusable="false" version="1.1" viewbox="0 0 16 16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>HTTP 100</h3>
- <p>HTTP 100 is a response from a server that the request is ok <em>so far</em>, and the client should keep going.</p>
- <p>Most of the time, this is a no-op. If you've started sending a request, you were probably going to keep going anyway, although it's always nice to have the server's support & encouragement.</p>
- <p>This becomes useful though if you send a request including a <code class="language-text">Expect: 100-continue</code> header. That header tells the server you expect a 100 response, and you're not going to send the full request body until you receive it.</p>
- <p>Sending <code class="language-text">Expect: 100-continue</code> allows the server to decide if it wants to receive the whole body, which might take a lot of time/bandwidth. If the URL & headers are enough for it to already send a response (e.g. to reject a file upload) this is a quick and efficient way to do that. If the server does want to receive the full body, it sends an interim 100 response, the client continues, and then the server handles the complete request as normal when it's done.</p>
- <h3 id="http-101"><a href="#http-101" aria-label="http 101 permalink" class="anchor"><svg aria-hidden="true" focusable="false" version="1.1" viewbox="0 0 16 16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>HTTP 101</h3>
- <p>HTTP 101 is used to switch protocols. It says "I've sent you a URL and headers, and now I want to do something <em>completely different</em> with this connection". Not just a different request, but different protocol entirely.</p>
- <p>The main use case is to set up a websocket. To do so, the client sends a request including these two headers:</p>
- <div class="gatsby-highlight" data-language="text">
- <pre class="language-text"><code class="language-text">Connection: upgrade
- Upgrade: websocket</code></pre>
- </div>
- <p>Then, if the server accepts, it sends a response like:</p>
- <div class="gatsby-highlight" data-language="text">
- <pre class="language-text"><code class="language-text">HTTP/1.1 101 Switching Protocols
- Upgrade: websocket
- Connection: Upgrade</code></pre>
- </div>
- <p>And then from there they stop speaking HTTP, and start exchanging raw websocket data on this connection instead.</p>
- <p>This status is also used to upgrade from HTTP/1.1 to HTTP/2 on the same connection, and you could use it to transform HTTP connections into all sorts of other TCP-based protocols too.</p>
- <p>That said, this status <em>isn't</em> supported in HTTP/2, which uses a different mechanism for protocol negotiation and a <a href="https://tools.ietf.org/html/rfc8441">totally different mechanism</a> to set up websockets (which basically isn't supported anywhere - websockets are always HTTP/1.1 right now).</p>
- <h3 id="http-102"><a href="#http-102" aria-label="http 102 permalink" class="anchor"><svg aria-hidden="true" focusable="false" version="1.1" viewbox="0 0 16 16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>HTTP 102</h3>
- <p>HTTP 102 tells the client that the server is still processing the request, and it'll respond <em>soon</em>. This differs from 100 in that the whole request has now been received, and all the action is now happening on the server side, with the client just waiting.</p>
- <p>This isn't much used as far as I can tell, and it seems to mainly exist as a keep-alive, to make sure the client doesn't think the server has simply died. It's in the original HTTP specifications, but it's been removed from many new editions.</p>
- <p>Still, it is supported & used in real places in the wild, so it's quite possible to use it in your applications if it fits your needs.</p>
- <h3 id="http-103"><a href="#http-103" aria-label="http 103 permalink" class="anchor"><svg aria-hidden="true" focusable="false" version="1.1" viewbox="0 0 16 16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>HTTP 103</h3>
- <p>HTTP 103 meanwhile is a new & trendy status intended to partially replace HTTP/2's server push functionality (which is now <a href="https://groups.google.com/a/chromium.org/g/blink-dev/c/K3rYLvmQUBY/m/vOWBKZGoAQAJ?pli=1">being removed from Chrome</a>).</p>
- <p>Using HTTP 103, a server can send some headers early, before fully handling the request and sending the rest of the response. This is primarily designed for delivering link headers, like <code class="language-text">Link: </style.css>; rel=preload; as=style</code>, telling the client about other content that it may want to start loading early (like stylesheets, JS & images, for web page requests) in parallel with the full response.</p>
- <p>When the server receives a request that takes a little processing, it often can't fully send the response headers until that processing completes. HTTP 103 allows the server to immediately nudge the client to download other content in parallel, without waiting for the requested resource data to be ready.</p>
- <h2 id="referer"><a href="#referer" aria-label="referer permalink" class="anchor"><svg aria-hidden="true" focusable="false" version="1.1" viewbox="0 0 16 16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Referer</h2>
- <p>The HTTP Referer header tells the server which page you came from previously, or which URL triggered a resource load. This has some privacy challenges, but it's stuck around, and it's sent in most requests made as you browse the internet.</p>
- <p>Notably, it's spelled wrong. This was added in the very early days of the web, and the unix spell checker at the time didn't recognize either referer or referrer (the correct spelling). By the time anybody noticed, it was in serious use in infrastructure and tools all over the place, so nothing could be changed and we have to live with every browser request having a misspelled header forever.</p>
- <p>Not especially important unless you're writing code to read this header yourself, but a great parable for the challenges of network compatibility.</p>
- <p>For maximum confusion and damage potential, new privacy/security headers related to this like <code class="language-text">Referrer-Policy</code> <em>do</em> use the correct spelling.</p>
- <h2 id="websockets-random-uuid"><a href="#websockets-random-uuid" aria-label="websockets random uuid permalink" class="anchor"><svg aria-hidden="true" focusable="false" version="1.1" viewbox="0 0 16 16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Websocket's 'random' UUID</h2>
- <figure>
- <img alt="XKCD's getRandom() comic" src="https://imgs.xkcd.com/comics/random_number.png">
- <figcaption>There's always <a href="https://xkcd.com/221/">a relevant XKCD</a></figcaption>
- </figure>
- <p>We talked about how HTTP 101 requests are used to set up websockets earlier. A full request to do so might look like this:</p>
- <div class="gatsby-highlight" data-language="text">
- <pre class="language-text"><code class="language-text">GET /chat HTTP/1.1
- Host: server.example.com
- Upgrade: websocket
- Connection: Upgrade
- Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
- Sec-WebSocket-Protocol: chat, superchat
- Sec-WebSocket-Version: 13
- Origin: http://example.com</code></pre>
- </div>
- <p>with a response that starts the websocket connection like this:</p>
- <div class="gatsby-highlight" data-language="text">
- <pre class="language-text"><code class="language-text">HTTP/1.1 101 Switching Protocols
- Upgrade: websocket
- Connection: Upgrade
- Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
- Sec-WebSocket-Protocol: chat</code></pre>
- </div>
- <p>The <code class="language-text">Sec-WebSocket-Accept</code> key here is interesting. This is designed to stop caching proxies accidentally reusing websocket responses that they don't understand, by requiring the response to include a header that matches the client header. Specifically:</p>
- <ul>
- <li>The server receives a base64 websocket key from the client</li>
- <li>The server appends the UUID <code class="language-text">258EAFA5-E914-47DA-95CA-C5AB0DC85B11</code> to the base64 string</li>
- <li>The server hashes the resulting string, encodes the hash in base64, and sends that back</li>
- </ul>
- <p>This is deeply weird. A single fixed random UUID that's used in the setup of every single websocket forever? Appending strings to base64 strings without decoding, and then base64-ing the result again too?</p>
- <p>The idea is that this logic isn't something that could be happen by accident, or something that could ever be used elsewhere, to guarantee that both parties are intentionally starting a websocket connection. This confirms that the server or proxy isn't used cached data without understanding it, and the client hasn't been tricked into opening a websocket connection that it doesn't understand.</p>
- <p>This totally works, it's widely used and quick & easy to implement, which is all great, but it's wild that every websocket connection in the world relies on one magic UUID.</p>
- <h2 id="websockets--cors"><a href="#websockets--cors" aria-label="websockets cors permalink" class="anchor"><svg aria-hidden="true" focusable="false" version="1.1" viewbox="0 0 16 16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Websockets & CORS</h2>
- <p>While we're talking about websockets: did you know that websockets effectively ignore all the CORS and single-origin policy restrictions that would normally apply to HTTP requests?</p>
- <p>CORS ensures that JavaScript running on a.com can't read data from b.com unless the latter explicitly opts into that in its response headers.</p>
- <p>This is important for lots of reasons, notably including network-local servers (a public web page shouldn't be able to talk to your router) and browser state (requests from one domain shouldn't be able to use cookies from another).</p>
- <p>Unfortunately though, websockets ignore CORS entirely, assuming instead that all websocket servers are modern & sensible enough to correctly check the <code class="language-text">Origin</code> header for themselves. Many servers do not, and most developers I've mentioned this to weren't aware of it.</p>
- <p>This opens a whole world of fun vulnerabilities, nicely summarized in <a href="https://christian-schneider.net/CrossSiteWebSocketHijacking.html">this article</a>.</p>
- <p>In short: if you have a websocket API, check the <code class="language-text">Origin</code> header and/or use CSRF tokens before trusting any incoming connections.</p>
-
- <p>Once upon a time (1982) <a href="https://tools.ietf.org/html/rfc822#section-4.7.4">an RFC</a> suggested that using an <code class="language-text">X-</code> prefix for message headers was a good way to differentiate custom extensions from standardized names.</p>
- <p>At the time this was relevant to email metadata, but this was later popularized for usage in HTTP headers too.</p>
- <p>This is still a common pattern, and if you look at HTTP requests as you browse the web you'll see quite a few of these:</p>
- <ul>
- <li><code class="language-text">X-Shenanigans: none</code> - this appears on every response from Twilio's API. I have no idea why, but it is comforting to know there's <em>definitely</em> no shenanigans this time round.</li>
- <li><code class="language-text">X-Clacks-Overhead: GNU Terry Pratchett</code> - a <a href="https://xclacksoverhead.org/home/about">tribute</a> to Terry Pratchett, based on the message protocols within his own books.</li>
- <li><code class="language-text">X-Requested-With: XMLHttpRequest</code> - appended by various JS frameworks including jQuery, to clearly differentiate AJAX requests from resource requests (which can't include custom headers like this).</li>
- <li><code class="language-text">X-Recruiting: <cheesy pitch to get you to apply for a job></code> - quite a few companies add these as a quick way to try and hire the kind of people who read HTTP headers for fun.</li>
- <li><code class="language-text">X-Powered-By: <framework></code> - used to advertise the framework or technology that the server is using (usually a bad idea).</li>
- <li><code class="language-text">X-Http-Method-Override</code> - used to set a method that couldn't be set as the real method of the request for some reason, usually a client or networking limitation. Mostly a bad idea nowadays, but still popular & supported by quite a few frameworks.</li>
- <li><code class="language-text">X-Forwarded-For: <ip></code> - A defacto standard used by many proxies & load balancers to include the original requester's IP in upstream requests.</li>
- </ul>
- <p>Each of these is weird and wonderful in its own way, but the pattern in general is mostly a bad idea, and a new (2011) <a href="https://tools.ietf.org/html/draft-saintandre-xdash-00">RFC</a> now formally discourages its use.</p>
- <p>The problem is that many non-standard headers eventually do become standard. When that happens, if you used an <code class="language-text">X-</code> prefix, now you either have to change the name (breaking all existing implementations) or standardize the <code class="language-text">X-</code> prefix (defeating the point of the prefix entirely, and adding annoying noise to the name forever).</p>
- <p>This is frustrating, and it's broken some real standards:</p>
- <ul>
- <li>Almost all web forms on the internet submit data with an unnecessarily confusing & long-winded <code class="language-text">Content-Type: application/x-www-form-url-encoded</code> header.</li>
- <li>In the <a href="https://tools.ietf.org/html/rfc2068#section-3.5">1997 RFC for HTTP</a> where it defines the parsing rules for <code class="language-text">content-encoding</code>, it requires all implementations to treat <code class="language-text">x-gzip</code> and <code class="language-text">x-compress</code> as equivalent to <code class="language-text">gzip</code> and <code class="language-text">compress</code> respectively.</li>
- <li>The <a href="https://tools.ietf.org/html/rfc7034">standardized</a> header for configuring web page framing is now forever <code class="language-text">X-Frame-Options</code>, not just <code class="language-text">Frame-Options</code></li>
- <li>Similarly, we have <code class="language-text">X-Content-Type-Options</code>, <code class="language-text">X-DNS-Prefetch-Control</code>, <code class="language-text">X-XSS-Protection</code>, and various <code class="language-text">X-Forwarded-*</code> CDN/proxy headers, all of which are widely implemented and have become either formally or defacto standard headers in widespread use.</li>
- </ul>
- <p>If you want to use a custom header, just use a custom header name that's not standardized by anybody else. If you really want to avoid collisions, consider namespacing it, but you're usually pretty safe if there's no standard header that appears after a 30 second google.</p>
- <hr>
- <p>Standardization is <em>hard</em>, and HTTP is full of weird corners and odd details when you look closely. Let me know what you think on <a href="https://twitter.com/pimterry">Twitter</a>.</p>
- <p>Interested in inspecting & rewriting HTTP for yourself? <strong><a href="https://httptoolkit.tech">Try out HTTP Toolkit</a></strong>.</p>
- </article>
-
-
- <hr>
-
- <footer>
- <p>
- <a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
- <use xlink:href="/static/david/icons2/symbol-defs.svg#icon-home"></use>
- </svg> Accueil</a> •
- <a href="/david/log/" title="Accès au flux RSS"><svg class="icon icon-rss2">
- <use xlink:href="/static/david/icons2/symbol-defs.svg#icon-rss2"></use>
- </svg> Suivre</a> •
- <a href="http://larlet.com" title="Go to my English profile" data-instant><svg class="icon icon-user-tie">
- <use xlink:href="/static/david/icons2/symbol-defs.svg#icon-user-tie"></use>
- </svg> Pro</a> •
- <a href="mailto:david%40larlet.fr" title="Envoyer un courriel"><svg class="icon icon-mail">
- <use xlink:href="/static/david/icons2/symbol-defs.svg#icon-mail"></use>
- </svg> Email</a> •
- <abbr class="nowrap" title="Hébergeur : Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33184162340"><svg class="icon icon-hammer2">
- <use xlink:href="/static/david/icons2/symbol-defs.svg#icon-hammer2"></use>
- </svg> Légal</abbr>
- </p>
- <template id="theme-selector">
- <form>
- <fieldset>
- <legend><svg class="icon icon-brightness-contrast">
- <use xlink:href="/static/david/icons2/symbol-defs.svg#icon-brightness-contrast"></use>
- </svg> Thème</legend>
- <label>
- <input type="radio" value="auto" name="chosen-color-scheme" checked> Auto
- </label>
- <label>
- <input type="radio" value="dark" name="chosen-color-scheme"> Foncé
- </label>
- <label>
- <input type="radio" value="light" name="chosen-color-scheme"> Clair
- </label>
- </fieldset>
- </form>
- </template>
- </footer>
- <script src="/static/david/js/instantpage-5.1.0.min.js" type="module"></script>
- <script>
- function loadThemeForm(templateName) {
- const themeSelectorTemplate = document.querySelector(templateName)
- const form = themeSelectorTemplate.content.firstElementChild
- themeSelectorTemplate.replaceWith(form)
-
- form.addEventListener('change', (e) => {
- const chosenColorScheme = e.target.value
- localStorage.setItem('theme', chosenColorScheme)
- toggleTheme(chosenColorScheme)
- })
-
- const selectedTheme = localStorage.getItem('theme')
- if (selectedTheme && selectedTheme !== 'undefined') {
- form.querySelector(`[value="${selectedTheme}"]`).checked = true
- }
- }
-
- const prefersColorSchemeDark = '(prefers-color-scheme: dark)'
- window.addEventListener('load', () => {
- let hasDarkRules = false
- for (const styleSheet of Array.from(document.styleSheets)) {
- let mediaRules = []
- for (const cssRule of styleSheet.cssRules) {
- if (cssRule.type !== CSSRule.MEDIA_RULE) {
- continue
- }
- // WARNING: Safari does not have/supports `conditionText`.
- if (cssRule.conditionText) {
- if (cssRule.conditionText !== prefersColorSchemeDark) {
- continue
- }
- } else {
- if (cssRule.cssText.startsWith(prefersColorSchemeDark)) {
- continue
- }
- }
- mediaRules = mediaRules.concat(Array.from(cssRule.cssRules))
- }
-
- // WARNING: do not try to insert a Rule to a styleSheet you are
- // currently iterating on, otherwise the browser will be stuck
- // in a infinite loop…
- for (const mediaRule of mediaRules) {
- styleSheet.insertRule(mediaRule.cssText)
- hasDarkRules = true
- }
- }
- if (hasDarkRules) {
- loadThemeForm('#theme-selector')
- }
- })
- </script>
- </body>
- </html>
|