I don't get it, I don't understand.
In a self-developed Python web framework I'm posting a file form element to the server using JavaScript.
var formData = new FormData();
formData.append('file', document.getElementById('file').files[0]);
var request = new XMLHttpRequest();
request.open("POST", url, true);
request.send(formData);
The server, sporting mod_wsgi, receives the request like so:
if environ['REQUEST_METHOD'] == 'POST':
post_env = environ.copy()
post_env['QUERY_STRING'] = ''
form = cgi.FieldStorage(
fp=environ['wsgi.input'],
environ=post_env,
keep_blank_values=True)
Then, when I want to access the form field's content to save it to a file, the following will return an empty result:
form['file'].file.read()
(All code has been edited for simplicity)
What puzzles me is that the form field will show the correct file name and MIME type, only file.read() remains empty.
Also all other information from other input text fields comes through and is accessible.
I also checked that environ['REQUEST_METHOD'] is actually POST.
As to HTTP Headers for encoding (such as multipart/form-data), I thought that the XMLHttpRequest object will take care of that, once formData.append() receives a file input as the value.
Okay, here's the solution:
I can't explain it, but the file form fields get emptied on first read, which I found out by accident. Maybe a bug, who knows.
I assumed that they would stay intact (like any other variable) and can be read multiple times.
What I ignored was that I had in fact addressed them several times.
So the solution was to first store the file's content in a variable, and then use that multiple times.
Related
I am working on a form set for a client. In a nutshell:
The forms are filled out by my client’s customers by selecting different options on each form.
Each form can have multiple instances, depending on the customer.
At the end of the process, the customer can opt to either sign one or all the forms digitally or decline to sign them digitally and at the end of the process the forms are printed out and signed manually.
To accomplish this, I’ve created a signature plugin written in jQuery. Once the customer fills out the forms, they are presented each form separately. To sign the form digitally they simply tap (click) the signature block, a dialog with a canvas element appears, they sign the form and save it, the signature appears in the form, and they move on to the next form.
Here is the portion of the code that stores the completed signature and adds the image to the form:
$.sig = {
signatures: {},
}
function signatureSave() {
var canvas = document.getElementById("sigcanvas"),
dataURL = canvas.toDataURL("image/png");
document.getElementById($.sig.target).src = dataURL;
$.sig.signatures[$.sig.target].url = dataURL;
$.sig.signatures[$.sig.target].hasSignature = true;
};
The function is only called if the signature is saved, if there is no signature, the $.sig.signatures[$.sig.target].hasSignature remains false and the system skips the object.
This all works as intended, almost.
My problem lies in the process used to save the form information. If the customer does not sign any forms digitally the form information is simply saved and the forms are printed out, no need to save any signatures.
If the customer signs at least one form, though, the signatures must be sent to the server using the FormData() object.
I’ve used the FormData object in other projects for the client successfully, but only when the customer uploads one or more images to the browser using the input file element. It’s a pretty simple process because the resulting images have a img.file property that I send to the server.
Not so with a canvas object. All I get is the .src property, an any attempt to use anything from the resulting .png image that is created in the function above results in either a “cannot use a blob” or some other error.
Now I know if I have a single image, I can send it using AJAX with the following:
$.ajax({
type: "POST",
url: "script.php",
data: {
imgBase64: dataURL
}
})
Problem is that I am sending from one to x number of signatures.
Edit: I forgot to add this in. This is the function that is supposed to create the FormData object used to send the signatures to the server (and where my problem lies):
function getUploadData() {
var upl = new FormData();
$.each($.sig.signatures, function (e, u) {
if (u.hasSignature == true && u.url != null) {
var im = new Image();
im.src = u.url;
upl.append(u.target, im, u.target + '.png');
}
})
return upl;
}
I've tried all the tricks and nothing is working. The var im = new Image(); as well as the following line are just my latest ill fated attempt.
Picture perfect would be the ability to save the image information in the $.sig.signatures object so I can simply loop through any signatures that are signed, add them as elements of the FormData object, and then send the FormData object as the data for the AJAX call. As stated before, I use this method in other projects and works fine.
Does anyone know a way to do this?
Please note:
The server-side AJAX processor functions correctly.
The signature process works correctly (customer signs canvas, signature is displayed, signature information is stored).
All I need is how to send multiple images created using the canvas element in a FormData object to the server.
I know the answer is staring me right in the face, but I am just not getting it. Any hints or suggestions would be greatly appreciated!
Edit: Just a note. I've searched the entire afternoon for this and have found entries that either deal with sending multiple files using FormData and AJAX - but the files are uploaded to the browser (not created using Canvas), or single files sent to the server that are created using Canvas, but nothing about sending multiple files sent using FormData and AJAX that are created using Canvas. Oje!
As stated, the answer was staring me in the face, but I didn't see it because was looking behind the wrong door. FormData has nothing to do with it (Homer Dope Slap!).
Since I already have the data stored in $.sig.signature for each signature, I just need to send the information to the server as the data in the AJAX function. I updated my function above as shown:
function getUploadData() {
var upl = {};
$.each($.sig.signatures, function (e, u) {
if (u.hasSignature == true && u.url != null) {
upl[e] = u.url;
}
})
return upl;
}
Since the form information is sent as JSON I just add the signature info to the object that contains the form information, JSON.stringify it and send it on its way. This should work because the information retrieved above are strings.
Server side will look something like this:
$info = json_decode( $_POST['info'] );
// Various validation routines and checks
foreach( $info->signatures as $sig=>$data ):
$data = str_replace('data:image/png;base64,', '', $data);
$data = str_replace(' ', '+', $data);
$img = base64_decode($data);
// Do some processing, file naming, database saving and other general dodads
$success = file_put_contents( $file, $img );
endforeach;
The above function is still concept, I am reworking some of the code but this should work.
Credit is given to this post for opening my eyes:
post sending base64 image with ajaxpost sending base64 image with ajax
So question answered and yeah, I deserve a dope slap, but all comes out right in the end.
CAVEAT: Works like a charm.
I am using JavaScript to take the info from a form completed by the user, then sending this data to PHP and generate a PDF with FPDF. The problem is I want the browser to ask the user to save the PDF or view it online but
I cannot figure out how. The PDF generates correctly but only when I save it directly to a certain path specified by me.
The question is how do you guys send data from JavaScript to PHP to generate a PDF file then the browser asks the user to open or download, Or how can I make a function where the user can retrieve this PDF.
The JavaScript:
function senddata() {//this activates when i push a button not a submit
var peticion = new XMLHttpRequest();
peticion.open('POST', 'generatepdf.php');
var nueva2 = {};
var key;
for (i = 0; i < 6; i++) {
key = document.forms[0].elements[i].id;
nueva2[key] = document.forms[0].elements[i].value;
}//here i take my data from the form and make an object
var json = JSON.stringify(nueva2);//here i tranform my object to json string so i can send it to my php
var parametros = "json_string=" + json;//here I do this so later I can easily transform the $_POST to an array in the form json_decode($_POST["json_string"],true);
peticion.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
peticion.send(parametros);//it sends ok
}
The PHP with the FPDF class and things
<?php
require('fpdf/fpdf.php');
require('functions.php');
if($_SERVER['REQUEST_METHOD']=='POST'){
$datos=json_decode($_POST["json_string"],true); //here i have my data in an array format so i can use the parameters to fill my pdf fields
//...lots of pdf things here...//
$pdf->Output('F',"pdfs/Cotizacion ".$datos["nombres"]." ".$datos["apellidos"].".pdf");//this works but never ask the user
//$pdf->Output('D',"pdfs/Cotizacion ".$datos["nombres"]." ".$datos["apellidos"].".pdf");//this should force a dowload or send to the browser but doesnt work
//$pdf->Output();//this should send to the browser but doesnt work
}
To view your PDF inline to the browser, you should use the I variable instead. View full documentation here.
Also I don't think outputting the file in two methods at the same time works. It might conflict each other. The best way to do that is to test each method and if it does conflict each other just simply add a condition for the user to decide whether he/she wants to download or view it in the browser. Hope this helps.
I'm using Prototype to submit a POST request, and the postdata contains a number of fields, one of which is binary data from a file (in this case an Excel spreadsheet the user has selected for upload.)
I am using the HTML5 FileReader interface to get the contents of the file via FileReader.readAsBinaryString() which works well. If I use charCodeAt() to print various characters in the string then they come out with the expected values.
However once I put this string data in an object (along with the other form fields) and pass it as the parameters option to Prototype's Ajax.Request(), the data arrives corrupted. Certain character values like 0x82 are replaced with 0xC2 0x82, 0xAC is replaced with 0xC2 0xAC, and so on.
I tried using window.atob() to base64 encode the string, but this fails with InvalidCharacterError: String contains an invalid character, so clearly there is some kind of processing going on which I need to avoid.
Does anyone know how to pass binary data through Prototype's Ajax.Request() while also including additional form fields in the same request?
To do ajax file upload you should really use a FormData object
var data = new FormData();
//var data = new FormData(someForm); if all your fields is in an html form
// add fields to form
data.append('fieldname',fieldvalue);
data.append('filefieldname',fileobject);
//etc
new Ajax.Request(url, {
contentType: null,
method: 'post',
postBody: data,
onSuccess: successFunction
})
With this method the server will see the request like if it was sent by a form with attribute enctype="multipart/form-data"
Doing this requires magic and pixie dust. (well not really)
firstly you would need to put the form fields values as URL parameters instead of POST and then override the post body with the file contents. For example
new Ajax.Request('/url.php?rawupload=1&id='+$F('id')+'&label='+label,
{
'method':'post',
'postBody':fileobject,
'onSuccess':function() { alert('success'); }
});
fileobject is the HTML5 file object retrieved from the file upload input element
Yes its not the most elegant solution if you have lots of form fields. You can also use Hash#toQueryString if you do have lots of form fields to build your query string instead of doing them by hand
http://api.prototypejs.org/language/Hash/prototype/toQueryString/
is there a way to send an email via Mailgun with html page as its content that is longer than ~2000 characters?
I have this code, that works perfectly for short html as I believe it is sent in URL address:
var obj = $.request.body.asString();
var req = new $.web.WebRequest($.net.http.POST, "/messages");
req.headers.set('Content-Type', encodeURIComponent("application/x-www-form-urlencoded"));
req.parameters.set("domain", "mailgundomain.com");
req.parameters.set("from", "me#mailgundomain.com");
req.parameters.set("to", 'to#email.com');
req.parameters.set("subject", "subject");
req.parameters.set("html", obj); //email content
In the code above I receive the file and save it to 'org' variable and then send it to mail. What I need is to probably get my "too large" .html file to the body and then show it as a content of the email. As you probably can see, I'm quite new in .xsjs so the more detailed answer the better. If you need any more info, feel free to ask. Thank you.
Edit1: I should add that when I try to send a larger file, the response I get is "414 Request-URI Too Large".
EDIT
This seems to be the right approach, jointly figured out by the OP and myself:
var obj = $.request.body.asString();
var req = new $.web.WebRequest($.net.http.POST, "/messages");
// request headers
req.headers.set('Content-Type', "application/x-www-form-urlencoded");
// request URL parameters
req.parameters.set("domain", "mailgundomain.com");
req.parameters.set("from", "me#mailgundomain.com");
req.parameters.set("to", 'to#email.com');
req.parameters.set("subject", "subject");
// request body
req.setBody(encodeURIComponent(message));
The $.web.WebRequest class sends everything you set in the .parameters collection as an URL parameter, even if the request method is POST. This is perfectly all-right, POST requests may have URL parameters. However, URLs are length-limited, as you have noticed.
The body of a POST request is not length-limited, but you have to do the proper content encoding on your own. The body of a application/x-www-form-urlencoded type request follows the same rules as the URL - key=value pairs separated by & characters.
var obj = $.request.body.asString();
var req = new $.web.WebRequest($.net.http.POST, "/messages");
req.headers.set('Content-Type', "application/x-www-form-urlencoded");
var message = {
domain: "mailgundomain.com",
from: "me#mailgundomain.com",
to: "to#email.com",
subject: "subject",
html: obj
};
req.setBody(urlEncode(message));
where urlEncodedFormat() is a little helper function:
function urlEncode(obj) {
return Object.keys(obj).map(function (key) {
return encodeURIComponent(key) + "=" + encodeURIComponent(obj[key]);
}).join("&");
}
Turning objects into an URL-encoded string is a pretty common operation. It's likely that one of the libraries you use already contains a function that does that.
While the above function is is probably correct (there might be edge cases with undefined or null values), it's preferable not to use a hand-rolled variant. Spend some time looking for the right function in your libraries.
Maybe WebRequest already does the right thing on its own, I have no way to test it, though. Try setting the message object as the body directly:
req.setBody(message);
I'm trying to submit a form with Java, which looks like this:
<form id="bForm" action="/a/b.php" method="POST"></form>
There are no <input type="hidden">, there are not even any <input> at all.
In the source, I see that this form is submitted when I click a link which
executes javascript code like this:
javascript:b(<k>,<v>)
where <k> and <v> is some key and value consisting of some numbers.
Now, the function b() in the source is this:
function b(value, key) {
var form = document.getElementById('bForm');
form.action = '/b/user/' + key + '/' + value + '.txt';
form.submit();
}
What I'm trying to receive is the .txt file send as response, but trying to directly open
http://<url>.com/b/user/<key>/<value>.txt
in a browser does not get me the .txt file, but a message saying it has to be accessed by using the POST method.
Now, I read about how to send requests via POST (also from trying to find a
solution here) and think I understood most. Still, I only read about mostly everything today, including what forms are, so excuse me if the solution is obvious.
The problem I have here is that there are no parameters I can pass to "let the URL know" which action to do upon submitting the form.
Now as I can probably not change the action of the form, what do I do?
Can I somehow "connect" to the URL which leads to the .txt file via "POST"?
Or do I need to execute the javascript somehow, and if so how?
Edit: I have solved the problem now, not sure if my explanation was not good enough, but the solution seemed pretty simple, I just didnt know enough about forms.
So, action=#myurl# means that the form is submitted to the #myurl#, not to the current page. And, although the target URL seems to point to some file which cannot do anything with the submitted form, this is not right. In fact, it points to a script, but the script is "hidden" in a way that we only see the path to the file we want to download in the URL.
So, I just did this:
URL url = new URL("http://myurl.com/b/user/" + key + "/" + value + ".txt");
HttpURLConnection c = (HttpURLConnection) url.openConnection();
//set User-Agent, Cookie, etc. properties here
c.setRequestMethod("POST");
c.setInstanceFollowRedirects(false); //not sure if this is needed
InputStream in = c.getInputStream(); //this submits the parameter-less form
//now I can get the response with this stream, i.e. read & save the .txt file
All tested and running complete code now, so it works for sure.