Tuesday

File Upload by PHP - Ajax





AJAX FILE UPLOAD - Uploading local files with AJAX/Javascript to a server
Many people say uploading files with AJAX is impossible! Well, they're wrong :-)
Granted this solution only works with FireFox/Mozilla. And the user has to change
a setting in "about:config" and accept the advanced access privileges.
Anyway, such an AJAX file upload is only suitable for restricted area stuff, such
as Content Management Systems, Group-Ware etc., so users can be briefed
before actually using it.



FireFox/Mozilla settings:

Open about:config and check that

signed.applets.codebase_principal_support

is set to "true"
Otherwise Firefox will display something like this

Error: uncaught exception: A script from "http://www.captain.at"
was denied UniversalXPConnect privileges.

Also make sure you check the checkbox "Remember this decision", when FireFox will
display this message

A script from "http://www.captain.at" is requesting enhanced abilities that are
UNSAFE and could be used to compromise your machine or data:

Run or install software on your machine

Allow these abilities only if you trust this source to be free of viruses or malicious
programs.
[ ] Remember this decision

and click "Allow", otherwise you have to click "Allow" everytime you upload a file.
The example itself is rather straightforward:
We use some Components.classes and Components.interfaces stuff to open the local
file from within FireFox/Mozilla - we read the file, construct our request body
for the POST request and send the whole data with an AJAX "multipart/form-data"
request.
NOTE about encoding the local files:
Since we also want to upload binary files, we need to encode (javascript "escape")
the file content. This is basically encoding a string for use in an URL. On the
server, after uploading the file we need to decode ("urldecode") the file.
"escape" does not encode the plus sign "+", but on the server
PHP's "urldecode" interprets any "+" and space. So we need an additional preg_replace
to replace any "+" to the HEX value "%2B".

This is a little annoying, since escaping large files (up to 1MB it is still fast)
with javascript can hang the browser for a few seconds. The problem here is
that the AJAX object XMLHttpRequest doesn't seem to be able to handle binary data.
ADVANTAGES:
If you upload images and process them on the server, it is common that the
server stops the script due too much memory consumption and/or the runtime limit
has been exceeded. In such a case PHP is just returning an error message ("Fatal error:
memory limit exceeded" or "Fatal error: running too long" or whatever) and the user
usually has to back up with the browser back button to repeat the procedure with
a smaller image. With AJAX you can check the returned string for errors and if
an error has occured, notify the user gracefully.
A possible extension to this example would be:
Let the user select a directory with a custom "directory-browser" or one file in a directory
with the regular file-dialog as shown here, then parse the directory automatically
for file with a certain extension and upload them in a bulk.


LICENCE: As stated in the policy:

The information provided on the websites of Captain's Universe is free for non-commercial,
educational use. For commercial use of any of the information provided, contact the owner
at the email address listed in the footer below.

index.html

<html>
<body>
<script>
var url = "post.php";
var binary;
var filename;
var mytext;

function upload() {
filename = document.getElementById('myfile').value;
mytext = document.getElementById('mytext').value;
document.getElementById('ajaxbutton').disabled = true;

// request local file read permission
try {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
} catch (e) {
alert("Permission to read file was denied.");
}

// open the local file
var file = Components.classes["@mozilla.org/file/local;1"]
.createInstance(Components.interfaces.nsILocalFile);
file.initWithPath( filename );
stream = Components.classes["@mozilla.org/network/file-input-stream;1"]
.createInstance(Components.interfaces.nsIFileInputStream);
stream.init(file, 0x01, 00004, null);
var bstream = Components.classes["@mozilla.org/network/buffered-input-stream;1"]
.getService();
bstream.QueryInterface(Components.interfaces.nsIBufferedInputStream);
bstream.init(stream, 1000);
bstream.QueryInterface(Components.interfaces.nsIInputStream);
binary = Components.classes["@mozilla.org/binaryinputstream;1"]
.createInstance(Components.interfaces.nsIBinaryInputStream);
binary.setInputStream (stream);

// start AJAX file upload in 1 second
window.setTimeout("ajax_upload()", 1000);
}

function ajax_upload() {
// request more permissions
try {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
} catch (e) {
alert("Permission to read file was denied.");
}

http_request = false;
http_request = new XMLHttpRequest();
if (!http_request) {
alert('Cannot create XMLHTTP instance');
return false;
}

// prepare the MIME POST data
var boundaryString = 'capitano';
var boundary = '--' + boundaryString;
var requestbody = boundary + '\n'
+ 'Content-Disposition: form-data; name="mytext"' + '\n'
+ '\n'
+ mytext + '\n'
+ '\n'
+ boundary + '\n'
+ 'Content-Disposition: form-data; name="myfile"; filename="'
+ filename + '"' + '\n'
+ 'Content-Type: application/octet-stream' + '\n'
+ '\n'
+ escape(binary.readBytes(binary.available()))
+ '\n'
+ boundary;

document.getElementById('sizespan').innerHTML =
"requestbody.length=" + requestbody.length;

// do the AJAX request
http_request.onreadystatechange = requestdone;
http_request.open('POST', url, true);
http_request.setRequestHeader("Content-type", "multipart/form-data; \
boundary=\"" + boundaryString + "\"");
http_request.setRequestHeader("Connection", "close");
http_request.setRequestHeader("Content-length", requestbody.length);
http_request.send(requestbody);

}

function requestdone() {
if (http_request.readyState == 4) {
if (http_request.status == 200) {
result = http_request.responseText;
document.getElementById('myspan').innerHTML = result;
} else {
alert('There was a problem with the request.');
}
document.getElementById('ajaxbutton').disabled = false;
}
}

</script>

<form>
Text: <input type="text" id="mytext" name="mytext" size="40">
<br>
File: <input type="file" id="myfile" name="datafile" size="40"><br>
<input type="button" id="ajaxbutton" value="AJAX IT" onclick="upload();">
</form>

<div id="sizespan"></div>

<hr>
<div id="myspan"></div>

</body>
</html>



post.php

<?
print_r($_FILES);
?>
<hr>
<?
print_r($_POST);

$fpath = "/tmp/";

// move (actually just rename) the temporary file to the real name
move_uploaded_file ( $_FILES{myfile}{tmp_name}, $fpath.$_FILES{myfile}{name} );

// convert the uploaded file back to binary

// javascript "escape" does not encode the plus sign "+", but "urldecode"
// in PHP make a space " ". So replace any "+" in the file with %2B first

$filename = $fpath.$_FILES{myfile}{name};
$handle = fopen($filename, "r");
$contents = fread($handle, filesize($filename));
fclose($handle);

$contents = preg_replace("/\+/", "%2B", $contents);

$handle = fopen($filename, "w");
fwrite($handle, urldecode($contents));
fclose($handle);

?>

courtsy: http://www.captain.at

1 comment:

admin said...

hello,

I'm just starting to learn JS/ajax and I have one question (if you don't mind). How I can simulate server response on desktop for Ajax app? Is there a way? Thanks.