Fixed Image Proxy's broken image placeholders should include "no-cache" Cache Control HTTP headers

Affected version
all versions

DeltaHF

Well-known member
When a broken image is posted and processed by the XenForo image proxy, a "missing-image.png" file is shown in its place. This "missing-image.png" is not served with the optimal Cache-Control HTTP headers. For visitors using proxy servers (or website owners using caching services like Cloudflare), this can cause the missing-image.png file to be cached and displayed when the original file may now be available.

To demonstrate, we can compare the headers of a broken image and a successfully fetched remote image:

Broken Image


Code:
Request URL: https://xenforo.com/community/proxy.php?image=https%3A%2F%2Fupload.wikimedia.org%2Fbroken.png&hash=1a2cda33dc1f3005bbdfcc1d27e91abf
Request Method: GET
Status Code: 200 
Remote Address: 104.20.79.242:443
Referrer Policy: no-referrer-when-downgrade
cf-ray: 4a7248f05fc256f9-IAD
content-disposition: inline; filename="missing-image.png"
content-length: 1761
content-type: image/png; charset=utf-8
date: Sun, 10 Feb 2019 23:16:41 GMT
expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
heartbleed: NO; see http://heartbleedheader.com
server: cloudflare
status: 200
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
x-proxy-error: 5
Successful Image


Code:
Request URL: https://xenforo.com/community/proxy.php?image=https%3A%2F%2Fupload.wikimedia.org%2Fwikipedia%2Fen%2F7%2F77%2FXenForo.png&hash=8e59b236b1b34e927cf66d2ac9d25315
Request Method: GET
Status Code: 200 
Remote Address: 104.20.79.242:443
Referrer Policy: no-referrer-when-downgrade
cache-control: public
cf-ray: 4a7248f04fb056f9-IAD
content-disposition: inline; filename="XenForo.png"
content-length: 3759
content-type: image/png; charset=utf-8
date: Sun, 10 Feb 2019 23:16:41 GMT
etag: "de3a8d4a305a21b9684faddda24fc38e8cc39730"
expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
heartbleed: NO; see http://heartbleedheader.com
server: cloudflare
status: 200
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
The only difference is that the broken image has an "x-proxy-error: 5" header, which is not a standard HTTP header. It should also send send the standard no-cache "Cache-Control" headers:

Code:
Cache-Control: no-cache, no-store, must-revalidate
The lack of proper cache-control headers has implications for visitors behind proxy servers, but it can have a wider impact for XenForo boards which serve proxied images from their own content delivery networks.

This is particularly problematic for sites behind Cloudflare which use the service's Page Rules to cache image proxy files. It is generally recommended to setup a "Cache Everything" Page Rule for proxy.php requests, so proxied images can be saved, stored, and compressed by Cloudflare. This can greatly reduce the number of requests to proxy.php (I use this on my own site and over half of my proxy.php requests are handled by Cloudflare) and allows you to take better advantage of their image optimization features like Polish and Mirage.

However, because XenForo's broken image placeholder does not return the proper Cache-Control headers, Cloudflare will cache "missing-image.png" in the file's place. Assuming the image did not load because of a problematic origin server (like several of us have recently experienced with Flickr), Cloudflare will not forward further requests for the image to XenForo, and XenForo's re-fetching algorithm will never have a chance to work. (It's actually worse than this — because other Cloudflare POPs will not have the missing-image.png cached yet, they will make the request back to proxy.php and may successfully retrieve the file. This means the images could appear broken to users accessing the site via one Cloudflare POP, and not broken for users in other areas, creating further confusion.)

According to Cloudflare's own documentation, they will not cache a file which sends the proper "no-cache" directives, even if a Page Rule is set, thus preventing this from being an issue:


It's important to note that Cloudflare will still respect Origin caching directive with this Page Rule in effect.

Cache-Control headers sending "private","no-cache", or "max-age=0" or Expires header with a date in the past will override the rule and cause Cloudflare to NOT cache the resource. To override these you'd need to add another setting called "Edge Cache TTL" and set it to however long you'd like Cloudflare to cache that resource on our servers.
I know this is a problem because I have experienced it myself on my own site with Cloudflare (including confused users who access my site via different Cloudflare POPs). It was very difficult to troubleshoot and figure out what was going on. For now, I have written a custom Cloudflare Worker which runs on requests to my forum's proxy.php. It inspects the response headers of each request to see if XenForo has returned "missing-image.php" and prevents Cloudflare from caching this file.

If XenForo sent the proper headers, this custom Worker script would not be required, and it would prevent other webmasters from experiencing this confusing situation as well.
 

XF Bug Bot

XenForo bug fixer bot
Staff member
Thank you for reporting this issue. It has now been resolved and we are aiming to include it in a future XF release (2.1.1).

Change log:
Send cache-control: no-cache for error images displayed by the image proxy. For successful fetches, set the max-age of the result based on when the next refresh is planned (and if unknown, cache for a day).
Any changes made as a result of this issue being resolved may not be rolled out here until later.
 
Top