Majid Shahabfar
Majid Shahabfar

Reputation: 4829

Hiding the physical path of a file downloading in ASP.NET

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

Answers (4)

Majid Shahabfar
Majid Shahabfar

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

Dennisch
Dennisch

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

Zan
Zan

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

Kinexus
Kinexus

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

Related Questions