HTTPS content-disposition attachment - asp.net

Symptoms are very like C# BinaryWrite over SSL but my code already complies with all of the proposed solutions. This behaviour was manifest on Edge, IE11 and Chrome at the time of writing. Code that worked fine over HTTP broke when delivered over HTTPS.
private void RenderPdfToResponse(bool asDownload, byte[] documentBytes)
{
Response.BufferOutput = true;
Response.ClearContent();
Response.ClearHeaders();
Response.AddHeader("Cache-control", "no-store");
Response.ContentType = "application/pdf";
Response.AddHeader("Content-Length", documentBytes.Length.ToString());
if (asDownload)
Response.AddHeader("Content-Disposition",
string.Format("attachment; filename=export{0:yyyyMMddHHmmss}.pdf", DateTime.UtcNow));
Response.BinaryWrite(documentBytes);
Response.Flush();
HttpContext.Current.ApplicationInstance.CompleteRequest();
}
The problem is at the browser. Delivered binary content that should be saved into a file is instead rendered in the browser window. Interception with debug tools and saving into a file allows verification that the delivered bytes form a valid PDF.
Given that all the suggestions of the referenced question are already in effect, and the problem is browser behaviour, what options remain?

Consulting https://www.rfc-editor.org/rfc/rfc6266 we find that the filename portion is optional. Experimentally specifying
Response.AddHeader("Content-Disposition", "attachment");
causes all major browsers to pop a Save As dialog with the name export-pdf.pdf which is apparently derived from the name of the ASPX file and the mime type.
At this point I was asked to ship. I hope someone finds this helpful.

Related

PDF file sometime being displayed as garbage

I am having a user who is reporting that files are being displayed as raw data in his browser. He uses Internet Explorer.
The files are being served via a .ashx handler file and it has been working until.
This is the relevant part of my .ashx handler:
context.Response.Clear()
context.Response.AppendHeader("Content-Disposition", "attachment; filename=" + name)
context.Response.AppendHeader("Content-Length", size.ToString)
context.Response.ContentType = "application/pdf"
context.Response.TransmitFile(fullname)
context.Response.Flush()
HttpContext.Current.ApplicationInstance.CompleteRequest()
Can anyone figure something out of this screenshot?
Update: this behaviour appears on Windows 10 when running either IE 11 or Edge and only the second time a file is being opened. It happens for both .pdf and .docx files.
This is the code I use to stream PDFs to a client. It works in IE 11. The main difference is that I am using BinaryWrite which based on your code, you may not want to do..
HttpContext.Current.Response.Clear();
HttpContext.Current.Response.AddHeader("Content-Type", "application/pdf");
HttpContext.Current.Response.AddHeader("Content-Disposition", "inline; filename=" + fileName + ".pdf");
HttpContext.Current.Response.BinaryWrite(bytes);
HttpContext.Current.Response.End();
There might be a solution here
I'll like this as well just in case..
According to this thread, it could be as simple as replacing Response.Close with Response.End (or in your case.. adding)
I finally found the answer myself - it had to do with the HTTP header content-length which i mistakenly submitted with a value exactly 1byte too large.
This caused the strange behavior in only IE/Edge and only Windows 10 as described in the OP.
I had the same problem with an aspx page that transmits file to browser in Page_Load event handler.
My mistake was an absense of
Response.End();
method call. When I added this line the problem has gone.

ASP plain text file response is cut short

I have an ASP web forms page with a button. On postback the button sends some XML content back to the user as a file. It has been working however in one case with a string with a length of 16759 the downloaded file has been cut short by 10 bytes. Both Chrome and Firefox have exhibited the same behaviour.
The solution has been to change the content-type from "text/xml" (I also tried "text/plain") to "application/octet-stream". However I would like to understand why the other content-types behave in this way.
My code is as follows. (I've played around with a few different methods and they did not change anything)
HttpContext.Current.Response.Clear();
HttpContext.Current.Response.ContentType = "text/plain";
HttpContext.Current.Response.AddHeader("Content-Length", content.Length.ToString());
HttpContext.Current.Response.AddHeader("Content-Disposition", "attachment; filename=\"test.txt\"");
HttpContext.Current.Response.Write(content);
HttpContext.Current.Response.Flush();
HttpContext.Current.Response.Close();
All you have to do is not call Close on the Stream. Don't ask me for an explanation, all I know is it works.
Explanation per the MSDN link for HttpResponse.Close kindly provided by Ray Cheng:
This method terminates the connection to the client in an abrupt manner and is not intended for normal HTTP request processing. The method sends a reset packet to the client, which can cause response data that is buffered on the server, the client, or somewhere in between to be dropped.

How do I send a binary blob to a client browser?

Pardon the dumb newbie question here; web programming isn't my forte... (blush)
I have an aspx page running on a web server. I have a blob (byte array) containing any kind of binary file, plus a file name.
I would like to push this file to be downloaded through the browser onto the client, and opened using whatever application is default for this file type. I really don't want to save the blob as a file on the server; that will leave a terrible housekeeping mess that I just don't want to think about.
I did try googling this question, but I guess I'm using the wrong keywords.
This really should be obvious how to do it, but I'm having no joy.
What is the trick?
Thanks!
Response.BinaryWrite(byteArray);
You should also set the content type
Response.ContentType = "application/pdf";
But that will be based on your file type.
And the file name (and everything together) is done like this
Response.AddHeader("content-disposition",
String.Format("attachment;filename={0}", fileName));
Response.ContentType = "application/pdf";
Response.BinaryWrite(byteArray);
First, you have to know the mime type. Once you know that, you can set the Response.ContentType property. After that, just use Response.BinaryWrite(). If you don't first set the ContentType property, the client will have almost no chance of opening the file correctly.

Zip Folder is always corrupted after downloaded

For some reason when I download a zip folder from my server's folder it is always corrupted. Here is the code:
protected void gvFiles_RowCommand(object sender, System.Web.UI.WebControls.GridViewCommandEventArgs e)
{
string fileUrl = String.Empty;
if(e.CommandName.Equals("DownloadFile"))
{
fileUrl = e.CommandArgument as String;
string fileName = Path.GetFileName(fileUrl);
Response.AppendHeader("content-disposition",
"attachment; filename=" + fileName);
Response.ContentType = "application/zip";
Response.WriteFile(fileUrl);
Response.End();
}
}
And here is how the GridView is populated:
private void BindData()
{
List<SampleFile> files = new List<SampleFile>();
for(int i=1;i<=3;i++)
{
SampleFile sampleFile = new SampleFile();
sampleFile.Name = "File " + i;
sampleFile.Url = Server.MapPath("~/Files/File"+i+".txt");
files.Add(sampleFile);
}
SampleFile file = new SampleFile();
file.Name = "Zip File";
file.Url = Server.MapPath("~/Files/WebSiteNestedMasters.zip");
files.Add(file);
gvFiles.DataSource = files;
gvFiles.DataBind();
}
I don't know asp.net at all, but this is pretty commonly a result of doing the download in a text mode instead of binary mode. Line ending characters get converted from \r\n to \n or vice versa, and everything goes nuts.
As #lassevk suggested you should download the corrupted zip file and compare it with the original file on the server. Are both the same length? Use a hex editor to inspect the contents of the file. In this related thread you are saying that if you point the browser directly to the zip file it is also corrupted, meaning that the problem is probably not related to the headers but something wrong with IIS. Are you using some third party ISAPI extensions that could modify the file?
Assuming that you need to do this: (e.g. you are unable to provide a direct link)
<a href='<% Eval("Url")) %>'>download</a>
I'll first make an observation that having a RowCommand handler start to return a file isn't the way to do this. Create some sort of download link to a different page. Separate file downloading from the rest of the page containing the grid.
now ...
You've got the basics about right, but like a comment in this CodeProject tutorial you're soon going to run into issues.
Where the above code sample falls
down:
not responsive to user
the code will keep going and you'll have no
idea if the user actually downloaded
the file (it may not matter to you)
won't work with all browsers
cannot resume a download
doesn't show progress
larger files will use more server memory and take longer to stream
and a lot of downloads will mean the server is going to take a resource hit
whereas you might want ..
works just like a clicked download (ie using get, not post)
works in all browsers on all platforms in the way the user expects
(ie filename hint works on things like
IE for Mac or Netscape 4.1).
show download progress
resumable downloads
tracking all downloads and knowing when downloads complete
looks like a file, even if it isn't
expiration on url.
allows for high concurrent # of downloads for any size of file
Although written in VB .net1.1 the article Tracking and Resuming Large File Downloads in ASP.NET [devx] is far better at explaining how to do it correctly.
Easy code is easy but doesn't always work, and efficiently streaming files to users 100% of the time takes a little more effort than setting a content header and shoveling some bits down the wire.
First, verify that the contents of the file actually looks like a zip file. You can do that simply by dropping the file into notepad, and looking at the contents. If the file starts with PK and some funny characters, it might be a zip file. If it contains HTML, that might be a clue as to why it is going wrong.
Looking at your code, it struck me that you're passing the url to the file to Response.WriteFile. Does Response.WriteFile handle url's or does it use local filenames? Perhaps everything up to Response.WriteFile works, and thus the right headers are sent, etc. but then the code crashes with an exception, which might make your "zip file" contain a HTML error message.
Edit: Ok, first problem-solving step completed. File looks like a zip file in notepad, not a HTML error message.
Next step, since the file resides on the server, try writing an url directly to the file, instead of going through your application. Does that work?
If not, try a different zip program (which one are you using by the way?).
As an example of why you should try the file directly and different unzipping programs. FinalBuilder produces zip files that PowerArchiver complains about, but 7zip does not, so there might be something wrong with either the file or your program, or possibly even both.
But verify that the files are correct, can be opened, both if you don't download them at all, and check what happens if you download them directly outside of your application.
Edit: Ok, you've verified that the file won't open even if you download it directly from your server.
How about opening the file in just explorer, bypassing the web server altogether? For instance, since it looks like you have the file locally, try just navigating to the right folder and opening the file directly. Does that work?
Here is the updated code:
fileUrl = e.CommandArgument as String;
string fileName = Path.GetFileName(fileUrl);
FileStream fs = new FileStream(fileUrl, FileMode.Open);
byte[] buffer = new byte[fs.Length];
fs.Read(buffer, 0, (int) fs.Length);
fs.Close();
Response.Clear();
Response.AppendHeader("content-disposition",
"attachment; filename=" + fileName);
Response.ContentType = "application/octet-stream";
Response.AppendHeader("content-length", buffer.Length.ToString());
Response.BinaryWrite(buffer);
Response.Flush();
Response.End();
Here is the fiddler stats:
HTTP/1.1 200 OK
Server: ASP.NET Development Server/8.0.0.0
Date: Wed, 17 Dec 2008 22:22:05 GMT
X-AspNet-Version: 2.0.50727
Content-Disposition: attachment; filename=MyZipFolder.zip
Content-Length: 148
Cache-Control: private
Content-Type: application/x-zip-compressed
Connection: Close
I just tried the same code with the .docx file and same result (The file is corrupted): Here is the code:
if(e.CommandName.Equals("DownloadFile"))
{
fileUrl = e.CommandArgument as String;
string fileName = Path.GetFileName(fileUrl);
FileInfo info = new FileInfo(fileUrl);
Response.Clear();
Response.ClearContent();
Response.ClearHeaders();
Response.Buffer = true;
Response.AppendHeader("Content-Length",info.Length.ToString());
Response.ContentType = GetContentType(fileUrl);
Response.AppendHeader("Content-Disposition:", "attachment; filename=" + fileName);
Response.TransmitFile(fileUrl);
Response.Flush();
Response.End();
}

Response Content type as CSV

I need to send a CSV file in HTTP response. How can I set the output response as CSV format?
This is not working:
Response.ContentType = "application/CSV";
Using text/csv is the most appropriate type.
You should also consider adding a Content-Disposition header to the response. Often a text/csv will be loaded by a Internet Explorer directly into a hosted instance of Excel. This may or may not be a desirable result.
Response.AddHeader("Content-Disposition", "attachment;filename=myfilename.csv");
The above will cause a file "Save as" dialog to appear which may be what you intend.
MIME type of the CSV is text/csv according to RFC 4180.
Use text/csv as the content type.
Over the years I've been honing a perfect set of headers for this that work brilliantly in all browsers that I know of
// these headers avoid IE problems when using https:
// see http://support.microsoft.com/kb/812935
header("Cache-Control: must-revalidate");
header("Pragma: must-revalidate");
header("Content-type: application/vnd.ms-excel");
header("Content-disposition: attachment; filename=$filename.csv");
Try one of these other mime-types (from here: http://filext.com/file-extension/CSV )
text/comma-separated-values
text/csv
application/csv
application/excel
application/vnd.ms-excel
application/vnd.msexcel
Also, the mime-type might be case sensitive...
Just Use like that
Response.Clear();
Response.ContentType = "application/CSV";
Response.AddHeader("content-disposition", "attachment; filename=\"" + filename + ".csv\"");
Response.Write(t.ToString());
Response.End();
In ASP.net MVC, you can use a FileContentResult and the File method:
public FileContentResult DownloadManifest() {
byte[] csvData = getCsvData();
return File(csvData, "text/csv", "filename.csv");
}
I have found that the problem with IE is that it sniffs the return data and makes up its own mind about what content-type it thinks it has been sent. There are a number of side effect that this causes, such as always openning a saveAs dialog for text files because you are using compression of data trasnferes. The solution is (in php code)......
header('X-Content-Type-Options: nosniff');
I suggest to insert an '/' character in front of 'myfilename.cvs'
Response.AddHeader("Content-Disposition", "attachment;filename=/myfilename.csv");
I hope you get better results.
Setting the content type and the content disposition as described above produces wildly varying results with different browsers:
IE8: SaveAs dialog as desired, and Excel as the default app. 100% good.
Firefox: SaveAs dialog does show up, but Firefox has no idea it is a spreadsheet. Suggests opening it with Visual Studio! 50% good
Chrome: the hints are fully ignored. The CSV data is shown in the browser. 0% good.
Of course in all of these cases I'm referring to the browsers as they come out of they box, with no customization of the mime/application mappings.
For C# MVC 4.5 you need to do like this:
Response.Clear();
Response.ContentType = "application/CSV";
Response.AddHeader("content-disposition", "attachment; filename=\"" + fileName + ".csv\"");
Response.Write(dataNeedToPrint);
Response.End();
return new EmptyResult(); //this line is important else it will not work.

Resources