63

In chrome 22 & safari 6.

Loading images from s3 for usage in a canvas (with extraction as a primary intent) using a CORS enabled S3 bucket, with the following code:

<!-- In the html -->
<img src="http://s3....../bob.jpg" /> 

// In the javascript, executed after the dom is rendered
this.img = new Image();
this.img.crossOrigin = 'anonymous';
this.img.src = "http://s3....../bob.jpg";

I have observed the following:

  1. Disable caches
  2. Everything works fine, both images load

Then trying it with caches enabled:

  1. Enable caches
  2. DOM image loads, canvas image creates a dom security exception

If I modify the javascript portion of the code to append a query string, like so:

this.img = new Image();
this.img.crossOrigin = 'anonymous';
this.img.src = "http://s3....../bob.jpg?_";

Everything works, even with caching enabled fully. I got on to the caching being a problem by using an http proxy and observing that in the failure case, the image isn't actually being requested from the server.

The conclusion I'm forced to draw is that the image cache is saving the original request headers, which are then being used for the subsequent CORS enabled request - and the security exception is being generated due to violation of the same origin policy.

Is this intended behavior?

Edit: Works in firefox.

Edit2: Cors policy on s3 bucket

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
    <CORSRule>
        <AllowedOrigin>*</AllowedOrigin>
        <AllowedMethod>GET</AllowedMethod>
    </CORSRule>
</CORSConfiguration>

I'm using wide open because I'm just testing from my local box right now. This isn't in production yet.

Edit3: Updated cors policy to specify an origin

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
    <CORSRule>
        <AllowedOrigin>http://localhost:5000</AllowedOrigin>
        <AllowedMethod>GET</AllowedMethod>
    </CORSRule>
</CORSConfiguration>

Verified outgoing headers:

Origin  http://localhost:5000
Accept  */*
Referer http://localhost:5000/builder
Accept-Encoding gzip,deflate,sdch
Accept-Language en-US,en;q=0.8
Accept-Charset  ISO-8859-1,utf-8;q=0.7,*;q=0.3

Incoming headers:

Access-Control-Allow-Origin http://localhost:5000
Access-Control-Allow-Methods    GET
Access-Control-Allow-Credentials    true

Still fails in chrome if I don't bust the cache when loading into the canvas.

Edit 4:

Just noticed this in the failure case.

Outgoing headers:

GET /373c88b12c7ba7c513081c333d914e8cbd2cf318b713d5fb993ec1e7 HTTP/1.1
Host    amir.s3.amazonaws.com
User-Agent  Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.91 Safari/537.4
Accept  */*
Referer http://localhost:5000/builder
Accept-Encoding gzip,deflate,sdch
Accept-Language en-US,en;q=0.8
Accept-Charset  ISO-8859-1,utf-8;q=0.7,*;q=0.3
If-None-Match   "99c958e2196c60aa8db385b4be562a92"
If-Modified-Since   Sat, 29 Sep 2012 13:53:34 GMT

Incoming headers:

HTTP/1.1 304 Not Modified
x-amz-id-2  3bzllzox/vZPGSn45Y21/vh1Gm/GiCEoIWdDxbhlfXAD7kWIhMKqiSEVG/Q5HqQi
x-amz-request-id    48DBC4559B5B840D
Date    Sat, 29 Sep 2012 13:55:21 GMT
Last-Modified   Sat, 29 Sep 2012 13:53:34 GMT
ETag    "99c958e2196c60aa8db385b4be562a92"
Server  AmazonS3

I think this is the first request, triggered by the dom. I don't know that it isn't the javascript request though.

amirpc
  • 1,638
  • 3
  • 19
  • 24
  • Does it work in Firefox or Opera? – Maz Sep 29 '12 at 00:23
  • Hace you tried with `Access-Control-Allow-Origin *`? Since `http://localhost:5000/` could be misleading. – Borislav Sabev Sep 16 '13 at 09:13
  • You are a beautiful man! Thank you for posting this - I was struggling for hours. – Chris Scott Dec 01 '15 at 23:09
  • 1
    Like chemitaxis, I don't see what the solution is. Could you tell us how this was solved? – Jared Martin Dec 03 '16 at 19:48
  • Thank you for a very well written explanation of the issue. Sweet mother of Perl did I learn something today! I might add that this whole issue is further complicated by the fact that caching behaviours differ, even for the same browser but on different OS's, and depending on whether you are in incognito mode or not. None of these subtleties are very well documented anywhere, which made debugging this thing take me a whole afternoon! – Timo May 28 '20 at 16:54
  • I spent soooo many days debugging this. Thanks! – Aanchal1103 Aug 18 '20 at 06:57

3 Answers3

16

The problem is that the image is cached from a former request, without the required CORS headers.Thus, when you ask for it again, for the canvas, with the 'crossorigin' specified, the browser uses the cached version, doesn't see the necessary headers, and raises a CORS error. When you add the '?_' to the url, the browser ignores the cache, as this is another URL. Take a look at this thread: https://bugs.chromium.org/p/chromium/issues/detail?id=409090

Firefox and other browsers do no have that problem.

IdoL
  • 181
  • 1
  • 8
  • If we want to use cache then, how can we cache the image with cors header? – Mayur Kukadiya Dec 24 '18 at 05:40
  • 1
    But this solution doesn't work with pre-signed urls – Matteo Oct 24 '19 at 03:32
  • Actually any parameter can do the trick if the file server cache is configured to includes query params in the cache key. The idea is to have a different url for an image loaded without crossorigin=anonymous and the other loaded with. So they will not "share" the same cache. You can add for example the param ?crossorigin (or ?tartiflette) to the image url you want to be cached with cors headers – Hugo Mallet Mar 31 '22 at 11:21
8

Described behavior seems logical since cache entry key is a target URI (see 7234 Hypertext Transfer Protocol (HTTP/1.1): Caching). To fix the issue and effectively use cache you need to make image hosting server give same response in both cases.

One option is to make user agent send Origin HTTP header in first request too (given that response with key targetUri is not in a cache already):

<img src="targetUri" crossorigin="anonymous" />

Another option is to configure image hosting server to send CORS related HTTP headers regardless of whether request contains Origin HTTP header. For more information see S3 CORS, always send Vary: Origin discussion on StackOverflow.

Also you can inform user agent that responses are sensitive to Origin request HTTP header using Vary response HTTP header. The downside is that probably user agent will use Vary header only as a response validator (and not as a part of a cache entry key) and store only single response instance for a target URI which makes harder to effectively use cache. For more information check The State of Browser Caching, Revisited article by Mark Nottingham.

Community
  • 1
  • 1
Leonid Vasilev
  • 11,910
  • 4
  • 36
  • 50
0

What CORS settings are you applying? This post suggests that wildcards in AllowedOrigin are parsed (rather then being sent verbatim, this appears to be undocumented behaviour); and the Access-Control-Allow-Origin header value is then cached for subsequent requests, causing issues similar to what you're reporting.

Oleg
  • 24,465
  • 8
  • 61
  • 91
  • I added my cors policy. Are you saying the `Access-Control-Allow-Origin` header is cached by the client? I will try using localhost for my origin. – amirpc Sep 29 '12 at 06:25
  • Verified even with a domain specified for AllowedOrigin, I'm still seeing this behavior. – amirpc Sep 29 '12 at 06:33
  • Just verified again that if I bust the cache, everything works fine. If I don't bust the cache, it doesn't matter whether my origin is wildcard or specific. I tested chrome & safari & firefox. Firefox works in all cases, Chrome/Safari seem to have this bug. Must be a webkit issue. – amirpc Sep 29 '12 at 06:41
  • @amirpc: no, it was my assumption that your issue would be caused by CloudFront or proxy caching. Now, with you pointing fingers at Chrome, I reckon [this question may be much more relevant to your situation](http://stackoverflow.com/q/3136140/1081234). – Oleg Sep 29 '12 at 07:47
  • I'm not seeing a preflight request, I'm using an http proxy and looking and there are no options request going through. Just gets that are adding If-None-Match headers. Get is in my cors policy and should work. However, these gets don't specify the origin presumably because the cached version didn't from the DOM request. – amirpc Sep 29 '12 at 14:01