Мне нужно иметь возможность отправлять изображение и некоторые поля формы из элемента canvas на стороне клиента в PHP-скрипт, заканчивая $ _POST и $ _FILES. Когда я отправлю его так:
<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 и $ _FILES возвращаются, но данные изображения в $ _FILES все еще нуждаются в расшифровке следующим образом:
$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);
… чтобы сохранить его как читаемый PNG. Это не вариант, поскольку я пытаюсь передать изображение в функцию Media_handle_upload () WordPress, для которой требуется индекс $ _FILES, указывающий на читаемое изображение. Я также не могу декодировать, сохранять и изменять «tmp_name» соответственно, так как это подрывает проверки безопасности.
Итак, я нашел это: http://www.webtoolkit.info/javascript-base64.html и попытался выполнить декодирование на стороне клиента:
img_split = img.split(",",2)[1]; img_decoded = Base64.decode( img_split );
но по какой-то причине я до сих пор не получаю доступного для чтения файла, когда он попадает на PHP. Поэтому вопрос: «Почему?» или «Что я делаю неправильно?» или «Возможно ли это?» 🙂
Любая помощь очень ценится!
Спасибо, Кейн
Основываясь на превосходном ответе Натана, я смог его обмануть, чтобы он все еще проходил через jQuery.ajax. Просто добавьте это в запрос 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; },
В принципе, вы просто возвращаете обратно объект xhr, который переопределяется, так что «send» означает «sendAsBinary». Тогда jQuery делает все правильно.
К сожалению, это невозможно в JavaScript без некоторой промежуточной кодировки. Чтобы понять, почему, давайте предположим, что вы base64 декодировали и разместили данные, как описано в вашем примере. Первые несколько строк в шестнадцатеричном файле действительного PHP могут выглядеть так:
0000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452 .PNG........IHDR 0000010: 0000 0080 0000 0080 0806 0000 00c3 3e61 ..............>a
Если вы посмотрели на один и тот же диапазон шестнадцатеричного файла загруженного PNG, он будет выглядеть так:
0000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452 .PNG........IHDR 0000010: 0000 00c2 8000 0000 c280 0806 0000 00c3 ................
Различия тонкие. Сравните второй и третий столбцы второй строки. В действительном файле четыре байта: 0x00
0x80
0x00
0x00
. В загруженном файле те же четыре байта: 0x00
0xc2
0x80
0x00
. Зачем?
Строки JavaScript – это UTF. Это означает, что любые двоичные значения ASCII (0-127) кодируются одним байтом. Однако все, начиная с 128-2047, получает два байта. Этот дополнительный 0xc2
в загруженном файле является артефактом этой многобайтовой кодировки. Если вы хотите точно знать, почему это происходит, вы можете узнать больше о кодировке UTF в Википедии .
Вы не можете предотвратить это со строк JavaScript, поэтому вы не можете загружать эти двоичные данные через AJAX без использования base64.
EDIT: после некоторого дальнейшего копания, это возможно с некоторыми современными браузерами. Если браузер поддерживает XMLHttpRequest.prototype.sendAsBinary
(Firefox 3 и 4), вы можете использовать это для отправки изображения, например:
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')); }
Для браузеров, у которых нет sendAsBinary
, но у них есть Uint8Array
(Chrome и WebKit), вы можете заполнить его так:
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); }; }