A Processing Method of Exceptional Failed to execute send on XMLHttpRequest when uploading files from ASP.NET WebApi

Keywords: JQuery firewall ASP.NET Javascript

The author recently wrote a system for uploading files through ASP.NET MVC4 WebApi, jQuery, ajax and FormData (see Multi-file uploading method based on ASP.NET MVC4, WebApi, jQuery and FormData Testing everything on your laptop, but publishing it to the client server (cloud server, Windows Server 2012) Operating system, with strong firewall protection), some files upload normally, but most files upload throw the following exception information (captured by error of ajax of jquery): Network Error: Failed to execute'send'on'XMLHttpRequest': Failed to load http://xxx/api/FilesApi/Upload. Here, Upload is a Post method for routing api and controller FilesApi.

Many methods are searched, generally the exception caused by cross-domain calls, but the related methods introduced do not solve the problem. The author estimates that the firewall checks the content and name of the file passed in through http, or the browser has a strong content filtering function, and refuses to call the relevant Api method when it finds that the keyword string it considers to be threatening. For this reason, the author thinks of FileReader object introduced by HTML5 (FormData is also introduced by html5). The client uses the asynchronous method readAsURLData of this object to obtain Base64 encrypted text of file content (note, FileReader.readAsURLData() method uses UTF-8 encoding to encrypt base64). Then the text is sent through FormData and decrypted in WebApi server. Then save the file. Similarly, a Base64 encrypted js plug-in is used to encrypt the uploaded file name.

The following is the client script corresponding to the Home controller (uploading only two files) Index.cshtml:

@model CSUST.Files.TDirItems

@{
    Layout = null;
}

<!DOCTYPE html>
<html>
<head>
    <title>File upload</title>
<script type="text/javascript" src='@Url.Action("jquery-1.12.4.min.js", "scripts")'></script>
<script type="text/javascript" src='@Url.Action("jquery.base64.min.js", "scripts")'></script>

<script type="text/javascript">

    var fileData = [];

    $(document).ready(function ()
    {
        $("#tbFileName1").on("change", function(){
            GetFile(this);
        });

        $("#tbFileName2").on("change", function () {

            GetFile(this);
        });
    });
 
    
    function GetFile(fileObj)
    {
        fileData[fileObj.id] = null;

        var fileName = $(fileObj).val();
        if (fileName == null || fileName == "")  // File name is empty
        {
            return;
        }

        var files = $(fileObj).get(0).files;
        if (files[0].size > 4 * 1024 * 1024)
        {
            alert("A single file cannot be greater than 4 M. ");
            $(fileObj).val("");
            return;
        }

        var reader = new FileReader();
        reader.readAsDataURL(files[0]);
        
        reader.onload = function ()
        {
            var base64 = reader.result;
            var n = base64.indexOf("base64,");
            fileData[fileObj.id] = base64.substr(n + 7);
        }
    }

    function Clear()
    {
        $("#tbFileName1").val("");
        $("#tbFileName2").val("");
    }

    function Upload()
    {
        var f1 = $("#tbFileName1").val();
        var f2 = $("#tbFileName2").val();

        if ((f1 == null || f1 == "") && (f2 == null || f2 == ""))
        {
            alert("Upload at least one file.");
            return;
        }

        if (f1 != "" && fileData["tbFileName1"] == null)
        {
            alert("File 1 has not been read yet, later!");
			return;
        }

        if (f2 != "" && fileData["tbFileName2"] == null)
        {
            alert("File 2 has not been read yet, later!");
			return;
        }
		
		if(f1 == f2)
		{
			alert("The same file cannot be uploaded.");
			return;
		}

        var n1 = $("#tbTicket").val();
        var n2 = $("#ckAllowNewFiles").is(":checked");
        var n3 = $("#cbDirNameKeys").val();

        if (n1 == "")
        {
            alert("You must enter a validation password.");
            return;
        }

        if (n3.indexOf("\\") == 0)
        {
            alert("Cannot choose\\Note format folder entries.");
            return;
        }

        var formData = new FormData();

        formData.append("VerifyTicket", n1);
        formData.append("AllowNewFiles", n2);
        formData.append("DirNameKey", n3);

        if (f1 != "")
        {
            formData.append("FileName1", $.base64('encode', f1));
            formData.append("FileContent1", fileData["tbFileName1"]);
        }

        if (f2 != "")
        {
            formData.append("FileName2", $.base64('encode', f2));
            formData.append("FileContent2", fileData["tbFileName2"]);
        }

        SendFiles(formData);
    }

    function SendFiles(formData)
    {
        $.ajax({
            type: "post",
            url: '@Url.Action("Upload", "Api/FilesApi")',
            async: false,
            data: formData,
            contentType: false,
            processData: false,
            success: function (data, status)
            {
                if (status != "success")
                {
                    alert("Failed to upload file: " + status);
                    return;
                }

                if (data == null)
                {
                    alert("Failed to upload file, No result was returned.");
                    return;
                }

                if (data.IsFailed == true)
                {
                    alert(data.ErrorMessage);
                    return;
                }

                alert(data.Note);

                $("#tbFileName1").val("");
                $("#tbFileName2").val("");
            },
            error: function (xhr, status, err)
            {
                alert("Upload file exception: " + status + "," + err);
            }
        });
    }
</script>
</head>
<body>
    <form id="Form1">
        <div align="center">
            <h2><label>@Model.WebSiteTitle</label></h2>
            <table style="width: 1050px;" border="1">
                <tr style="height: 32px">
                    <td rowspan="2" style="width: 120px;text-align:center">
                        //file name
                    </td>
                    <td colspan="2" align="left">
                        <input ID="tbFileName1" name="tbFileName1" type="file" style="width: 96%;"/>
                    </td>
                </tr>
                <tr style="height: 32px">
                    <td colspan="2" align="left">
                        <input ID="tbFileName2" name="tbFileName2" type="file" style="width: 96%;"/>
                    </td>
                </tr>
                <tr style="height: 42px">
                    <td style="text-align: center">To folder</td>
                    <td style="width: 650px; text-align: left;">
                        <select ID="cbDirNameKeys" style="width: 650px;">
                            @foreach(CSUST.Files.TDirItem item in Model.DirItems)
                            {
                                <option>@item.DirNameKey</option>
                            }
                        </select>
                    </td>
                    <td style="width: 280px; text-align:left;">
                        <input type="checkbox" ID="ckAllowNewFiles" />
                        <label for="ckAllowNewFiles">Newly added cshtml.css.js.jpg Other documents</label>
                    </td>
                </tr>
                <tr style="height: 42px;">
                    <td style="height: 42px; text-align: center;">Verify password</td>
                    <td style="height: 42px; text-align: left;">
                        <input type="password" id="tbTicket" style="width: 645px;" />
                    </td>
                    <td style="height: 42px; text-align:center;">
                        <input type="button" ID="bnUpload" value="Upload files" onclick="Upload()" style="width: 96px; height: 32px;"/>   
                        <input type="button" ID="bnClearFile" value="Empty files" onclick="Clear()" style="width: 96px; height: 32px;" />
                    </td>
                </tr>
            </table>
        </div>
    </form>
</body>
</html>
The following is the POST method Upload corresponding to WebApi of server-side ASP.NET MVC4:

using System;
using System.Collections.Generic;
using System.Web;
using System.Web.Http;

namespace CSUST.Files
{
    public class FilesApiController : ApiController
    {
        [HttpPost]
        public CSUST.Web.TWebApiResult Upload()
        {
            try
            {
                var httpRequest = HttpContext.Current.Request;

                var dirNameKey = httpRequest.Form["DirNameKey"];
                var allowNewFiles = httpRequest.Form["AllowNewFiles"];
                var verifyTicket = httpRequest.Form["VerifyTicket"];

                if (dirNameKey.StartsWith(TDirItem.NoteChars) == true)
                {
                    return new Web.TWebApiResult("Cannot choose" + TDirItem.NoteChars + "Note format folder entries.");
                }

                TDirItems dirItems = new TDirItems();
                if (dirItems.VerifyTicket != verifyTicket)
                {
                    return new Web.TWebApiResult("Verify password error.");
                }

                List<string> fileNames = new List<string>();
                List<string> fileContents = new List<string>();

                if (string.IsNullOrWhiteSpace(httpRequest.Form["FileName1"]) == false)
                {
                    string fileName1 = this.GetFileNameByBase64(httpRequest.Form["FileName1"]);
                    fileNames.Add(fileName1);
                    fileContents.Add(httpRequest.Form["FileContent1"]);
                }

                if (string.IsNullOrWhiteSpace(httpRequest.Form["FileName2"]) == false)
                {
                    string fileName2 = this.GetFileNameByBase64(httpRequest.Form["FileName2"]);
                    fileNames.Add(fileName2);
                    fileContents.Add(httpRequest.Form["FileContent2"]);
                }

                CSUST.Text.TStringItemsBuilder sb = new Text.TStringItemsBuilder(Environment.NewLine);
                foreach (var fn in fileNames)
                {
                    if (dirItems.IsAllowedFileName(dirNameKey, fn) == false)
                    {
                        sb.AppendItem(fn + "Cannot save to the specified folder.");
                    }

                    var saveFileName = dirItems.GetSavedFileName(dirNameKey, fn);

                    if (System.IO.File.Exists(saveFileName) == false && allowNewFiles.ToUpper() != "TRUE")
                    {
                        sb.AppendItem(fn + "You must check the new check box when you create a new file.");
                    }
                }

                if (sb.Length > 0)
                {
                    return new Web.TWebApiResult(sb.ToString());
                }

                sb.Clear();
                sb.AppendItem("Save the file successfully:");
                for (int k = 0; k < fileNames.Count; k++)
                {

                    var saveFileName = dirItems.GetSavedFileName(dirNameKey, fileNames[k]);
                    byte[] fb = Convert.FromBase64String(fileContents[k]);
                    using (System.IO.FileStream fs = new System.IO.FileStream(saveFileName, System.IO.FileMode.Create, System.IO.FileAccess.Write))
                    {
                        fs.Write(fb, 0, fb.Length);
                        fs.Flush();
                        sb.AppendItem(saveFileName);
                    }
                }

                CSUST.Web.TWebApiResult r = new Web.TWebApiResult() { Note = sb.ToString() };
                return r;
            }
            catch (Exception err)
            {
                return new CSUST.Web.TWebApiResult(err, true);
            }
        }

        private string GetFileNameByBase64(string base64FileName)
        {
            byte[] b = Convert.FromBase64String(base64FileName);
            string fileName = System.Text.Encoding.UTF8.GetString(b);  // The front end uses base64 encryption to prevent text strings from being rejected by firewalls
            return System.IO.Path.GetFileName(fileName);
        }
    }
}

It is normal to submit general files after the above technology processing, but the above exception is still thrown when uploading Global. asax files. When testing, the file is renamed @@Global.asax and uploaded normally. Obviously, browsers or firewalls have rejected Global. asax as a threat (I guess browsers have rejected upload submissions).

At present, some of the problems have been solved. However, it is not known whether encrypted text is considered a threat by firewalls or browsers. Fundamentally, it's not clear whether it's a browser or a firewall or Windows Server that refused WebApi access in 2012. Of course, you can negotiate with the network management to open the firewall for testing. However, the firewall is controlled by the user, which involves the security of cloud services and so on. Generally, it will not be released.

When you come across a wall, you can find someone to open a hole, or you can take a ladder to turn over. Ha ha, the author adopted the latter method.

Posted by extrovertive on Thu, 10 Jan 2019 19:36:10 -0800