custom HTTP headers for static files with Django - django

I'm writing an image bank with Django, and I want to add a button to get a hi-res version of an image (the low-res is shown in the details page). If I put just an <a> link, the browser will open the image instead of downloading it. Adding an HTTP header like:
Content-Disposition: attachment; filename="beach008.jpg"
works, but since it's an static file, I don't want to handle the request with Django. Currently, I'm using NGINX to serve static files, and dynamic pages are redirected via FastCGI to the Django process. I'm thinking about using NGINX add-header command, but could it set the filename="xx" part?. Or maybe there's some way to handle the request in Django, but make NGINX serve the content?

If your django app is proxied by nginx you can use x-accell-redirect. You need to pass a special header in your response, nginx will intercepet this and start serving the file, you can also pass Content-Disposition in the same response to force a download.
That solution is good if you want to control which users acess these files.
You can also use a configuration like this:
#files which need to be forced downloads
location /static/high_res/ {
root /project_root;
#don't ever send $request_filename in your response, it will expose your dir struct, use a quick regex hack to find just the filename
if ($request_filename ~* ^.*?/([^/]*?)$) {
set $filename $1;
}
#match images
if ($filename ~* ^.*?\.((jpg)|(png)|(gif))$) {
add_header Content-Disposition "attachment; filename=$filename";
}
}
location /static {
root /project_root;
}
This will force download on all images in some high_res folder (MEDIAROOT/high_rest). And for the other static files it will behave like normal. Please note that this is a modified quick hack that works for me. It may have security implications, so use it with precaution.

I wrote a simple decorator, for django.views.static.serve view
Which works for me perfectly.
def serve_download(view_func):
def _wrapped_view_func(request, *args, **kwargs):
response = view_func(request, *args, **kwargs)
response['Content-Type'] = 'application/octet-stream';
import os.path
response['Content-Disposition'] = 'attachment; filename="%s"' % os.path.basename(kwargs['path'])
return response
return _wrapped_view_func
Also you can play with nginx mime-types
http://wiki.codemongers.com/NginxHttpCoreModule#types
This solution didn't work for me, because I wanted to have both direct link for the file (so user can view images, for example), and download link.

What i'm doing now is to use a different URL for download than for 'views', and add the filename as an URL arg:
usual media link: http://xx.com/media/images/lores/f_123123.jpg
download link: http://xx.com/downs/hires/f_12323?beach008.jpg
and nginx has a config like this:
location /downs/ {
root /var/www/nginx-attachment;
add_header Content-Disposition 'attachment; filename="$args"';
}
but i really don't like the smell of it.

Related

Cloudfront not redirecting with trailing slash

I am hosting a website with multiple subdomains from S3 buckets through Cloudfront.
When I go to www.domain.com/subdomain/ (note: with trailing slash), the website loads correctly and fetches the minified .js and .css files from www.domain.com/subdomain/****.js.
However if I navigate to www.domain.com/subdomain without the trailing slash, the site's index.html is still served but the assets attempt to be fetched from www.domain.com/****.js.
I have tried to use a lamba#edge function to change the request uri and append the slash however that is not working. Thanks for any help!
This will be caused by the html referencing relative file paths (i.e. src="****.js" vs src="/subdomain/****.js").
If you are looking to fix this, you will need to perform a redirect to the slash path in the users browser. This can be done by using a Lambda#Edge function to perform the redirect in the Origin Response event.
An example redirect function is below
def lambda_handler(event, context):
# Generate HTTP redirect response with 302 status code and Location header.
response = {
'status': '302',
'statusDescription': 'Found',
'headers': {
'location': [{
'key': 'Location',
'value': 'http://docs.aws.amazon.com/lambda/latest/dg/lambda-edge.html'
}]
}
}
return response
For this you would need to add your custom logic to check if the URL needs to be redirected by checking for a "/" character as the last character of the request.
Additionally if you can change the path of your css, js and images from being relative to being absolute as pointed out at the top of this answer.

Prevent Force Download of AWS S3 Files Django

I am using
storages.backends.s3boto3.S3Boto3Storage
storage backend to upload files in my django project.
field declaration in model:
document = models.FileField(upload_to=s3_directory_path.user_directory_path)
user_directory_path
def user_directory_path(instance, filename):
# TODO: Try to include this along with check filetype on the request object
document = instance.document
mime = magic.from_buffer(document.read(), mime=True)
extension = mimetypes.guess_extension(mime, strict=False)
file_name = str(uuid.uuid4()) + extension
document.seek(0)
return os.path.join("users", str(instance.user.id), file_name)
The saving of the document works perfectly fine, but the link which is generated force downloads the file. How can i avoid that?
Have a look at this answer to a general question about forcing file downloads via HTTP response headers. See also the MDN docs about Content-Disposition.
Can you show us the response headers you get when visiting the document URL?
It would be interesting to see how S3 delivers your files.
If you cannot change the headers in S3, you have the option to write a Django view that proxies the file download. Alternatively, configure your webserver (i.e. NGINX) to act as a proxy and set the required headers).
For Django, this section of the docs will show you how to set the headers.
response = HttpResponse(
document,
headers={
'Content-Type': mimetype,
'Content-Disposition': f'attachment; filename="{document.name}"',
}
)

a web server developed by python, how to transfer a image file, and can show in a web browser?

I want to develop a simple web server using python to handle some simple http request. I have learn how to response the request, such as transferring html pages or transferring some other file. When I transfer a image file, a client use a browser to get the file, the url is like below:
http://114.212.82.104:8080/1.png
I set 'Content-Type = application/x-png'. But the browser directly download the file, and can not display in the browser. Not like the image below
https://www.baidu.com/img/bd_logo1.png
it can display in the browser. How to display the image in the browser?
Can someone help me?
and i know i can encode the image file into html page to fix it. code like below:
class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
def do_GET(self):
path = os.getcwd()+ self.path
if os.path.isfile(path):
with open(path,'rb') as fileTrans:
content = fileTrans.read().encode('base64').replace('\n','')
#self.sendContent(200, content)
self.send_response(200)
page = "<p>\"fef\"</p><img src=\"data:image/jpg;base64,{0}\"/>"
contentPage = page.format(content)
self.send_header('Content-Type', 'text/html')
self.send_header("Content-Length", str(len(contentPage)))
self.end_headers()
self.wfile.write(contentPage)
else:
self.sendContent(404,"file do not exists")
But I know there must be another way, i see the source code of URL(https://www.baidu.com/)
it just use
<img hidefocus="true" src="//www.baidu.com/img/bd_logo1.png" width="270" height="129"></div><a href="/" id="result_logo" onmousedown="return c({'fm':'tab','tab':'logo'})">
different from my page:
<p>"fef"</p><img src="data:image/jpg;base64,iVBORw0KGgoAAAANSUhEUgAAAcIAAAJYCAIAAAB+b3GqAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAvppVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTMyIDc5LjE1OTI4NCwgMjAxNi8wNC8xOS0xMzoxMzo0MCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6ODg3NkM1Njg1MzVFMTFFNkE0NkJFNTEzMUFCNzc4RTMiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6ODg3NkM1Njc1MzVFMTFFNkE0NkJFNTEzMUFCNzc4RTMiIHhtcDpDcmVhdG9yVG9vbD0iOC4xLjMiPiA8eG1wTU06RG......
OK,I think I have solved this problem.
Just set the attribute - 'Content-Type' of header as 'image/png' instead of 'application/x-png'.

Serving protected files with Django and Nginx X-accel-redirect

I'm trying to get Nginx and Django to play together to serve downloadable protected files. I just cannot get it to work. Here's my Nginx config:
location ~ ^.*/protected-test/ {
alias /<path-to-my-protected-files-on-server>/;
internal;
}
the relevant urls.py for viewing the file(s):
url(r'^static_files/downloads/protected-test/(?P<filename>.+)$', 'download_or_view',
{'download_dir': '%s%s' % (settings.MEDIA_ROOT, 'downloads/protected-test/'),
'content_disposition_type': 'inline',
'protected': 'True'},
name='protected_files')
my view:
def download_or_view(request, content_disposition_type, download_dir, filename=None, protected=False):
'''Allow a file to be downloaded or viewed,based on the request type and
content disposition value.'''
if request.method == 'POST':
full_path = '%s%s' % (download_dir, request.POST['filename'])
short_filename = str(request.POST['filename'])
else:
full_path = '%s%s' % (download_dir, filename)
short_filename = str(filename)
serverfile = open(full_path, 'rb')
contenttype, encoding = mimetypes.guess_type(short_filename)
response = HttpResponse(serverfile, mimetype=contenttype)
if protected:
url = _convert_file_to_url(full_path)
response['X-Accel-Redirect'] = url.encode('utf-8')
response['Content-Disposition'] = '%s; filename="%s"' % (content_disposition_type, smart_str(short_filename))
response['Content-Length'] = os.stat(full_path).st_size
return response
I have 2 values in my settings file:
NGINX_ROOT = (os.path.join(MEDIA_ROOT, 'downloads/protected-test'))
NGINX_URL = '/protected-test'
_convert_file_to_url() takes the full file path and, using the two settings values above, turns it into a url that (I thought) Nginx would allow:
<domain-name>/protected-test/<filename>
So, if I try to access:
<domain-name>/static_files/downloads/protected-test/<filename>
In my browser window, it doesn't allow it (404). Good.
BUT - if I try to access that url from a form download, which I want to allow, I get a redirect in the browser to:
<domain-name>/protected-test/<filename>
and it's a 404 as well.
I've tried so many different configurations my brain now hurts. :-)
Should I not be reading the file with open(), and let Nginx serve it? If I remove that line, it returns a file with the dreaded zero bytes. Why do I still get a 404 on the redirected url??
Should I not be reading the file with open(),
That's correct. Your script shouldn't be opening the file. You just tell Nginx where the file exists and let it open the file and serve it.
I believe you want to just return an empty response after setting the appropriate headers
return HttpResponse('', mimetype=contenttype)
In PHP I setup the Nginx accel redirect by doing:
//Set content type and caching headers
//...
header("X-Accel-Redirect: ".$filenameToProxy);
exit(0);
i.e. exiting immediately after setting the header.
For the continuing 404 problem, you've probably got an error in the Nginx conf, but you need to post the rest to be sure. Your external URL appears to be something like:
static_files/downloads/protected-test/(?P<filename>.+)$
This will be matched on:
location ~ ^.*/protected-test/ {
alias /<path-to-my-protected-files-on-server>/;
internal;
}
giving the 404.
There is no need (and it's quite confusing) to have the same word protected-test in both the external URL and internal URL. I'd recommend not doing that i.e. have the external URL be like:
/static_files/downloads/(?P<filename>.+)$
Then have the internal location block be:
location ~ ^/protected-test {
alias /<path-to-my-protected-files-on-server>;
internal;
}
And then when you setup the x-accel-redirect header, swap between the two:
external_path = "/static_files/downloads";
nginx_path = "/protected-test";
filenameToProxy = str_replace(external_path, nginx_path, full_path);
header("X-Accel-Redirect: ".$filenameToProxy);
Rather than having the word protected-test be on both sides of the request.

Django Request URL becomes weird

I have a django project running on my localhost and it is working very well, however when I uploaded it to real server, some problem started happening with the url. it happens every time HttpResponseRedirect or any redirect gets called
a page on my local host
http://127.0.0.1:8000/signin
while on the server it becomes
http://xyz.com,%20xyz.com/signin
in firebug i see
GET signin 301 MOVED PERMANENTLY
GET signin http://xyz.com,%20xyz.com/signin
I belive this happens because the urls.py has ^signin/$^ and APPEND_SLASH = True in settings.py because when I visit /signin/ it works!
404 page on my local host
Request URL: http://127.0.0.1:8000/test
on the server
Request URL: http://xyz.com,%20xyz.com/test
for some reason it is adding [comma][space] to url and redirects it.
home page is working without issues
The issue is tracked in the following ticket:
https://code.djangoproject.com/ticket/11877
It has to do with how Django handles proxy redirection. The following middleware will help you out.
class MultipleProxyMiddleware(object):
FORWARDED_FOR_FIELDS = [
'HTTP_X_FORWARDED_FOR',
'HTTP_X_FORWARDED_HOST',
'HTTP_X_FORWARDED_SERVER',
]
def process_request(self, request):
"""
Rewrites the proxy headers so that only the most
recent proxy is used.
"""
for field in self.FORWARDED_FOR_FIELDS:
if field in request.META:
if ',' in request.META[field]:
parts = request.META[field].split(',')
request.META[field] = parts[-1].strip()
If, for example, your Django site is sitting behind a proxy which includes proxy information in the X-Forwarded-For header, and then your web server also does proxying, the header will contain a list (comma separated) of the proxied addresses. By using this middleware, it will strip all but one of the proxied addresses in the headers.
It might not be an answer since I'm working with you on the same application, I fixed it.
It has something to do with nginx to apache redirection, we had proxy_set_header Host $host; and when I disabled it the redirection worked without errors.