I'm designing an API that receives a form post request from the front end. The front-end form contains both regular form
fields and a file upload field. Let's say the behaviour of a trivial front-end from show as below
<form>
<input type="file" name="image">
<input type="text" name="field_1">
<input type="text" name="field_2">
</form>
would result the following post request:
Content-Disposition: form-data; name="image"
Content-Type: application/octet-stream
===CONTENT OF THE IMAGE===
Content-Disposition: form-data; name="field_1"
value of field 1
Content-Disposition: form-data; name="field_2"
value of field 2
But another backend developer insisted that we should wrap all of the fields except the image into a json object so that the request appears like
Content-Disposition: form-data; name="image"
Content-Type: application/octet-stream
===CONTENT OF THE IMAGE===
Content-Disposition: form-data; name="body"
{
"field_1": "value of field 1",
"field_2": "value of field 2"
}
Our backend technology is Spring/Boot, and as a result, the latter design would greatly simplify our controller signature, like
public EmptyResponse acceptFormInput(
#RequestPart EmptyResponse body,
#RequestPart("file") MultipartFile file
)
It appears to me that the Controller should directly reflect what our API looks like. And the controller signature should reflect the API declaration. But that fellow developer argued that the nesting API design is also part of the W3C standard (in draft though), thus should be doable. (W3C plan)
I'm having trouble figuring out which is the common practice in designing such API. Any suggestion is appreciated.
Related
I've built a RESTful service with Spring (java annotation based configuration) which I can execute successfully via Curl. I'm trying to submit files via a HTML Form too, however that's not working.
#RequestMapping(path = "/upload", method = RequestMethod.POST)
public String handleFileUpload(#RequestPart(value = "file") MultipartFile file,
RedirectAttributes redirectAttributes) {
logger.info("POST '/upload'");
storageService.store(file);
redirectAttributes.addFlashAttribute("message",
"You successfully uploaded " + file.getOriginalFilename() + "!");
return "redirect:/";
}
And I'm submitting a file via Curl as follows:
curl -i -H "Content-Type: multipart/*; boundary=------------BOUNDARY--" -X POST --noproxy localhost, localhost:8080/upload -F "file=#test.txt"
Even though my form submission has an input of type file, with the name file, I get this error via a HTML form
org.springframework.web.multipart.support.MissingServletRequestPartException: Required request part 'file' is not present
From what I've been investigating, it would seem as if the controller would expect a Model Attribute when the file is submitted via HTML Form, so what could be a good practice to resolve this? Include a controller parameter for a Model Attribute, which would be checked for null value (to distinguish between html form or other submission methods), or would a sort of Proxy controller between the HTML Form and the RESTful service be better?
The service looks good. It works fine for me on both CURL and HTML form submit.
Here is my form. Please try using this.
<html>
<head>
<title>Post Tool</title>
</head>
<body>
<h1>Sample Requests</h1>
<h2>Upload Document</h2>
<form action="http://localhost:8080/upload" method="post" enctype="multipart/form-data">
Choose a file : <input type="file" name="file" multiple/>
<input type="submit" value="Upload" />
</form>
</body>
</html>
After more research I got to know that the commons multi part resolver doesn't fare well with the new versions of spring boot. It struggles obtaining POST parameters from forms.
One solution is to use the StandardServletMultipartResolver implementation of the MultipartResolver interface!
My target is to modify MIME contents & add footer note in email, without changing it's behavior.
If email content content type is text/plain then I'm updating email content type to multipart/mixed as below:
Content-Type: multipart/mixed;
Boundary="--=_SAMPLE_MIME_BOUNDARY_"
email original data
----=_SAMPLE_MIME_BOUNDARY_
Content-Type: text/plain;
Content-Transfer-Encoding: 7bit
!-- Sample footer note --!
----=_SAMPLE_MIME_BOUNDARY_--
And if email content type is by default multipart/mixed then I'm updating email contents are as below:
Content-Type: multipart/mixed;
boundary="----=_SAMPLE_MIME_BOUNDARY_"
------=_SAMPLE_MIME_BOUNDARY_
Content-Type: text/plain;
Content-Transfer-Encoding: 8bit
email data
------=_SAMPLE_MIME_BOUNDARY_
Content-Type: application/octet-stream;
name="test_attachment"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="test_attachment"
<<attachment data>>
------=_SAMPLE_MIME_BOUNDARY_
Content-Type: text/plain;
Content-Transfer-Encoding: 7bit;
Content-Disposition: inline;
!-- Sample footer note --!
------=_20160127171644_62291--
But when I update email body then Outlook treat sample note as an attachment. And in case of Thunderbird when I click on forward email it will treat footer as an attachment.
So my questions are, Which is best way to add footer text in email?
Am I doing right? Suggest changes/pinpoints if anything wrong.
The MIME structure of your message is wrong. It needs to be
multipart/mixed
multipart/alternative
text/plain
text/html
application/octet-stream
I am creating buttons that send the customer to a hosted payment system. In this case an authorize.net Simple Checkout button in test mode.
<form name="PrePage" method = "post" action = "https://Simplecheckout.authorize.net/payment/CatalogPayment.aspx"> <input type = "hidden" name = "LinkId" value ="8a40541d-2f0f-4bfe-a1e8-397292f5dee5" /> <input type = "image" src ="//content.authorize.net/images/buy-now-gold.gif" /> </form>
My attempt to get the form inputs into the url are following:
https://Simplecheckout.authorize.net/payment/CatalogPayment.aspx/?LinkId=8a40541d%2D2f0f%2D4bfe%2Da1e8%2D3d397292f5dee5
What am I doing wrong?
The diffence is that you are using the GET method instead of POST.
When you include data in your query string (things after the ? in the URL) you are using the GET method.
If you have a form you can specify to use the POST (as it is in your code example also). In this set up the data is transferred a different way. Not in the query string.
see: HTTP - Post and Get
Maybe your server is not handling, only the POST method, so your GET request won't work.
In my project an User can create a Customer and assigning it zero or more Tag. These entities have a relation with User of course. This is done by a form that has a tag field of entity type, filtered by current logged user:
$user = $this->securityContext->getToken()->getUser();
$builder
->add('tags', 'meta_selector', array(
'label' => 'Tag',
'class' => 'Acme\HelloBundle\Entity\Tag',
'property' => 'select_label',
'query_builder' => function(EntityRepository $er) use($user) {
$qb = $er->createQueryBuilder('t');
return $qb
->where($qb->expr()->eq('t.user_id', ':user')
->orderBy('t.name')
->setParameter('user', $user);
}
))
;
And this is working fine. Looking at a generated HTML tags are rendered as checkboxes:
<div class="controls">
<label class="checkbox">
<input type="checkbox" value="2" name="customer[tags][2]"
id="customer_tags_2"> A Tag
</label>
<label class="checkbox">
<input type="checkbox" value="3" name="customer[tags][3]"
id="customer_tags_3"> Another Tag
</label>
</div>
I'd like to investigate further about form tampering. In particular making a POST request from a trusted user adding customer%5Btags%5D%5B1%5D=1, that is a tag with id equal to 1 which exists but it has been created by another user. Attacker user is creating an customer with a tag created by another user:
POST http://localhost/Symfony2/web/app_dev.php/app/customers/new HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:13.0) Gecko/20100101 Firefox/13.0.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: it-it,it;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Connection: keep-alive
Referer: http://localhost/Symfony2/web/app_dev.php/app/customers/new
Cookie: PHPSESSID=3avu1a2a1eufthr5tdftuhrnn7; hl=it
Content-Type: application/x-www-form-urlencoded
Content-Length: 276
customer%5Bfirst%5D=fake&customer%5Blast%5D=fake&customer%5Bgender%5D=m&customer%5Bbirthday%5D=&customer%5Bemail%5D=&customer%5Bmobile%5D=&customer%5Baddress%5D=&customer%5Bcountry%5D=IT&customer%5Btags%5D%5B1%5D=1&customer%5B_token%5D=455783fa2f866677669c9034a90554b9f75d68b4
.. and seems there is some sort of control that prevents this. Result is 200 OK (should be a 302 in case of success) without any error and form is rendered again. Of course entity is not persisted.
Actual question is: how Symfony 2 protect from this kind of form "tampering" attacks? A possible explanation is the it checks that submitted tags exist inside the collection returned by the form builder. But a reference is needed...
EDIT: even disabling CSRF protection the result is the same. By the way i was passing a valid token and CSRF is intended to protect from other types of attacks.
The answer to your question can be explained quite easily. Every choice field (and the entity type is a specialization of the choice type) has a list of choices. For each choice, the field is aware about
the model representation ("choice") of the choice (e.g. a Tag instance)
the view representation ("value") of the choice (e.g. the ID)
the label used in the view (e.g. a property of Tag)
When you submit the form, the choice field looks in this list which model representation matches the submitted view representation. If none can be found, the field remains unassigned.
The code for this logic can be found in the class ChoiceList and its descendants, in your case EntityChoiceList. Upon submission, the method getChoicesForValues() is executed which does the lookup and is optimized for speed.
Wild guess: CSRF protection is enabled and you do not render the errors.
Update: I've found a workaround. If I submit a dummy form field along with the file, it works. Is this a ColdFusion bug, or is there something in the HTTP spec that says forms must contain at least one non-file form field?
Update 2: I'm convinced this is a ColdFusion cfhttp bug. This is based on Leigh's answer and the fact that I used the code below to submit a form with only the file element using javascript, and it works fine:
<form enctype="multipart/form-data" action="<cfoutput>#CGI.PATH_INFO#</cfoutput>" method="POST" name="theForm">
<input name="theFile" type="file" /><br/>
</form>
submit
I'm running into a problem uploading files from a ColdFusion server to another webserver. It seems that cfhttpparam type="file" is indiscriminately appending a newline (carriage return and line feed) to the end of the file. This is breaking binary files. This does not happen when I manually upload the file via form field. I have tried with and without mimetype parameter, and I've tried lying about mimetype with various binary formats (exe, zip, jpg), but nothing has worked. Is there some parameter I'm missing, or is this a bug in ColdFusion? (I'm running on CF 8.0.1.195765 on WinXP.)
Below is test code I'm using, it just uploads the file to the same directory. The manual upload works, but the server-based upload ends up appending a CRLF to the file.
<cfset MyDir = "C:\test" />
<cfset MyFile = "test.zip" />
<cfif IsDefined("Form.TheFile")>
<cffile action="upload" fileField="theFile" destination="#MyDir#" nameConflict="MakeUnique" />
<cfelse>
<cfhttp url="http://#CGI.SERVER_NAME##CGI.SCRIPT_NAME#" method="POST" throwOnError="Yes">
<cfhttpparam type="file" name="theFile" file="#MyDir#\#MyFile#" />
</cfhttp>
</cfif>
<html><body>
<h2>Manual upload</h2>
<form enctype="multipart/form-data" action="<cfoutput>#CGI.PATH_INFO#</cfoutput>" method="POST">
<input name="theFile" type="file" /><br/>
<input type="submit" value="Submit" />
</form>
</body></html>
or is there something in the HTTP spec
that says forms must contain at least
one non-file form field?
I do not know for certain. But according to these definitions it seems like a POST containing only a file input should be valid. So I suspect the problem may be CFHTTP in ACF.
According to Fiddler the raw content from the cfhttp call in ACF contains an extra new line just before the end boundary (0D 0A in hex view). But under Railo it does not. So I think ACF's cfhttp might be the culprit.
Sample Code:
<cfhttp url="http://127.0.0.1:8888/cfusion/receive.cfm" method="post">
<cfhttpparam name="myFile" type="file" file="c:/test/testFile.zip" mimetype="application/octet-stream" />
</cfhttp>
Results Railo 3.1.2
POST /railo/receive.cfm HTTP/1.1
User-Agent: Railo (CFML Engine)
Host: 127.0.0.1:8888
Content-Length: 382
Content-Type: multipart/form-data; boundary=m_l7PD5xIydR_hQpo8fDxL0Hb7vu_F8DSzwn
--m_l7PD5xIydR_hQpo8fDxL0Hb7vu_F8DSzwn
Content-Disposition: form-data; name="myFile"; filename="testFile.zip"
Content-Type: application/octet-stream; charset=ISO-8859-1
Content-Transfer-Encoding: binary
PK
&�1=�cN'testFile.txtTestingPK
&�1=�cN' testFile.txtPK:1
--m_l7PD5xIydR_hQpo8fDxL0Hb7vu_F8DSzwn--
Results ACF (versions 8 and 9)
POST /cfusion/receive.cfm HTTP/1.1
Host: 127.0.0.1:8888
... other headers removed for brevity ....
Content-type: multipart/form-data; boundary=-----------------------------7d0d117230764
Content-length: 350
-------------------------------7d0d117230764
Content-Disposition: form-data; name="JobFile"; filename="c:\test\testFile.zip"
Content-Type: application/octet-stream
PK
&�1=�cN'testFile.txtTestingPK
&�1=�cN' testFile.txtPK:1
-------------------------------7d0d117230764--
Maybe Railo 3.1.2 and ColdFusion 9 handle this a bit differently, but your code looks a bit incorrect for me.
Your CGI.PATH_INFO is not applicable here.
While browser is smart enough to use path without hostname, CFHTTP feels better with full hostname + script path + script name. Note: cgi.SCRIPT_NAME worked in CF9, Railo required cgi.SERVER_NAME to be prepended, though I feel this more correct in general.
That's why a bit modified version of the code works fine for me. Zip file is uploaded and posted without being corrupted.
Form:
<form enctype="multipart/form-data" action="<cfoutput>#cgi.SCRIPT_NAME#</cfoutput>" method="POST">
<input name="theFile" type="file" /><br/>
<input type="submit" value="Submit" />
</form>
CFHTTP:
<cfhttp url="#cgi.SERVER_NAME##cgi.SCRIPT_NAME#" method="POST" throwOnError="Yes">
<cfhttpparam type="file" name="theFile" file="#MyDir#/#MyFile#" />
</cfhttp>
Hope this helps.
I get the extra line feed and carriage return on file appends too. The problem for me is/was the combination of cfhttp and the cfloop. Once I broke the file creation into 3 parts: Create, cfloop endrow-1, then appending last record.
Seems like a kludgy way to do it, but no extra line feed.