Httplistener and file upload

这一生的挚爱 提交于 2019-11-27 22:20:32
Paul Wheeler

I think you are making things harder on yourself than necessary by doing this with an HttpListener rather than using the built in facilities of ASP.Net. But if you must do it this way here is some sample code. Note: 1) I'm assuming you're using enctype="multipart/form-data" on your <form>. 2) This code is designed to be used with a form containing only your <input type="file" /> if you want to post other fields or multiple files you'll have to change the code. 3) This is meant to be a proof of concept/example, it may have bugs, and is not particularly flexible.

static void Main(string[] args)
{
    HttpListener listener = new HttpListener();
    listener.Prefixes.Add("http://localhost:8080/ListenerTest/");
    listener.Start();

    HttpListenerContext context = listener.GetContext();

    SaveFile(context.Request.ContentEncoding, GetBoundary(context.Request.ContentType), context.Request.InputStream);

    context.Response.StatusCode = 200;
    context.Response.ContentType = "text/html";
    using (StreamWriter writer = new StreamWriter(context.Response.OutputStream, Encoding.UTF8))
        writer.WriteLine("File Uploaded");

    context.Response.Close();

    listener.Stop();

}

private static String GetBoundary(String ctype)
{
    return "--" + ctype.Split(';')[1].Split('=')[1];
}

private static void SaveFile(Encoding enc, String boundary, Stream input)
{
    Byte[] boundaryBytes = enc.GetBytes(boundary);
    Int32 boundaryLen = boundaryBytes.Length;

    using (FileStream output = new FileStream("data", FileMode.Create, FileAccess.Write))
    {
        Byte[] buffer = new Byte[1024];
        Int32 len = input.Read(buffer, 0, 1024);
        Int32 startPos = -1;

        // Find start boundary
        while (true)
        {
            if (len == 0)
            {
                throw new Exception("Start Boundaray Not Found");
            }

            startPos = IndexOf(buffer, len, boundaryBytes);
            if (startPos >= 0)
            {
                break;
            }
            else
            {
                Array.Copy(buffer, len - boundaryLen, buffer, 0, boundaryLen);
                len = input.Read(buffer, boundaryLen, 1024 - boundaryLen);
            }
        }

        // Skip four lines (Boundary, Content-Disposition, Content-Type, and a blank)
        for (Int32 i = 0; i < 4; i++)
        {
            while (true)
            {
                if (len == 0)
                {
                    throw new Exception("Preamble not Found.");
                }

                startPos = Array.IndexOf(buffer, enc.GetBytes("\n")[0], startPos);
                if (startPos >= 0)
                {
                    startPos++;
                    break;
                }
                else
                {
                    len = input.Read(buffer, 0, 1024);
                }
            }
        }

        Array.Copy(buffer, startPos, buffer, 0, len - startPos);
        len = len - startPos;

        while (true)
        {
            Int32 endPos = IndexOf(buffer, len, boundaryBytes);
            if (endPos >= 0)
            {
                if (endPos > 0) output.Write(buffer, 0, endPos-2);
                break;
            }
            else if (len <= boundaryLen)
            {
                throw new Exception("End Boundaray Not Found");
            }
            else
            {
                output.Write(buffer, 0, len - boundaryLen);
                Array.Copy(buffer, len - boundaryLen, buffer, 0, boundaryLen);
                len = input.Read(buffer, boundaryLen, 1024 - boundaryLen) + boundaryLen;
            }
        }
    }
}

private static Int32 IndexOf(Byte[] buffer, Int32 len, Byte[] boundaryBytes)
{
    for (Int32 i = 0; i <= len - boundaryBytes.Length; i++)
    {
        Boolean match = true;
        for (Int32 j = 0; j < boundaryBytes.Length && match; j++)
        {
            match = buffer[i + j] == boundaryBytes[j];
        }

        if (match)
        {
            return i;
        }
    }

    return -1;
}

To help you better understand what the code above is doing, here is what the body of the HTTP POST looks like:

Content-Type: multipart/form-data; boundary=----WebKitFormBoundary9lcB0OZVXSqZLbmv

------WebKitFormBoundary9lcB0OZVXSqZLbmv
Content-Disposition: form-data; name="my_file"; filename="Test.txt"
Content-Type: text/plain

Test
------WebKitFormBoundary9lcB0OZVXSqZLbmv--

I've left out the irrelevant headers. As you can see, you need to parse the body by scanning through to find the beginning and ending boundary sequences, and drop the sub headers that come before the content of your file. Unfortunately you cannot use StreamReader because of the potential for binary data. Also unfortunate is the fact that there is no per file Content-Length (the Content-Length header for the request specifies the total length of the body including boundaries, sub-headers, and spacing.

The problem is you are reading the file as text.

You need to read the file as a bytearray instead and using the BinaryReader is better and easier to use than StreamReader:

Byte[] bytes;
using (System.IO.BinaryReader r = new System.IO.BinaryReader(request.InputStream))
{
    // Read the data from the stream into the byte array
    bytes = r.ReadBytes(Convert.ToInt32(request.InputStream.Length));
}
MemoryStream mstream = new MemoryStream(bytes);

May have bugs, test thoroughly. This one gets all post, get, and files.

using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Net;
using System.Text;
using System.Web;

namespace DUSTLauncher
{
    class HttpNameValueCollection
    {
        public class File
        {
            private string _fileName;
            public string FileName { get { return _fileName ?? (_fileName = ""); } set { _fileName = value; } }

            private string _fileData;
            public string FileData { get { return _fileData ?? (_fileName = ""); } set { _fileData = value; } }

            private string _contentType;
            public string ContentType { get { return _contentType ?? (_contentType = ""); } set { _contentType = value; } }
        }

        private NameValueCollection _get;
        private Dictionary<string, File> _files;
        private readonly HttpListenerContext _ctx;

        public NameValueCollection Get { get { return _get ?? (_get = new NameValueCollection()); } set { _get = value; } }
        public NameValueCollection Post { get { return _ctx.Request.QueryString; } }
        public Dictionary<string, File> Files { get { return _files ?? (_files = new Dictionary<string, File>()); } set { _files = value; } }

        private void PopulatePostMultiPart(string post_string)
        {
            var boundary_index = _ctx.Request.ContentType.IndexOf("boundary=") + 9;
            var boundary = _ctx.Request.ContentType.Substring(boundary_index, _ctx.Request.ContentType.Length - boundary_index);

            var upper_bound = post_string.Length - 4;

            if (post_string.Substring(2, boundary.Length) != boundary)
                throw (new InvalidDataException());

            var raw_post_strings = new List<string>();
            var current_string = new StringBuilder();

            for (var x = 4 + boundary.Length; x < upper_bound; ++x)
            {
                if (post_string.Substring(x, boundary.Length) == boundary)
                {
                    x += boundary.Length + 1;
                    raw_post_strings.Add(current_string.ToString().Remove(current_string.Length - 3, 3));
                    current_string.Clear();
                    continue;
                }

                current_string.Append(post_string[x]);

                var post_variable_string = current_string.ToString();

                var end_of_header = post_variable_string.IndexOf("\r\n\r\n");

                if (end_of_header == -1) throw (new InvalidDataException());

                var filename_index = post_variable_string.IndexOf("filename=\"", 0, end_of_header);
                var filename_starts = filename_index + 10;
                var content_type_starts = post_variable_string.IndexOf("Content-Type: ", 0, end_of_header) + 14;
                var name_starts = post_variable_string.IndexOf("name=\"") + 6;
                var data_starts = end_of_header + 4;

                if (filename_index == -1) continue;

                var filename = post_variable_string.Substring(filename_starts, post_variable_string.IndexOf("\"", filename_starts) - filename_starts);
                var content_type = post_variable_string.Substring(content_type_starts, post_variable_string.IndexOf("\r\n", content_type_starts) - content_type_starts);
                var file_data = post_variable_string.Substring(data_starts, post_variable_string.Length - data_starts);
                var name = post_variable_string.Substring(name_starts, post_variable_string.IndexOf("\"", name_starts) - name_starts);
                Files.Add(name, new File() { FileName = filename, ContentType = content_type, FileData = file_data });
                continue;

            }
        }

        private void PopulatePost()
        {
            if (_ctx.Request.HttpMethod != "POST" || _ctx.Request.ContentType == null) return;

            var post_string = new StreamReader(_ctx.Request.InputStream, _ctx.Request.ContentEncoding).ReadToEnd();

            if (_ctx.Request.ContentType.StartsWith("multipart/form-data"))
                PopulatePostMultiPart(post_string);
            else
                Get = HttpUtility.ParseQueryString(post_string);

        }

        public HttpNameValueCollection(ref HttpListenerContext ctx)
        {
            _ctx = ctx;
            PopulatePost();
        }


    }
}

I like @paul-wheeler answer. However I needed to modify their code to include some additional data (In this case, the directory structure).

I'm using this code to upload files:

var myDropzone = $("#fileDropZone");
myDropzone.dropzone(
    {
        url: "http://" + self.location.hostname + "/Path/Files.html,
        method: "post",
        createImageThumbnails: true,
        previewTemplate: document.querySelector('#previewTemplateId').innerHTML,
        clickable: false,
        init: function () {
            this.on('sending', function(file, xhr, formData){
                // xhr is XMLHttpRequest
                var name = file.fullPath;
                if (typeof (file.fullPath) === "undefined") {
                    name = file.name;
                }

                formData.append('fileNameWithPath', name);
            });
        }
    });

Here is @paul-wheeler modified code. Thanks @paul-wheeler.

public class FileManager
{
    public static void SaveFile(HttpListenerRequest request, string savePath)
    {
        var tempFileName = Path.Combine(savePath, $"{DateTime.Now.Ticks}.tmp");
        if (!Directory.Exists(savePath))
        {
            Directory.CreateDirectory(savePath);
        }

        var (res, fileName) = SaveTmpFile(request, tempFileName);
        if (res)
        {
            var filePath = Path.Combine(savePath, fileName);
            var fileDir = filePath.Substring(0, filePath.LastIndexOf(Path.DirectorySeparatorChar));
            if (!Directory.Exists(fileDir))
            {
                Directory.CreateDirectory(fileDir);
            }

            if (File.Exists(filePath))
            {
                File.Delete(filePath);
            }

            File.Move(tempFileName, filePath);
        }
    }

    private static (bool, string) SaveTmpFile(HttpListenerRequest request, string tempFileName)
    {
        var enc = request.ContentEncoding;
        var boundary = GetBoundary(request.ContentType);
        var input = request.InputStream;

        byte[] boundaryBytes = enc.GetBytes(boundary);
        var boundaryLen = boundaryBytes.Length;

        using (FileStream output = new FileStream(tempFileName, FileMode.Create, FileAccess.Write))
        {
            var buffer = new byte[1024];
            var len = input.Read(buffer, 0, 1024);
            var startPos = -1;

            // Get file name and relative path
            var strBuffer = Encoding.Default.GetString(buffer);
            var strStart = strBuffer.IndexOf("fileNameWithPath") + 21;
            if (strStart < 21)
            {
                Logger.LogError("File name not found");
                return (false, null);
            }

            var strEnd = strBuffer.IndexOf(boundary, strStart) - 2;
            var fileName = strBuffer.Substring(strStart, strEnd - strStart);
            fileName = fileName.Replace('/', Path.DirectorySeparatorChar);

            // Find start boundary
            while (true)
            {
                if (len == 0)
                {
                    Logger.LogError("Find start boundary not found");
                    return (false, null);
                }

                startPos = IndexOf(buffer, len, boundaryBytes);
                if (startPos >= 0)
                {
                    break;
                }
                else
                {
                    Array.Copy(buffer, len - boundaryLen, buffer, 0, boundaryLen);
                    len = input.Read(buffer, boundaryLen, 1024 - boundaryLen);
                }
            }

            // Advance to data
            var foundData = false;
            while (!foundData)
            {
                while (true)
                {
                    if (len == 0)
                    {
                        Logger.LogError("Preamble not Found");
                        return (false, null);
                    }

                    startPos = Array.IndexOf(buffer, enc.GetBytes("\n")[0], startPos);
                    if (startPos >= 0)
                    {
                        startPos++;
                        break;
                    }
                    else
                    {
                        // In case read in line is longer than buffer
                        len = input.Read(buffer, 0, 1024);
                    }
                }

                var currStr = Encoding.Default.GetString(buffer).Substring(startPos);
                if (currStr.StartsWith("Content-Type:"))
                {
                    // Go past the last carriage-return\line-break. (\r\n)
                    startPos = Array.IndexOf(buffer, enc.GetBytes("\n")[0], startPos) + 3;
                    break;
                }
            }

            Array.Copy(buffer, startPos, buffer, 0, len - startPos);
            len = len - startPos;

            while (true)
            {
                var endPos = IndexOf(buffer, len, boundaryBytes);
                if (endPos >= 0)
                {
                    if (endPos > 0) output.Write(buffer, 0, endPos - 2);
                    break;
                }
                else if (len <= boundaryLen)
                {
                    Logger.LogError("End Boundaray Not Found");
                    return (false, null);
                }
                else
                {
                    output.Write(buffer, 0, len - boundaryLen);
                    Array.Copy(buffer, len - boundaryLen, buffer, 0, boundaryLen);
                    len = input.Read(buffer, boundaryLen, 1024 - boundaryLen) + boundaryLen;
                }
            }

            return (true, fileName);
        }
    }

    private static int IndexOf(byte[] buffer, int len, byte[] boundaryBytes)
    {
        for (int i = 0; i <= len - boundaryBytes.Length; i++)
        {
            var match = true;
            for (var j = 0; j < boundaryBytes.Length && match; j++)
            {
                match = buffer[i + j] == boundaryBytes[j];
            }

            if (match)
            {
                return i;
            }
        }

        return -1;
    }

    private static string GetBoundary(string ctype)
    {
        return "--" + ctype.Split(';')[1].Split('=')[1];
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!