Angular.js html5 file upload to grails controller - javascript

I would like to have the files that are dropped in this angularjs html5 upload example at JSFiddle:
http://jsfiddle.net/danielzen/utp7j/
uploaded to a backend by a grails controller.
While trying to accomplish this I created a simple grails controller:
class UploadController {
def index() {
if (request instanceof MultipartHttpServletRequest){
for(filename in request.getFileNames()){
MultipartFile file = request.getFile(filename)
String newFileName = UUID.randomUUID().toString() + file.originalFilename.substring(file.originalFilename.lastIndexOf("."))
file.transferTo(new File("/home/myuser/temp/$newFileName"))
}
}
render "ok"
}
}
Then I open an ajax XmlHttpRequest2 POST to the grails controller:
xhr.open("POST", "http://localhost:8080/app/upload")
but the grails controller cannot cast the request to MultipartHttpServletRequest,
probably because this ajax way of invoking the grails controller is not using the multipart/form-data way of uploading.
I tried setting the header on xhr for enctype multipart/form-data to no avail.
I'm completely stuck at the moment and would like to know how I can process the uploaded files in the grails controller

I traced the POST request that was generated by the XmlHttpRequest.send(formData) call with the built-in Chrome Developer Tools.
To my surprise the request method was not of type POST with enctype=multipart/form-data but of type OPTIONS.
This hint got me on the right track and with the help of Google I found out that this OPTIONS request happens as a preflight check as per-spec defined by CORS.
From wikipdia:
Cross-origin resource sharing (CORS) is a mechanism that allows
JavaScript on a web page to make XMLHttpRequests to another domain,
not the domain the JavaScript originated from.[1] Such "cross-domain"
requests would otherwise be forbidden by web browsers, per the same
origin security policy. CORS defines a way in which the browser and
the server can interact to determine whether or not to allow the
cross-origin request.[2] It is more powerful than only allowing
same-origin requests, but it is more secure than simply allowing all
such cross-origin requests.
The CORS standard works by adding new HTTP headers that allow servers
to serve resources to permitted origin domains. Browsers support these
headers and enforce the restrictions they establish. Additionally, for
HTTP request methods that can cause side-effects on user data (in
particular, for HTTP methods other than GET, or for POST usage with
certain MIME types), the specification mandates that browsers
“preflight” the request, soliciting supported methods from the server
with an HTTP OPTIONS request header, and then, upon “approval” from
the server, sending the actual request with the actual HTTP request
method. Servers can also notify clients whether “credentials”
(including Cookies and HTTP Authentication data) should be sent with
requests.
Because my grails (tomcat) server was running from localhost:8080 and my html/javascript was running from within the WebStorm IDE on its built-in http server on localhost:63342, the XmlHttpRequest was effectively CORS, because different port numbers on the same host are also considered cross-origin.
Thus, I needed to make sure that the Grails (tomcat) server allowed this, and I was able to do this with the excellent cors plugin for Grails which can be found at https://github.com/davidtinker/grails-cors
After that the request was recognized as a MultipartHttpServletRequest and the file could be fetched from params

Taking a peek at the network when doing the upload from the fiddle you get:
------WebKitFormBoundarygZi30fqQLB2A9qAC
Content-Disposition: form-data; name="uploadedFile"; filename="file.txt"
Content-Type: text/javascript
------WebKitFormBoundarygZi30fqQLB2A9qAC--
I believe then you'll get an "uploadedFile" as the param key into Grails, so you could write a controller action like:
def uploadFile(CommonsMultipartFile uploadedFile) {
///accessing the file data: uploadedFile.bytes, uploadedFile.contentType, uploadedFile.originalFilename
}
To be sure what is being passed do a debug on the params map that the action receives and change the action method parameter name accordingly.

<body ng-controller="FileUploadCtrl">
$scope.selectedFile=[];
$scope.onFileSelect = function ($files) {
$scope.uploadProgress = 0;
$scope.selectedFile = $files;
};
$scope.myData = {};
$scope.upload = function(){
var formData = new FormData();
formData.append("file", $scope.selectedFile[0]);
formData.append("data", myData.message.value);
$http.post('api/upload', formData, {
transformRequest: angular.identity,
headers: {
'enctype': 'multipart/form-data',
'Content-Type': undefined
}
})
.success(function(){
alert("done");
})
.error(function(){
alert("failed");
});
</body>
<form ng-submit="upload()" method="post" enctype="multipart/form-data" name="myData"> <div>
<textarea rows="3" name="message" placeholder="Send Message"></textarea>
<input type="file" ng-file-select="onFileSelect($files)" />
</div>
<button type="submit" class="btn btn-primary">upload</button>
</form>

Related

Axios how to set form data similar to jQuery ajax method?

I'm using axios in my application, but I'm having a hard time setting the content of the request.
There's currently a call to a URL using $.ajax like this:
$.ajax({
method: 'POST',
data: { 'accountId': accountId },
url: serverUrl,
/* success: ... */
});
And when I look at this request in Chrome dev tools, at the end I see something like this:
Now, I'm trying to do the same thing with axios:
axios.post(serverUrl, { accountId: accountId })
.then(/* ... */);
But, when I look at the request in Chrome dev tools, I have this:
How can I get axios to do the same formatting as jQuery? And maybe the question is that: are they different or it's just the representation?
Also, I noticed that the jQuery call is somehow adding this header: x-requested-with: XMLHttpRequest, but to have the same header in axios, I have to set it manually. Is it normal? Am I missing an axios configuration to add this header?
Thank you
Some frameworks use this header to detect XHR requests, for example. Grails Spring uses this header to identify the query XHR and gives the JSON response or the HTML response as a response.
Most Ajax libraries (Prototype, JQuery and Dojo from version 2.1) include the X-Requested-With header, which indicates that the query was made using XMLHttpRequest instead of running by clicking a regular hyperlink or submitting a form button.
A good reason for security is that it can prevent CSRF attacks, because this header can not be added to the cross domain of the AJAX request without the server's consent through CORS.
Only the following headers are allowed:
To accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type
any others call the "before flight" request in the browsers supported by CORS.
Without CORS, X-Requested-With can not be added to an XHR request with a cross domain.
If the server checks the presence of this header, it knows that the request did not initiate an attempt to make a request on behalf of the user from the attacker's domain using JavaScript.
It also checks that the request was not sent from the usual HTML form, from which it is more difficult to verify that it is not a cross domain without the use of tokens. (However, checking the Origin header can be an option in supported browsers although you leave old browsers vulnerable.)
See also: https://markitzeroday.com/x-requested-with/cors/2017/06/29/csrf-mitigation-for-ajax-requests.html
So also read for greater understanding:
FormData()
https://developer.mozilla.org/en-US/docs/Web/API/FormData/Using_FormData_Objects
https://developer.mozilla.org/en-US/docs/Web/API/FormData/FormData
Request Payload
What's the difference between "Request Payload" vs "Form Data" as seen in Chrome dev tools Network tab
As documented here, You can use the URLSearchParams API to send data in the application/x-www-form-urlencoded format using axios.
Example from offical docs:
var params = new URLSearchParams();
params.append('param1', 'value1');
params.append('param2', 'value2');
axios.post('/foo', params);

Unable to load Angular templates cross-domain with custom headers in requests

we have a project for a client that consists of 3 different websites, and we wanted to re-use as much code as possible. We have one project that contains the bulk of the core Angular JavaScript / templates for directives etc.
We are using ASP.NET 4 and Angular 1.5.5.
I am able to render out a template that's held in Project 1 from Project 2 using the absolute URL with CORS enabled.
However, we are also using a request interceptor for authentication for Single-Sign-On between applications, using the request headers. This stops the cross-domain templates from being pulled over into Project 2 with the following error:
XMLHttpRequest cannot load https://localhost:44302/path/to/template.html. Response for preflight has invalid HTTP status code 405.
If I remove the code that is setting an 'Authorization' header, the template works perfectly cross-domain.
It looks like the only difference in the request with the interceptor is it has the following headers:
access-control-request-headers:authorization
access-control-request-method:GET
Could this be causing the template to not be loaded?
This was solved by setting up CORS in the ASP.NET web side instead of the Angular side.
We enabled it in the Startup.cs file with a private method. We have a Web.config setting called Cors.AllowedOrigins which contains a ; separated string with each application we want to allow in CORS.
private void EnableCors(IAppBuilder app)
{
var policy = new CorsPolicy
{
AllowAnyMethod = true,
AllowAnyHeader = true
};
var origins = ConfigurationManager.AppSettings["Cors.AllowedOrigins"];
if (origins != null)
{
foreach (var origin in origins.Split(';'))
{
policy.Origins.Add(origin);
}
}
else
{
policy.AllowAnyOrigin = true;
}
var corsOptions = new CorsOptions
{
PolicyProvider = new CorsPolicyProvider
{
PolicyResolver = context => Task.FromResult(policy)
}
};
app.UseCors(corsOptions);
}
And we called this in the Configuration method, passing the IAppBuilder.
Adding the header makes the browser make a preflight OPTIONS request, and the server returns a 405 for that OPTIONS request. The browser then aborts the "real" request. Only simple requests can be made without the need of this preflight request. You can only use a limited set of headers to qualify as simple request
If you can serve the template on the same domain and port, it will not be a CORS request and this preflight request will not be made. If not, you can send the correct response to the OPTIONS request using this guide.

Trying to make a JSON POST request to the TVDB REST API at https://api.thetvdb.com/, but getting CORS error

I am porting an application from Django to purely HTML5/CSS with AngularJS and am facing issues making JSON POST requests to TheTVDB REST API server (https://api.thetvdb.com/).
The API call is being done in a service on AngularJS:
return {
login: function(){
return $http({
method: 'POST',
dataType: 'json',
headers: {'Content-Type': 'application/json'},
data: {'apikey':apiKey, 'username': username, 'userkey': identifier},
url: tvdbAuthURL
})
}
}
And then I get this error:
XMLHttpRequest cannot load https://api.thetvdb.com/login/. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8000' is therefore not allowed access. The response had HTTP status code 401.
I have tried avoiding pre-flighting the request but no luck and since I did not have this issue using python requests lib, for example, (I trigger request to the tvDB using this python lib from the same machine running the angular JS app with no problems at all) I wonder if there isn't a way or a different directive in AngularJS to keep it from setting CORS headers.
TheTVDB will not add CORS to their server as most of their users are running Laravel (PHP), Java and Django(Python) applications and are using their services with no such CORS problems.
Your help is much appreciated.
Thank you,
Best Regards
TS
You can have a look here: https://forums.thetvdb.com/viewtopic.php?t=9125
The TVdb doesn't provide any cors header, which means you can't access the API directly via Javascript. However, as #Matt West said, the cors policy only works in browsers.
The quick solution: Create a python program as a proxy, which redirect all your AJAX request to TVdb.
Your Python request works because it is coming from your server or local machine, and the same origin policy only applies inside a browser.
Your best answer is to use a server-side script. Have your client side POST request go to a controller or endpoint on your server, which then triggers the server script. Assuming there is some return data from the API, you'd add that to your server's response and then send it to the client.
Use a JSONP request. That way you can dodge the CORS.
EDIT: sorry, you're using POST, this would only work for get requests.
You can create a proxy that receives the request and then makes a CURL POST. There is no server side 'CORS' so you will be ok.

Using JQuery.ajax to post on another domain

I need to send a multipart form POST request from my web application running on Chrome.
That works well with the following code:
<form method="post" action="http://localhost:999/some/path" target="iframeId" id="mainForm">
...
</form>
<iframe id="iframeId" name="iframeId" width="100%"></iframe>
I would like to create the multipart request payload manually instead, since the file to be submitted needs to be encrypted first.
var boundary = "---------------------------7da24f2e50046";
var body = '--' + boundary + '\r\n'
+ 'Content-Disposition: form-data; name="file";'
+ 'filename="temp.bin"\r\n'
+ 'Content-type: application/octet-stream\r\n\r\n'
+ body + '\r\n'
+ boundary + '--';
$.ajax({
contentType: "multipart/form-data",
data: body,
type: "POST",
url: "http://localhost:999/some/path",
success: function (data, status) {
alert('done');
}
});
When I run this code I get the following error:
XMLHttpRequest cannot load localhost:999/some/path. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'file://' is therefore not allowed access.
Why setting the target of the POST to an iFrame works, but an ajax won't? Is there a way to work around this, to be able to build my own multipart payload?
Why setting the target of the POST to an iFrame works, but an ajax won't?
Because Ajax would let the JavaScript running in Alice's browser on the site (Mallory's site) that triggered the request access the response from Bob's site (which could leak private data to Mallory when only Alice and Bob should have access to it).
iframes have other mechanisms to prevent JS from accessing the data.
Is there a way to work around this, to be able to build my own multipart payload?
Several. The main ones are:
CORS
JSONP (not suitable for POST requests)
Having JS make the request to the same server and then relaying it using server side code
To make a cross domain AJAX request the receiving domain must accept CORS requests, or you need to send in JSONP format. The latter is not really going to work for you as you're trying to send multi-part form data.
Your only option is to check if the receiving domain does indeed accept CORS. If not you will need to make this request on the server side.
This is all due to the Same Origin Policy which is a security policy that all modern browsers now implement.

How to add an authorization header to a DOM form element?

I am trying to upload a file to a server, and have my DOM element setup as such:
_uploadForm = document.createElement("form");
_uploadForm.method = "POST";
_uploadForm.action = "#";
if(_isBrowser.IE)
_uploadForm.encoding = "multipart/form-data";
else
_uploadForm.enctype = "multipart/form-data";
My server requires an http basic authorization header. How can I pass that through this DOM element?
Your javascript client is going to have to add headers when submitting the form.
I am not sure which javacript client lib you are using, but things like angular do allow you to set default headers or even have interceptors that can add headers before sending.
var req = {
method: 'POST',
url: 'http://example.com',
headers: {
'Content-Type': undefined,
'Authorization': 'your-scheme your-token-perhaps'
},
data: { test: 'test' }
}
DOM elements have nothing to do with authorization, they're just the internal browser's representation of the various html tags. Authorization is demanded by the server - if the page is in a protected area, then the server will demand credentials, and that has nothing to do with the form itself.
The easiest way is to serve the upload form and the upload handler from the same folder, and use .htaccess to force Basic authentication for both. Then the browser automatically requests credentials when the upload form is loaded, and the credentials would be sent automatically with the upload (I believe).
However, your best bet is not to use HTTP Basic authentication at all. It sends passwords in clear text on every request unless you're using SSL, and in general, it is a hassle to use.
If you insist on using Basic, I believe it can be done with ActionScript (compiled to an SWF). I also know a way to set an Authorization header on an AJAX call, but you can't upload a file with an AJAX call, and the browser does not remember Authorization headers sent via an AJAX call.

Categories