Reputation: 4829
I wanna let to some users download some files from my website and I don't want them to see the physical path of the downloading file.
I moved the files in a folder outside of my web folder and use Response.WriteFile(filePath) to download them. this help me to hide the path but this method doesn't support resume download which I really want it.
So what method you recommend I use?
Upvotes: 0
Views: 12777
Reputation: 4829
Here is the best approach:
public static bool DownloadFile(HttpContext httpContext, string filePath, long speed)
{
// Many changes: mostly declare variables near use
// Extracted duplicate references to HttpContext.Response and .Request
// also duplicate reference to .HttpMethod
// Removed try/catch blocks which hid any problems
var response = httpContext.Response;
var request = httpContext.Request;
var method = request.HttpMethod.ToUpper();
if (method != "GET" &&
method != "HEAD")
{
response.StatusCode = 501;
return false;
}
if (!File.Exists(filePath))
{
response.StatusCode = 404;
return false;
}
// Stream implements IDisposable so should be in a using block
using (var myFile = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
var fileLength = myFile.Length;
if (fileLength > Int32.MaxValue)
{
response.StatusCode = 413;
return false;
}
var lastUpdateTiemStr = File.GetLastWriteTimeUtc(filePath).ToString("r");
var fileName = Path.GetFileName(filePath);
var fileNameUrlEncoded = HttpUtility.UrlEncode(fileName, Encoding.UTF8);
var eTag = fileNameUrlEncoded + lastUpdateTiemStr;
var ifRange = request.Headers["If-Range"];
if (ifRange != null && ifRange.Replace("\"", "") != eTag)
{
response.StatusCode = 412;
return false;
}
long startBytes = 0;
// Just guessing, but I bet you want startBytes calculated before
// using to calculate content-length
var rangeHeader = request.Headers["Range"];
if (rangeHeader != null)
{
response.StatusCode = 206;
var range = rangeHeader.Split(new[] { '=', '-' });
startBytes = Convert.ToInt64(range[1]);
if (startBytes < 0 || startBytes >= fileLength)
{
// TODO: Find correct status code
response.StatusCode = (int)HttpStatusCode.BadRequest;
response.StatusDescription =
string.Format("Invalid start of range: {0}", startBytes);
return false;
}
}
response.Clear();
response.Buffer = false;
response.AddHeader("Content-MD5", GetMD5Hash(filePath));
response.AddHeader("Accept-Ranges", "bytes");
response.AppendHeader("ETag", string.Format("\"{0}\"", eTag));
response.AppendHeader("Last-Modified", lastUpdateTiemStr);
response.ContentType = "application/octet-stream";
response.AddHeader("Content-Disposition", "attachment;filename=" +
fileNameUrlEncoded.Replace("+", "%20").Replace(",",";"));
var remaining = fileLength - startBytes;
response.AddHeader("Content-Length", remaining.ToString());
response.AddHeader("Connection", "Keep-Alive");
response.ContentEncoding = Encoding.UTF8;
if (startBytes > 0)
{
response.AddHeader("Content-Range",
string.Format(" bytes {0}-{1}/{2}", startBytes, fileLength - 1, fileLength));
}
// BinaryReader implements IDisposable so should be in a using block
using (var br = new BinaryReader(myFile))
{
br.BaseStream.Seek(startBytes, SeekOrigin.Begin);
const int packSize = 1024 * 10; //read in block,every block 10K bytes
var maxCount = (int)Math.Ceiling((remaining + 0.0) / packSize); //download in block
for (var i = 0; i < maxCount && response.IsClientConnected; i++)
{
response.BinaryWrite(br.ReadBytes(packSize));
response.Flush();
// HACK: Unexplained sleep
var sleep = (int)Math.Ceiling(1000.0 * packSize / speed); //the number of millisecond
if (sleep > 1)
Thread.Sleep(sleep);
}
}
}
return true;
}
static string GetMD5Hash(string input)
{
// Create a new instance of the MD5CryptoServiceProvider object.
MD5 md5Hasher = MD5.Create();
// Convert the input string to a byte array and compute the hash.
byte[] data = md5Hasher.ComputeHash(Encoding.Default.GetBytes(input));
// Create a new Stringbuilder to collect the bytes
// and create a string.
StringBuilder sBuilder = new StringBuilder();
// Loop through each byte of the hashed data
// and format each one as a hexadecimal string.
for (int i = 0; i < data.Length; i++)
{
sBuilder.Append(data[i].ToString("x2"));
}
// Return the hexadecimal string.
return sBuilder.ToString();
}
Upvotes: 0
Reputation: 7533
I would implement a file handler (.ashx file) that serves the files based on some sort of id or name in the querystring and send it like this:
asp.net ashx handler prompting download instead of displaying file
Upvotes: 0
Reputation: 114
It's simple, I have done this before. Just hide the "input file" control, so it is hidden on the UI, then create a button which click event triggers the click event of the "input file" control. Then, you can also use javascript (in my case, it is JQuery), to take the name of the browsed file into a textbox which represent the name of the file.
Therefore, the file path is hidden from the UI.
Upvotes: 0
Reputation: 12904
Create a temporary directory and copy the file into there. You could even rename the file as something else so that it is not 'guessable'.
I am assuming if they can download the file, the contents of it are applicable to that person therefore there is no issue that user knowing the direct link. Others will not be able to guess the random directory and/or filename.
var directoryName = String.Format("{0}\{1}\{2}",
Server.MapPath("original Path"),System.Guid.NewGuid().Replace("-",""), fileName);
This pretty much the same process we use to export information from our system.
Upvotes: 1