I post images on reddit by hotlinking images from my site. By allowing reddit to hotlink my images it will display the image on their site but I also want it to redirect if a user clicks on the image link from reddit. How can I do this while allowing hotlinking for reddit only?
Basically I'm trying to mimic the same way tumblr does with their images. Just try posting an image on reddit by hotlinking a tumblr image and it will display the image on their site and if you try to direct access the image it will redirect.
Example: https://www.reddit.com/user/HeavenlyTasty/comments/vqgn9r/demo_pic/
So far I thought of using nginx http referrer module but that doesn't achieve what I want it to do since it doesn't redirect if coming from reddit.
Anyone know another method to do this?
location ~* ^/(?<filenum>.*)\.(jpeg|jpg|png|webp)$ {
proxy_pass http://195.xxx.xxx.xxx:7492;
valid_referers none blocked server_names
~\.reddit\.;
if ($invalid_referer) {
add_header Cache-Control "no-cache";
return 301 https://example.com/$filenum;
}
Here is a reverse engineered tumblr behavior. Decision to serve an image file or an HTML page is made according to the Accept
HTTP request header. The trick is that this header value will vary depending on how is it requested - being entered at the browser address bar (or following the <a>
tag hyperlink) or being referred via the src
attribute of the <img>
tag. Here is a (somewhat inaccurate) sample list for various browsers from MDN and here are the actual Accept
header values for several browsers (checked at the time of writing):
Chrome 102
<a>
tag:
text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
<img>
tag:
image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
Firefox 102
<a>
tag:
text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
<img>
tag:
image/avif,image/webp,*/*
Safari 14.1 (MacOS Big Sur)
<a>
tag:
text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
<img>
tag:
image/webp,image/png,image/svg+xml,image/*;q=0.8,video/*;q=0.8,*/*;q=0.5
Internet Explorer 11
<a>
tag:
text/html, application/xhtml+xml, */*
<img>
tag:
image/png, image/svg+xml, image/*;q=0.8, */*;q=0.5
As you can see, a certain pattern can be observed for all browsers Accept
header value. When an image is requested from the address bar (or via the <a>
HTML tag), a text/html
item is present in the accepted MIME types list; when an image is referred via src
attibute of the <img>
HTML tag, accepted MIME types list consists of image/<MIME_subtype>
elements and the */*
element (except the Safari, where an aditional video/*
item is present). If accepted MIME types list from the Accept
header contains a text/html
item, tumblr will serve such a request with an HTML page (except if the User-Agent
header contains one of the well-known values for download managers, e.g. curl
, wget
, etc. - in this case, the server will still return the image). Note that tumblr won't give you a redirect to the HTML file. Instead it will serve the request with HTML file. The same request (with another Accept
header) will be served later with the actual image file. Behaving like that, a properly configured server should warn the client browser (or intermediate proxy servers that may be present along the request/response route) that the content of the response may vary depending on certain request headers. The tumblr behaves exactly like that, adding the Vary: Accept
header to the response. Here is an example requesting the same URL using two different Accept
headers (User-Agent
header removed from the request due to the aforementioned reasons):
> curl -I --http1.1 -H "Accept: */*" -H "User-Agent:" https://64.media.tumblr.com/f34905b055dc7fe1d7370bbea305662a/b3dcc022e27e2c22-f0/s1280x1920/84a7c043ea887caa2febb83ccd73c3c173a6beae.jpg
HTTP/1.1 200 OK
Server: nginx
Date: Tue, 05 Jul 2022 01:37:31 GMT
Content-Type: image/jpeg
Content-Length: 56077
Last-Modified: Fri, 01 Jul 2022 12:15:02 GMT
Etag: "9a02a2354a0fc8847dbddc91ff869735-1498089600-d32ddc9"
Content-Disposition: inline; filename="tumblr_f34905b055dc7fe1d7370bbea305662a_84a7c043_1280.jpg"
Cache-Control: max-age=315360000
...
> curl -I --http1.1 -H "Accept: text/html,*/*" -H "User-Agent:" https://64.media.tumblr.com/f34905b055dc7fe1d7370bbea305662a/b3dcc022e27e2c22-f0/s1280x1920/84a7c043ea887caa2febb83ccd73c3c173a6beae.jpg
HTTP/1.1 200 OK
Server: nginx
Date: Tue, 05 Jul 2022 01:37:52 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 17267
Vary: Accept-Encoding
Vary: Accept
ETag: W/"4373-SQYv0QHYV7hXuB2cQlEhOw0Czx4"
Cache-Control: private, max-age=0
...
Issuing a 301 redirect with the Cache-Control: no-cache
header can be an alternative; however, I'd rather use 302 temporary redirect instead:
map $invalid_referer $redirect {
'' $check_accept;
default 1;
}
map $http_accept $check_accept {
~\btext/html\b 1;
}
server {
...
location ~* ^/(?<filenum>.*)\.(jpe?g|png|webp)$ {
proxy_pass http://195.xxx.xxx.xxx:7492;
valid_referers none blocked server_names ~\.reddit\.;
if ($redirect) {
return 302 https://example.com/$filenum;
}
}
To completely mimic tumblr behavior you can try internal rewrite ... last
instead of redirect (the map
blocks will be the same as from the previous example):
server {
...
location ~* ^/(?<filenum>.*)\.(jpe?g|png|webp)$ {
proxy_pass http://195.xxx.xxx.xxx:7492;
valid_referers none blocked server_names ~\.reddit\.;
if ($redirect) {
set $vary Accept;
rewrite ^ /$filenum last;
}
}
location ... {
# location where rewritten request will be actually processed
add_header Vary $vary;
...