我需要能够将客户端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时,我仍然没有得到可读文件。 所以问题是:“为什么?”或者“我做错了什么?”或者“这有可能吗?” : - )
非常感谢任何帮助!
谢谢, 凯恩
答案 0 :(得分:14)
不幸的是,如果没有一些中间编码,这在JavaScript中是不可能的。为了理解原因,让我们假设您已经解码并发布了数据,就像您在示例中所描述的那样。有效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 encoding on Wikipedia的更多信息。
您无法阻止JavaScript字符串发生这种情况,因此您无法在不使用base64的情况下通过AJAX上传此二进制数据。
编辑:经过一些进一步的挖掘,这可以通过一些现代浏览器实现。如果浏览器支持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);
};
}
答案 1 :(得分:4)
在Nathan的优秀答案的基础上,我能够完成它,以便它仍然通过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做正确的事。