1

In my extjs6 project I am uploading a file to my webapi. (using form... fileuploadfield) The file gets successfully uploaded and it is supposed to return a simple string list however even though the file gets uploaded properly, in my controller it ALWAYS returns FAILURE on form.submit. Reason..."Blocked a frame with origin "http://localhost:57007" from accessing a cross-origin frame."

I believe I read somewhere that when I do form.submit it creates some kind of frame that causes the cross origin.

Normally I wouldn't care if it always returns failed because the job is still getting done... but I want to return something which wont work if it fails. Can someone help me with a SECURE way of doing this?

PANEL

                    xtype: 'form',
                fileUpload: true, //(1)
                width: 500,
                frame: true,
                title: 'Client Recap Upload Form',
                bodyPadding: '10 10 10 10',
                margin: '10px 10px 10px 10px',
                standardSubmit: false,
                defaults: {
                    anchor: '100%',
                    allowBlank: false,
                    msgTarget: 'side',
                    labelWidth: 50
                },
                items: [{
                    xtype: 'fileuploadfield',
                    emptyText: 'Select a file',
                    fieldLabel: 'Filename',
                    name: 'file',
                    buttonText: 'Choose a file'
                }],
                buttons: [
                    {
                        text: 'Upload',
                        listeners: {
                            click: 'onButtonFileUpload'
                        }
                    }
                ]

CONTROLLER

    onUploadClientRecap: function (field, e, options, mid) {

    var me = this;

    if (field.up('form').getForm().isValid()) {
        field.up('form').getForm().submit({
            url: ExtApplication4.util.GlobalVar.urlTM_UploadClientRecap + mid,
            waitMsg: 'Uploading your file...',
            success: function (form, o)
            {
                Ext.Msg.show({
                    title: 'Result',
                    msg: o.response.responseText,//.result.result,
                    buttons: Ext.Msg.OK,
                    icon: Ext.Msg.INFO
                });
            },
            failure: function (form, o)
            {
                debugger;
                Ext.Msg.show({
                    title: 'Result',
                    msg: 'File Uploaded...',
                    buttons: Ext.Msg.OK,
                    icon: Ext.Msg.INFO
                });
            }
        });
    }
},

WEB API

        [Route("api/tradematch/UploadClientRecap/{mid}")]
    [HttpPost]
    public List<string> UploadClientRecap(HttpRequestMessage request, int mid)
    {
        HttpContext context = HttpContext.Current;
        HttpPostedFile postedFile = context.Request.Files["file"];

        return _repo.UploadClientRecap(postedFile, mid);
    }

in my webapi I am also running this code in my application_beginrequest

        protected void Application_BeginRequest(object sender, EventArgs e)
    {
        string[] allowedOrigin = new string[5];
        allowedOrigin[0] = "http://localhost:57007";
        allowedOrigin[1] = "http://x.com";
        allowedOrigin[2] = "https://x.com";
        allowedOrigin[3] = "https://www.p.com";
        allowedOrigin[4] = "http://www.p.com";

        var origin = HttpContext.Current.Request.Headers["Origin"];
        if (origin != null && allowedOrigin.Contains(origin))
        {
            HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", origin);

            if (HttpContext.Current.Request.HttpMethod == "OPTIONS")
            {
                HttpContext.Current.Response.AddHeader("Cache-Control", "no-cache");
                HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods", "GET, POST");
                HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Accept, Authorization, X-Requested-With");
                HttpContext.Current.Response.AddHeader("Access-Control-Max-Age", "1728000");
                HttpContext.Current.Response.End();
            }
        }

trying new webapi to return redirect

        [Route("api/tradematch/UploadClientRecap/{mid}")]
    [HttpPost]
    public HttpResponseMessage UploadClientRecap(HttpRequestMessage request, int mid)
    {
        HttpContext context = HttpContext.Current;
        HttpPostedFile postedFile = context.Request.Files["file"];

        var response = Request.CreateResponse(HttpStatusCode.Moved);
        response.Headers.Location = new Uri("http://www.google.com/" + "&output=crudeOil");
        return response;

        //return _repo.UploadClientRecap(postedFile, mid);
    }
solarissf
  • 1,199
  • 2
  • 23
  • 58
  • Where exactly are you submitting the file To? The general solution is to explicitly allow CORS between the involved domains by sending appropriate http headers, but from your snippets it's impossible to give any specific advice... – Mastacheata Aug 05 '17 at 04:20
  • Could it be that your frontend is running on a different port than your api? You can use a reverse proxy to make them available under the same origin - or enable CORS as Mastacheata said already before, more here: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS – hwsw Aug 05 '17 at 13:38
  • @Mastacheata I just updated my code to show you the webapi. does that give you the info you need? – solarissf Aug 07 '17 at 17:39
  • @devbnz the web api is published on 1 pc locally, and the front end is published on a diff pc, so I'm sure they are different. – solarissf Aug 07 '17 at 17:40
  • i also added the code I run on Application_beginRequest – solarissf Aug 07 '17 at 17:43
  • Are you sure the headers are really passed with the response? I'm not super used to .NET programming, but it seems like the HttpContext.Current.Response and the actual returned response object have nothing to do with each other. You can check the headers using the Netwok Tab in Dev Tools or by sending a simple Upload request using curl or similar tools. – Mastacheata Aug 08 '17 at 02:05

1 Answers1

0

The upload (POST request) of the file is not subject to CORS; however, accessing the body of the iframe (whose context is currently a cross-domain origin) IS certainly subject to it, and is where the cross-domain issue is probably occurring (I've seen this many times).

One way I've circumvented this is to employ an approach similar to what this jquery plugin does: https://github.com/blueimp/jQuery-File-Upload/wiki/Cross-domain-uploads#cross-site-iframe-transport-uploads

In short, in your upload-handling code, you redirect to your client-side app, and pass along the desired data that you wish to be available as a result of the upload in the querystring (e.g., upload time, file name, etc). Then, in your client-side app, create a simple redirect page that will handle the incoming querystring and process it appropriately.

The reason all of this works is that content of the iframe will be ultimately served from the same domain as the request, once the redirect has occurred:

iframe src = cross-domain url

=> POST upload
=> Process upload
=> Redirect response to same domain as original client app

iframe src = original requesting client

Because of this, you can successfully read the content via JS without stepping on the cross-domain policies of iframes.

Here's a very basic example of what your upload code (in Node) might look like to create the redirect:

app.post('/api/photo', function(req, res) {
    if (done == true) {
        var params = {
            sometext: "I am some text",
            success: true,
            msg: 'The upload was successful',
            filename: 'Avatar.png'
        };

        res.redirect('http://myapp.com/results.html?data=' + JSON.stringify(params));
    }
});

And then, your redirect file to handle the response:

<html>
    <head>
        <meta charset="utf-8">
        <title>Cross-Domain Upload Redirect Page</title>
    </head>
    <body>
        <script>
            // strip off "?data="...
            var data = window.location.search.slice(6),
                decodedJSON = decodeURIComponent(data);
            // set JSON string into innerText and textContent 
            // so Ext.data.Connection can treat it
            // the same as regular iframe uploads
            document.body.innerText=document.body.textContent=decodedJSON;
    </script>
</body>

Joel Watson
  • 173
  • 4
  • I'm trying to understand this but I'm very new to some of this. starting at the beginning of your post where you create the redirect, you stated this in node, how do I do this? it it on my front end or in my webapi? my front end is a javascript framework EXTJS, and the front end api is c# – solarissf Aug 07 '17 at 18:00
  • The redirect would occur where the upload is being processed; in this case, in your application server code. – Joel Watson Aug 07 '17 at 18:08
  • i changed my webapi code to return a redirect but I'm not sure how to actually handle it on the front end. I am getting error requested url code was not found on the server. most likely because I just made it up. do you have an example of how to handle redirect in extjs or javascript? – solarissf Aug 07 '17 at 18:47
  • not sure if this matters, but this project is a single page application. which I think means I will only use the one main url. So even though this approach is loading a page, I want it to go back to the page it started at... which in this case is http://localhost:57007/Sencha/index.html. I tried returning response.Headers.Location = new Uri("http://localhost:57007/Sencha/index.html/?output=fakeparameter"); return response; but I get an error 404 not found – solarissf Aug 07 '17 at 19:15
  • if I only return this... Uri("localhost:57007/Sencha/index.html/.. I get error You're trying to decode an invalid JSON string – solarissf Aug 07 '17 at 19:31
  • You actually *don't* want it to go back to the page it started. What I suggested in my answer is that you create a new file in your application root (maybe "results.html" or something like that...name doesn't matter) and then include the code to parse the querystring. The important part is that the iframe's context is the same as the client application so that the content of the iframe can be accessible to the calling script in the Ext JS application. – Joel Watson Aug 07 '17 at 19:36