2011-03-13 2 views
6

Devo essere in grado di inviare un'immagine e alcuni campi modulo da un elemento canvas lato client a uno script PHP, finendo in $ _POST e $ _FILES. Quando invio in questo modo:

<script type="text/javascript"> 
var img = canvas.toDataURL("image/png"); 
... 
ajax.setRequestHeader('Content-Type', "multipart/form-data; boundary=" + boundary_str); 
var request_body = boundary + '\n' 
+ 'Content-Disposition: form-data; name="formfield"' + '\n' 
+ '\n' 
+ formfield + '\n' 
+ '\n' 
+ boundary + '\n' 
+ 'Content-Disposition: form-data; name="async-upload"; filename="' 
+ "ajax_test64_2.png" + '"' + '\n' 
+ 'Content-Type: image/png' + '\n' 
+ '\n' 
+ img 
+ '\n' 
+ boundary; 
ajax.send(request_body); 
</script> 

$ _POST e $ _FILES entrambi tornano popolato, ma i dati dell'immagine in $ _FILES ha ancora bisogno di decodifica in questo modo:

$loc = $_FILES['async-upload']['tmp_name']; 
$file = fopen($loc, 'rb'); 
$contents = fread($file, filesize($loc)); 
fclose($file); 
$filteredData=substr($contents, strpos($contents, ",")+1); 
$unencodedData=base64_decode($filteredData); 

... al fine di salvarlo come PNG leggibile. Questa non è un'opzione in quanto sto cercando di passare l'immagine alla funzione media_handle_upload() di Wordpress, che richiede un indice per $ _FILES che punta a un'immagine leggibile. Inoltre, non posso decodificare, salvare e modificare "nome_tmp" di conseguenza, in quanto ricade sui controlli di sicurezza.

Così, ho trovato questo: http://www.webtoolkit.info/javascript-base64.html e ho cercato di fare la decodifica sul lato client:

img_split = img.split(",",2)[1]; 
img_decoded = Base64.decode(img_split); 

ma per qualche motivo non ho ancora finire con un file leggibile quando si arriva a il PHP. Quindi la domanda è: "Perché?" o "Cosa sto facendo di sbagliato?" o "E 'anche possibile?" :-)

Qualsiasi aiuto molto apprezzato!

Grazie, Kane

+0

Avrei impostato un 'Content-Transfer-Encoding: base64' e ho guardato [questa risposta] (http://stackoverflow.com/questions/934012/get-image-data-in-javascript) per perdere il prefisso, non l'ho provato però. – Wrikken

+0

@Wrikken Content-Transfer-Encoding, sebbene un'intestazione MIME valida, non è un'intestazione HTTP valida. Vedi [questa appendice] (http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.4.5) della specifica. –

+0

@Nathan: Ack, mio ​​errore. Mostra che non ho mai avuto bisogno di costruire manualmente un caricamento di file :) – Wrikken

risposta

4

sulla risposta eccellente di Nathan, ho potuto finnagle in modo che esso è ancora in corso attraverso jQuery.ajax. Basta aggiungere questo per la richiesta ajax:

  xhr: function() { 
       var myXHR = new XMLHttpRequest(); 
       if (myXHR.sendAsBinary == undefined) { 
        myXHR.legacySend = myXHR.send; 
        myXHR.sendAsBinary = function (string) { 
         var bytes = Array.prototype.map.call(string, function (c) { 
          return c.charCodeAt(0) & 0xff; 
         }); 
         this.legacySend(new Uint8Array(bytes).buffer); 
        }; 
       } 
       myXHR.send = myXHR.sendAsBinary; 
       return myXHR; 
      }, 

In pratica, basta tornare indietro di un oggetto XHR che viene sovrascritto in modo che "inviare" significa "sendAsBinary". Quindi jQuery fa la cosa giusta.

+0

Grazie per questo - appena fatto un tentativo e funziona benissimo. Abbiamo finito per abbandonare Wordpress per il progetto originale, ma ho avuto qualcosa di altro in arrivo che dovrebbe essere utile, quindi è molto apprezzato signore! – BaronVonKaneHoffen

+0

Controlla http://stackoverflow.com/questions/9915199/download-a-html5-canvas-element-as-an-image-with-the-file-extension-with-javascr/11200935#11200935. Ho pubblicato una risposta che potrebbe essere davvero utile –

14

Sfortunatamente, questo non è possibile in JavaScript senza alcuna codifica intermedia. Per capire perché, supponiamo che base64 sia decodificato e abbia postato i dati, come descritto nel tuo esempio. Le prime righe in esadecimale di un file PHP valida potrebbe essere simile a questo:

0000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452 .PNG........IHDR 
0000010: 0000 0080 0000 0080 0806 0000 00c3 3e61 ..............>a 

Se avete guardato la stessa gamma di esadecimale del file PNG caricato, sarebbe simile a questa:

0000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452 .PNG........IHDR 
0000010: 0000 00c2 8000 0000 c280 0806 0000 00c3 ................ 

Le differenze sono sottili. Confronta la seconda e la terza colonna della seconda riga. Nel file valido, i quattro byte sono 0x000x800x000x00. Nel file caricato, gli stessi quattro byte sono 0x000xc20x800x00. Perché?

Le stringhe JavaScript sono UTF. Ciò significa che tutti i valori binari ASCII (0-127) sono codificati con un byte. Tuttavia, qualsiasi valore compreso tra 128 e 2047 ottiene due byte. Quel 0xc2 in più nel file caricato è un artefatto di questa codifica multibyte. Se vuoi sapere esattamente perché questo accade, puoi leggere di più su UTF encoding on Wikipedia.

Non è possibile impedire che ciò accada con le stringhe JavaScript, quindi non è possibile caricare questi dati binari tramite AJAX senza utilizzare base64.

MODIFICA: Dopo qualche ulteriore ricerca, questo è possibile con alcuni browser moderni.Se un browser supporta XMLHttpRequest.prototype.sendAsBinary (Firefox 3 e 4), è possibile utilizzare questo per inviare l'immagine, in questo modo:

function postCanvasToURL(url, name, fn, canvas, type) { 
    var data = canvas.toDataURL(type); 
    data = data.replace('data:' + type + ';base64,', ''); 

    var xhr = new XMLHttpRequest(); 
    xhr.open('POST', url, true); 
    var boundary = 'ohaiimaboundary'; 
    xhr.setRequestHeader(
    'Content-Type', 'multipart/form-data; boundary=' + boundary); 
    xhr.sendAsBinary([ 
    '--' + boundary, 
    'Content-Disposition: form-data; name="' + name + '"; filename="' + fn + '"', 
    'Content-Type: ' + type, 
    '', 
    atob(data), 
    '--' + boundary + '--' 
    ].join('\r\n')); 
} 

Per i browser che non hanno sendAsBinary, ma c'è bisogno Uint8Array (Chrome e WebKit), si può polyfill in questo modo:

if (XMLHttpRequest.prototype.sendAsBinary === undefined) { 
    XMLHttpRequest.prototype.sendAsBinary = function(string) { 
    var bytes = Array.prototype.map.call(string, function(c) { 
     return c.charCodeAt(0) & 0xff; 
    }); 
    this.send(new Uint8Array(bytes).buffer); 
    }; 
} 
+0

Ah, giusto! Grazie per la spiegazione! Quindi la domanda è come faccio a compilare la richiesta di post in modo che PHP sappia che sto cercando di inviarlo in base64 e che dovrebbe decodificarlo? Ho provato a impostare Content-Transfer-Encoding: base64' (grazie @Wrikken!) Ma come hai detto tu non è un'intestazione valida, quindi il browser si rifiuta di farlo ("Rifiutato di impostare un'intestazione non sicura"). C'è qualcosa che posso mettere dopo "Content-Disposition" che lo farebbe? Ho fatto esperimenti per un po 'ma non ho trovato nulla che funzioni. – BaronVonKaneHoffen

+0

... inoltre, un'altra cosa che trovo confusa qui è quando guardo una richiesta di lavoro generata dal browser stesso (da un modulo con un input di immagine), non vedo alcun dato di immagine. Solo "Content-Disposition: form-data; name = "formfield"; filename = "picture.png" Content-type: image/png' quindi niente. Dove sono questi dati? Significa che dovrei trasferire i dati effettivi dell'immagine in una richiesta separata o qualcosa del genere? Scusate, sono molto nuovo a questo genere di cose e devo ancora trovare un tutorial decente ... – BaronVonKaneHoffen

+0

@BaronVonKaneHoffen Per un po 'di tempo ho provato a trovare un modo per far sì che PHP decodificasse automaticamente il file in base64. Non sono riuscito a trovarne uno. :(Sei in grado di caricare qualsiasi PHP personalizzato sul server? Se puoi, puoi inoltrare la richiesta tramite uno script personalizzato per decodificarlo. –