使用 Ajax 和 PHP $_FILES 从 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。这不是一个选项,因为我试图将图像传递给 Wordpress 的 media_handle_upload() 函数,该函数需要 $_FILES 的索引指向可读图像。我也无法相应地解码、保存和更改“tmp_name”,因为它违反了安全检查。
所以,我发现了这个: http://www.webtoolkit.info/javascript-base64.html 并尝试在客户端进行解码:
img_split = img.split(",",2)[1];
img_decoded = Base64.decode( img_split );
但由于某种原因,当它到达 PHP 时我仍然没有得到可读的文件。 所以问题是:“为什么?”或“我做错了什么?”或“这可能吗?” :-)
非常感谢任何帮助!
谢谢, 凯恩
I need to be able to send an image and some form fields from a client side canvas element to a PHP script, ending up in $_POST and $_FILES. When I send it like this:
<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 and $_FILES both come back populated, but the image data in $_FILES still needs decoding like this:
$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);
...in order to save it as a readable PNG. This isn't an option as I'm trying to pass the image to Wordpress's media_handle_upload() function, which requires an index to $_FILES pointing to a readable image. I also can't decode, save and alter 'tmp_name' accordingly, as it falls foul of security checks.
So, I found this:
http://www.webtoolkit.info/javascript-base64.html
and tried to do the decode on the client side:
img_split = img.split(",",2)[1];
img_decoded = Base64.decode( img_split );
but for some reason I still don't end up with a readable file when it gets to the PHP.
So the question is: "Why?" or "What am I doing wrong?" or "Is this even possible?" :-)
Any help very much appreciated!
Thanks,
Kane
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
不幸的是,如果没有一些中间编码,这在 JavaScript 中是不可能的。为了理解原因,我们假设您对数据进行了 Base64 解码并发布,就像您在示例中所描述的那样。有效 PHP 文件的前几行十六进制可能如下所示:
如果您查看上传的 PNG 文件的相同十六进制范围,它会如下所示:
差异很微妙。比较第二行的第二列和第三列。在有效文件中,这四个字节是
0x00
0x80
0x00
0x00
。在您上传的文件中,同样的四个字节是0x00
0xc2
0x80
0x00
。为什么?JavaScript 字符串是 UTF。这意味着任何 ASCII 二进制值 (0-127) 都用一个字节进行编码。但是,从 128 到 2047 的任何数字都会获得两个字节。上传文件中额外的
0xc2
是这种多字节编码的产物。如果您想确切了解发生这种情况的原因,可以阅读维基百科上的 UTF 编码的更多信息。您无法阻止 JavaScript 字符串发生这种情况,因此您无法在不使用 base64 的情况下通过 AJAX 上传此二进制数据。
编辑:经过进一步挖掘,某些现代浏览器可以实现这一点。如果浏览器支持
XMLHttpRequest.prototype.sendAsBinary
(Firefox 3 和 4),您可以使用它来发送图像,如下所示:对于没有
sendAsBinary
的浏览器,但是有Uint8Array
(Chrome 和 WebKit),你可以像这样填充它:Unfortunately, this isn't possible in JavaScript without some intermediate encoding. To understand why, let's assume you base64 decoded and posted the data, like you described in your example. The first few lines in hex of a valid PHP file might look like this:
If you looked at the same range of hex of your uploaded PNG file, it would look like this:
The differences are subtle. Compare the second and third columns of the second line. In the valid file, the four bytes are
0x00
0x80
0x00
0x00
. In your uploaded file, the same four bytes are0x00
0xc2
0x80
0x00
. Why?JavaScript strings are UTF. This means that any ASCII binary values (0-127) are encoded with one byte. However, anything from 128-2047 gets two bytes. That extra
0xc2
in the uploaded file is an artifact of this multibyte encoding. If you want to know exactly why this happens, you can read more about UTF encoding on Wikipedia.You can't prevent this from happening with JavaScript strings, so you can't upload this binary data via AJAX without using base64.
EDIT: After some further digging, this is possible with some modern browsers. If a browser supports
XMLHttpRequest.prototype.sendAsBinary
(Firefox 3 and 4), you can use this to send the image, like so:For browsers that don't have
sendAsBinary
, but do haveUint8Array
(Chrome and WebKit), you can polyfill it like so:基于 Nathan 的出色回答,我能够找到它,以便它仍然通过 jQuery.ajax。只需将其添加到 ajax 请求中:
基本上,您只需返回一个被覆盖的 xhr 对象,以便“send”意味着“sendAsBinary”。那么 jQuery 就会做正确的事情。
Building on Nathan's excellent answer, I was able to finnagle it so that it is still going through jQuery.ajax. Just add this to the ajax request:
Basically, you just return back an xhr object that is overriden so that "send" means "sendAsBinary". Then jQuery does the right thing.