Last Updated: April 27, 2017
·
2.674K
· lukemadhanga

HTML5 Cross Origin drag and drop

I wrote a plugin that is hosted on GitHub that allows one to perform cool upload stuff. One of the features it has is cross origin drag and drop, i.e. dragging an image from one website to the uploader. Whilst it doesn't work on some websites, e.g. Google Images results page or GMail attachments (obviously), it does for the vast majority of sites. Today I will share with you the trickery I performed to get it working.

I assume that you understand the basic concepts of OOP and the HTML5 drag and drop functions

The JavaScript


var api = !!(win.Blob || win.File || win.FileList || win.FileReader),
    canvtest = document.createElement('canvas'),
    canv = !!(canvtest.getContext && canvtest.getContext('2d'));

document.body.ondragover = dragOver;
document.body.ondrop = dragDrop;

/**
 * Determine whether a value is in an array
 * @note Type sensitive
 * @param {Array} lookin The array to test
 * @param {Mixed} lookfor The thing to lookfor
 * @returns {Boolean} True if 'lookfor' is in 'lookin'
 */
function in_array(lookin, lookfor) {
    for (var i = 0; i < lookin.length; i++) {
        if (lookin[i] === lookfor) {
            return true;
        }
    }
    return false;
}

/**
 * Drag over event handler
 * @param {object(MouseEvent)} e
 */
function dragOver (e) {
    var dt = e.dataTransfer;
    if (!in_array(dt.types, 'Files') && in_array(dt.types, 'text/uri-list')) {
        // The thing we are hovering with IS NOT a file, return.
        return;
    };
    e.stopPropagation();
    e.preventDefault();
    dt.dropEffect = 'copy';
}

/**
 * Drop event handler
 * @param {object(MouseEvent)} e
 */
function dragDrop (e) {
    // Prevent an accidental drop outside the drop zone
    e.stopPropagation();
    e.preventDefault();
    if ($(e.target).closest('#mydropzonewrapper').length) {
        // Only accept drops inside the drop zone
        var dt = e.dataTransfer,
        files = dt.files;
        if (!files.length && canv) {
            // We may have a uri-list
            var tdp = filesrc = dt.getData('url');
            if (!filesrc) {
                filesrc = dt.getData('text/plain');
                if (!filesrc) {
                    filesrc = dt.getData('text/uri-list');
                    if (!filesrc) {
                        // We have tried all that we can to get this url but we can't. Abort mission
                        return;
                    }
                }
            }

            $.ajax({
                url: '/processing.php',
                type: 'post',
                dataType: 'json',
                data: {filesrc: filesrc, uploaddir: 'destinationfolder'}
            }).done(function (e) {
                if (e.result === 'OK') {
                    // The file has been successfully 'uploaded' 
                   console.log(e.data);
                    if (e.data.mimetype.match('image/*')) {
                        var img = new Image();
                        img.onload = function () {
                            // Special image processing
                        };
                        img.onerror = function () {
                            // Special image processing
                        };
                        // Add '?cachekill=unixtime' to prevent the browser from caching
                        img.src = e.data.src + '?cachekill=' + (new Date().getTime());
                    } else {
                        // Normal processing
                    }
                } else {
                    // There was an error
                }
            }).fail(function () {
                // There was an error
            });
            return;
        }
    }
    // Normal file processing
}

The PHP (processing.php)


cExternalUpload::processExternalUpload();

class cExternalUpload {

    static $settings;

    /**
     * Retrieve a file from another server, save it and pretend that it was an upload from our system
     */
    static function processExternalUpload () {
        $path = filter_input(INPUT_POST, 'filesrc');
        self::$settings = new stdClass();
        self::$settings->uploaddir = filter_input(INPUT_POST, 'uploaddir');
        self::setUploadDir();
        try {
            $pathbits = explode('/', $path);
            $filename = substr(preg_replace('/[^a-z0-9\_\.]/', '_', strtolower(end($pathbits))), 0, 100);
            $destination = self::$settings->uploaddir . time() . '_' . $filename;
            $relpath = substr($destination, strlen(filter_input(INPUT_SERVER, 'DOCUMENT_ROOT')));
            self::save($destination, $path);
            $finfo = finfo_open(FILEINFO_MIME_TYPE);
            $mimetype = finfo_file($finfo, $destination);
            $output = array(
                'src' => $relpath,
                'name' => $filename,
                'mimetype' => $mimetype
            );
            if (preg_match("/image\/.*/", $mimetype)) {
                // The 'uploaded' file is an image
                $size = getimagesize($destination);
                $output['width'] = $output['croppedWidth'] = $output['resizedWidth'] = $size[0];
                $output['height'] = $output['croppedHeight'] = $output['resizedHeight'] = $size[1];
            }
            self::ajaxExit('OK', $output);
        } catch (Exception $ex) {
            self::ajaxExit('Fail', "Unable to get external file: {$ex->getMessage()}");
        }
    }

    /**
     * Send a JSON-encoded response from an Ajax call and exit
     * @param $result string Message to return to the browser
     * @param $data mixed Any additional data to return
     * @param $status int Value of HTTP status to be sent to the browser
     */
    private static function ajaxExit($result, $data = null, $status = 200) {
        header('Content-Type:text/json; charset=utf-8');
        header("HTTP/1.0 $status");
        $response = array('result' => $result);
        if (!empty($data)) {
            $response['data'] = $data;
        }
        print json_encode($response);
        exit;
    }

    /**
     * Save an external file
     * @param string $destination The destination of the upload
     * @param string $src The path to the external file
     * @return int The number of bytes written, or false on failure 
     * @throws Exception 
     */
    static function save($destination, $src) {
        $ans = @file_put_contents($destination, file_get_contents($src));
        if ($ans === false) {
            throw new Exception("Could not write data to {$destination}");
        }
        if (!(fileperms($destination) & 0020)) {
            if (!chmod($destination, 0777)) {
                throw new Exception('Failed to change permissions on ' . $destination);
            }
        }
        return $ans;
    }


    /**
     * Set the destination path for the uploaded file
     * @return string The destination path
     */
    private static function setUploadDir() {
        $uploaddir = self::$settings->uploaddir;
        if(!preg_match("/\/$/", $uploaddir)) {
            // The upload dir requires a '/' at the end
            $uploaddir = self::$settings->uploaddir = "{$uploaddir}/";
        }
        if(substr($uploaddir, 0, 1) === '/') {
            $uploaddir = self::$settings->uploaddir = substr($uploaddir, 0);
        }
        self::$settings->uploaddir = filter_input(INPUT_SERVER, 'DOCUMENT_ROOT') . "/$uploaddir";
    }
}

Enjoy, and comment if you don't understand any of the code

2 Responses
Add your response

ns5219139, your comments look like spam

over 1 year ago ·

que tal lukemadhanga, me intesaria funcionar tu código, no se si me podrias pasar el ejemplo completo con el html por que asi ya copie el jave y php por aparte pero no me funciona, como o de donde meto la imagen?

over 1 year ago ·