Javascript Icon

Cross Domain Ajax Upload

So I’ve been trying to create a cross domain ajax upload form and run into a few issues. Hopefully someone can help me out and get this working.

I’ve been using Ajax Upload which is a great ajax file uploader. It works great on a single domain and is very easy to set up.

Here is the basics:

Client File:




    
	%MINIFYHTML709f64914e23331b1b86f3a03e8de8ec32%
		
    

Back to project page

To upload a file, click on the button below. Drag-and-drop is supported in FF, Chrome.

Progress-bar is supported in FF3.6+, Chrome6+, Safari4+

%MINIFYHTML709f64914e23331b1b86f3a03e8de8ec12%%MINIFYHTML709f64914e23331b1b86f3a03e8de8ec13%

Server File:

require_once('upload.class.php');

// list of valid extensions, ex. array("jpeg", "xml", "bmp")
$allowedExtensions = array();
// max file size in bytes
$sizeLimit = 10 * 1024 * 1024;

$uploader = new qqFileUploader($allowedExtensions, $sizeLimit);
$result = $uploader->handleUpload('uploads/');
// to pass data through iframe you will need to encode all html tags
echo htmlspecialchars(json_encode($result), ENT_NOQUOTES);

This all works great until you change the action parameter in the createUploader function to a URL on a different domain. Then it breaks down. Upon closer inspection with the Live HTTP Headers Firefox addon we can see that the standard post request on the same server:

POST processUpload.php?qqfile=5weeks-copy.jpg HTTP/1.1
Host: domain1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 115
Connection: keep-alive
X-Requested-With: XMLHttpRequest
X-File-Name: 5weeks-copy.jpg
Content-Type: application/octet-stream
Referer: http://domain1/test-upload.php
Content-Length: 12154
Origin: http://domain1
Pragma: no-cache
Cache-Control: no-cache
ÿØÿà

is now changed when using a different domain to:

OPTIONS http://domain2.com/processUpload.php?qqfile=5weeks-copy.jpg HTTP/1.1
Host: domain2
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 115
Connection: keep-alive
Origin: http://domain1
Access-Control-Request-Method: POST
Access-Control-Request-Headers: x-file-name,x-requested-with

After searching for Access-Control-Request-Method and Access-Control-Request-Headers headers I came across this WC3 Cross-Origin Resource Sharing article which details the process for enabling cross domain sharing. From reading this document it looks as though the browser requests access from the external domain before attempting to upload the file. Therefore, we need a way to tell the browser it’s ok to upload to the external domain. Well, after a lot of testing and trial and error, I came up with this modified version of my server upload processing script which allows for cross domain uploading:

//check for cross server access check
if($_SERVER['REQUEST_METHOD'] == 'OPTIONS' && $_SERVER['HTTP_ORIGIN'] == 'http://www.domain1.com'){
header('Access-Control-Allow-Origin: http://www.domain1.com'); 
header('Access-Control-Allow-Methods: POST'); 
header('Access-Control-Allow-Headers: X-Requested-With,X-File-Name'); 
header('Access-Control-Allow-Credentials: true'); 
header('Access-Control-Max-Age: 1728000'); 
header("Content-Length: 0"); 
header("Content-Type: text/plain"); 
exit();

} else {

require_once('upload.class.php');

// list of valid extensions, ex. array("jpeg", "xml", "bmp")
$allowedExtensions = array();
// max file size in bytes
$sizeLimit = 10 * 1024 * 1024;

$uploader = new qqFileUploader($allowedExtensions, $sizeLimit);
$result = $uploader->handleUpload('uploads/');
// to pass data through iframe you will need to encode all html tags
echo htmlspecialchars(json_encode($result), ENT_NOQUOTES);

}

This checks for the OPTIONS method from the first domain and sends the necessary headers to allow the upload. The file uploads fine but the only problem now is that for some reason the ajax call always returns an error. This is the part where I’m losing my mind. The response for the external domain is fine:

HTTP/1.1 200 OK
Date: Thu, 09 Dec 2010 00:27:42 GMT
Server: Apache/2.0.52 (Red Hat)
X-Powered-By: PHP/5.2.14
Vary: Accept-Encoding,User-Agent
Content-Encoding: gzip
Cache-Control: max-age=259200
Expires: Sun, 12 Dec 2010 00:27:42 GMT
Content-Length: 36
Connection: close
Content-Type: text/html

And I’ve verified that it returns data but for some reason there is no status value of the xhr object. Upon looking at fileuploader.js the _onComplete method gets called which means the readyState is 4 signifying the ajax call is complete:

//line 1189 of fileupoader.js
xhr.onreadystatechange = function(){            
            if (xhr.readyState == 4){
                self._onComplete(id, xhr);                    
            }
        };

But there is no status so the call doesn’t finish properly:

//line 1215 of fileupoader.js
if (xhr.status == 200){
            this.log("xhr - server response received");
            this.log("responseText = " + xhr.responseText);
                        
            var response;
                    
            try {
                response = eval("(" + xhr.responseText + ")");
            } catch(err){
                response = {};
            }
            
            this._options.onComplete(id, name, response);
                        
        } else {                   
            this._options.onComplete(id, name, {});
        }

I’ve debugged this like crazy and can’t figure out why this isn’t returning properly. Does anyone have any ideas as to why this is happening or what I can try?

Continue Reading