HTML JS PHP File chunk upload - javascript

I am working with a file chunking solution ( cant remember where I found it ) but I have modified the solution to my needs.
The issue is that although most of the time the file will upload successfully sometimes I get an error
Uncaught SyntaxError: Unexpected token < in JSON at position 0
I also get another error at the same time
( unlink(test/H2jig-6.png): Resource temporarily unavailable in ... )
I was hoping that you guys will be able to spot the issue in my code that is causing this problem. I think that it manages to unlink the files too early before the uploads are done and then is unable to find them.
Upload file HTML/JS
<!DOCTYPE HTML>
<html lang="en-US">
<head>
<meta charset="UTF-8">
<title>test upload by chunk</title>
</head>
<body>
<input type="file" id="f" />
<script>
function makeid() {
var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (var i = 0; i < 5; i++)
text += possible.charAt(Math.floor(Math.random() * possible.length));
return text;
}
(function() {
var f = document.getElementById('f');
if (f.files.length)
processFile();
f.addEventListener('change', processFile, false);
function processFile(e) {
var scount = 0;
var file = f.files[0];
console.log(file);
var ext = file.name.split('.').pop();
ext = '.'+ext;
var size = file.size;
var sliceSize = 250000;
var num_of_slices = Math.ceil(size / sliceSize);
var fileid = makeid();
var start = 0;
setTimeout(loop, 1);
function loop() {
var end = start + sliceSize;
if (size - end < 0) {
end = size;
}
var s = slice(file, start, end);
scount ++;
send(s, start, end,scount,size,num_of_slices,ext,fileid);
if (end < size) {
start += sliceSize;
setTimeout(loop, 1);
}
}
}
function send(piece, start, end,scount,size,num_of_slices,ext,fileid) {
var formdata = new FormData();
var xhr = new XMLHttpRequest();
xhr.open('POST', 'uploadchunk2.php', true);
formdata.append('start', start);
formdata.append('end', end);
formdata.append('file', piece);
formdata.append('scount', scount);
formdata.append('fsize', size);
formdata.append('num_of_slices', num_of_slices);
formdata.append('ext', ext);
formdata.append('fileid', fileid);
xhr.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
var myArr = JSON.parse(this.responseText);
console.log(myArr);
// myFunction(myArr);
}
};
xhr.send(formdata);
}
/**
* Formalize file.slice
*/
function slice(file, start, end) {
var slice = file.mozSlice ? file.mozSlice :
file.webkitSlice ? file.webkitSlice :
file.slice ? file.slice : noop;
return slice.bind(file)(start, end);
}
function noop() {
}
})();
</script>
</body>
</html>
PHP upload
<?php
$target_path = '/test/';
$tmp_name = $_FILES['file']['tmp_name'];
$filename = $_FILES['file']['name'];
$target_file = $target_path.$filename;
$num_chunks_uploaded = $_POST['scount'];
$num_chunks_created = $_POST['num_of_slices'];
$extension = $_POST['ext'];
$file_id = $_POST['fileid'];
$file_location = 'test/';
$file_path = $file_location.$file_id.$extension;
$chunked_file_path = $file_location.$file_id.'-'.$num_chunks_uploaded.$extension;
move_uploaded_file(
$_FILES['file']['tmp_name'],
$chunked_file_path
);
// count amount of uploaded chunks
$chunksUploaded = 0;
for ($i=1; $i <= $num_chunks_created ; $i++) {
if ( file_exists($file_location.$file_id.'-'.$i.$extension) ) {
++$chunksUploaded;
}
}
// when this triggers - that means the chunks are uploaded
if ($chunksUploaded == $num_chunks_created) {
/* here you can reassemble chunks together */
for ($i = 1; $i <= $num_chunks_created; $i++) {
$file = fopen($file_location.$file_id.'-'.$i.$extension, 'rb');
$buff = fread($file, 2097152);
fclose($file);
$final = fopen($file_path, 'ab');
$write = fwrite($final, $buff);
fclose($final);
unlink($file_location.$file_id.'-'.$i.$extension);
}
}
$data = $chunksUploaded;
header('Content-Type: application/json');
echo json_encode($_POST);
?>

This isn't the answer to your problem, but it solves one problem you do have in your JavaScript.
PHP requires that a MAX_FILE_SIZE field be sent with uploaded files. This field must appear before the file field in the POST variables. So you need to add:
formdata.append('MAX_FILE_SIZE', 1000000) // or whatever you want the max size to be
before the field containing the file to be sent.

Related

Ajax post has previous data

This is driving me crazy. I know I am doing something wrong but what? The problem is I am getting previous data in my ajax call when I post it to php.
Story: I click upload button which calls JavaScript loadme() where i am cleaning the input type file element. I then select file/files and click upload which calls doit() which internally calls uploadFiles(). UploadFiles makes an array with loaded images and post it to the php file through ajax.
I have tried plain javascript and jquery to clean file input element and php to set post values to null after using it, but still i am getting the previous values.
The form
<form id="theform" method="post" action="" enctype="multipart/form-data" target="multifile">
<input type="hidden" id="folderpath" name="folderpath" value="<?=htmlspecialchars($path)?>">
<input name="upfiles[]" id="upfiles" type="file" multiple="" />
</form>
Javascript
async function doit(){
//some code
uploadFiles();
}
function loadmeup(){
$('upfiles').value = '';
$('uploadbox').show();
}
function closeupload(){
$('uploadbox').hide();
}
var masterFileArray = [];
var left_loaded_count = 0;
var progress = 0;
function uploadFiles() {
var files = document.getElementById("upfiles").files;
var path = document.getElementById('folderpath').value;
var numOfFiles = files.length;
alert(numOfFiles);
if (files && numOfFiles>0) {
for (var i = 0, f; f = files[i]; i++) {
if(checkExtension(f.name) && checkSize(f.size)){
progress = i+1/numOfFiles;
var r = new FileReader();
r.onload = (function (f) {
return function (e) {
var contents = e.target.result;
masterFileArray.push({name:f.name, contents: contents, type:f.type, size:f.size});
console.log(f.name);
left_loaded_count -= 1;
if(left_loaded_count == 0)
{
var filesarray = null;
filesarray = JSON.stringify(masterFileArray);
console.log("after stringify: "+filesarray.length);
new Ajax.Request('upload_do.php', {
method: 'post',
parameters: {files: filesarray, folderpath:path},
onSuccess: function(transport){
var response = transport.responseText;
parent.notify(response);
closeupload();
get_listview(currlist,sort);
}
});
}
};
})(f);
left_loaded_count += 1;
r.readAsDataURL(f);
}
else{
alert('Invalid file extension or size ' + f.name);
if(progress==0)
location.reload();
}
}
} else {
alert('Failed to load files');
closeupload();
}
}
PHP
try{
$error = null;
$files = json_decode($_POST['files'],true);
$numFiles = 0;
foreach($files as $file){
$output = $reso->writeUpload($file['contents'], $file['name']);
$numFiles++;
}
}
catch (Exception $ex){
$error = $ex->getMessage();
$GLOBALS['log']->log($error);
}
isset($_POST['files'])? $_POST['files'] = null: '';
if (!is_null($error))
echo _($error);
else
echo _($numFiles.' file'.(($numFiles > 1) ? 's' : '').' successfully
uploaded');

Ajax data not being displayed after upload

Currently my entire code is working almost flawlessly, how ever upon upload the ajax data is not being displayed on my webpage after a successful upload.
Below is the code I am using (completely) because I am rather new and have been reviewing various stack over flow posts to guide me in building this.
Sorry for lack of structure below.. I don't know how to adapt to stack overflows structure requirements..
The actual form input / submit
<form action="upload2.php" class="upload" enctype="multipart/form-data" id="upload" method="post" name="upload">
<fieldset>
<legend>Uplaod Files</legend> <input id="file" multiple name="file[]" required="" type="file"> <input id="submit" name="submit" type="submit" value="upload">
</fieldset>
<div class="bar">
<span class="bar-fill" id="pb"><span class="bar-fill-text" id="pt"></span></span>
</div>
<div class="uploads" id="uploads"></div>
</form>
Here's the PHP code I am using to upload the file.
<?
header('Content-Type: application/json');
$upload = [];
$allowed = ['mp3', 'm4a'];
$succeeded = [];
$failed = [];
if (!empty($_FILES['file'])) {
foreach ($_FILES['file']['name'] as $key => $name) {
if ($_FILES['file']['error'][$key] === 0) {
$temp = $_FILES['file']['tmp_name'][$key];
$file_name = $_FILES['file']['name'][$key];
$file_size = $_FILES['file']['size'][$key];
$ext = explode('.', $name);
$ext = strtolower(end($ext));
$s = substr(str_shuffle(str_repeat("0123456789abcdefghijklmnopqrstuvwxyz", 4)), 0, 4);
$file = $s . '.' . $ext;
$data = $file_name . PHP_EOL . $file_size;
file_put_contents('d/' . $s . '.txt', $data);
if (in_array($ext, $allowed) === true && move_uploaded_file($temp, "v/{$file}") === true) {
$succeeded[] = array(
'name' => $name,
'file' => $file,
);
} else {
$failed[] = array(
'name' => $name
);
}
}
}
if (isset($_POST['ajax'])) {
echo json_encode(array(
'succeeded' => $succeeded,
'failed' => $failed
));
}
}
Below is javascript event listener + the returned data from ajax
document.getElementById('submit').addEventListener('click', function (e) {
e.preventDefault();
var f = document.getElementById('file'),
pb = document.getElementById('pb'),
pt = document.getElementById('pt');
app.uploader({
files: f,
progressBar: pb,
progressText: pt,
processor: 'upload2.php',
finished: function (data) {
var uploads = document.getElementById('uploads'),
succeeded = document.createElement('div'),
failed = document.createElement('div'),
anchor,
span,
x;
if (data.failed.length) {
failed.innerHTML = '<p>file didnt upload<\/p>';
}
uploads.innerText = '';
for (x = 0; x < data.succeeded.length; x = x + 1) {
anchor = document.createElement('a');
anchor.href = 'uploads/' + data.succeeded[x].file;
anchor.innerText = data.succeeded[x].name;
archor.target = '_blank';
console.log(anchor);
succeeded.appendChild(anchor);
}
uploads.appendChild(succeeded);
},
});
});
Below is the javascript we're using for ajax (I think the issue is here but I keep reviewing it and I'm not able to find the problem.)
var app = app || {};
(function (o) {
"use strict";
var ajax, getFormData, setProgress;
ajax = function (data) {
var xmlhttp = new XMLHttpRequest(),
uploaded;
xmlhttp.addEventListener('readystatechange', function () {
if (this.readystate === 4) {
if (this.status === 200) {
uploaded = JSON.parse(this.response);
if (typeof o.options.finished === 'function') {
o.options.finished(uploaded);
}
} else {
if (typeof o.options.error === 'function') {
o.options.error();
}
}
}
});
xmlhttp.upload.addEventListener('progress', function (event) {
var percent;
if (event.lengthComputable === true) {
percent = Math.round((event.loaded / event.total) * 100);
setProgress(percent);
}
});
xmlhttp.open('post', o.options.processor);
xmlhttp.send(data);
};
getFormData = function (source) {
var data = new FormData(),
i;
for (i = 0; i < source.length; i = i + 1) {
data.append('file[]', source[i]);
}
data.append('ajax', true);
return data;
};
setProgress = function (value) {
if (o.options.progressBar !== undefined) {
o.options.progressBar.style.width = value ? value + '%' : 0;
}
if (o.options.progressText !== undefined) {
o.options.progressText.innerText = value ? value + '%' : '';
}
};
o.uploader = function (options) {
o.options = options;
if (o.options.files !== undefined) {
ajax({});
ajax(getFormData(o.options.files.files));
}
}
}(app));
My file is uploading, the progress bar is working and without returning e.preventDefault(); when it redirect to upload2.php I'm able to see the contents of the json i really don't know what my problem could be..
First of all, you have a typo in anchor here:
archor.target = '_blank';
Maybe this throws error and stops other code execution
Also you could check if your uploads div is visible via element inspector in browser.
If console.log shows nothing - do you mean by this it is not even called or it shows undefined? If it doesn't show at all, then look into the for that uses data.succeeded.length param as a limit - maybe it equals 0 and thus console.log is never called

Splice, Upload, and Re-Combine File Blobs

I am currently working on an upload system with a combination of Javascript and PHP. The Javascript essentially reads a file in by splicing blobs of cutoffSize size (or, the remaining file size, if less). Each splice is sent to the php script via Post. Then, the PHP script re-assembles the file blob by blob.
However, in my implementation (as shown below), the output file is too large. I am not sure why the file is not being written correctly, but I believe that I have localized the problem to the php script. All of my javascript testing appears to have succeeded. However, I have included the Javascript as well, just in case.
I believe the problem may be in the way I write/append files. Unfortunately, I am not sure how to move forward, so I cannot investigate this further or describe in more detail.
Any help would be greatly appreciated.
Javascript:
function getBigStringAndPost(files,i,p) {
var key = $("#keyText")[0].value
var reader = new FileReader();
var blob = files[i].slice(p*cutoffSize,Math.min((p+1)*cutoffSize,files[i].size));
var folderName = $("#folderInput")[0].value;
reader.onload = function(e) {
var result = reader.result;
console.log(result.length);
$.post("http://brandonquinndixon.com/PHP/uploadFile.php",{
uploadKey: key,
doUpload: 1,
folderName: folderName,
splitPart: p,
fileName: files[i].name,
fileContents: reader.result
},function(e) {
console.log(e);
if ((p+1)*cutoffSize<files[i].size) {
currentUploaded += cutoffSize;
updateProgressBar();
reader.abort();
getBigStringAndPost(files,i,p+1);
} else {
$("#statusDiv").html($("#statusDiv").html()+"<p class='normalP'>File: "+files[i].name+" uploaded successfully.</p>");
currentUploaded += files[i].size%cutoffSize;
updateProgressBar();
reader.abort();
loadNextFile(files,i+1);
}
});
};
reader.onerror = function(e) {
console.log(e);
totalSizeOfFiles -= files[i].size;
$("#statusDiv").html($("#statusDiv").html()+"<p class='normalP'>File: "+files[i].name+" could not be read.</p>");
loadNextFile(files,i+1);
};
reader.readAsBinaryString(blob);
}
PHP:
$targetDir = $_SERVER['DOCUMENT_ROOT']."/Downloads";
$getKey = $_POST['uploadKey'];
$data = array();
error_log("upload attempt started");
//first, check the upload key
if ($getKey !== $uploadKey) {
$data['status'] = "failure";
$data['message'] = "Incorrect Upload Key";
error_log("bad key");
} else if ($_POST['doUpload'] == 1) {
$fileName = "";
if ($_POST['folderName']!=="") {
$folderName = cleanInput($_POST['folderName']);
if (!is_dir($targetDir."/".$folderName)) {
mkdir($targetDir."/".$folderName, 0777, true);
}
$fileName = $targetDir."/".$folderName."/".$_POST['fileName'];
} else {
$fileName = $targetDir."/".$_POST['fileName'];
}
/*
Don't allow executables
*/
if (strpos($fileName,".exe") > -1 || strpos($fileName,".sh") > -1 || strpos($fileName,".deb") > -1 || strpos($fileName,".rpm") > -1) {
$data['status'] = 'failure';
$data['message'] = 'Executable type files are not allowed';
} else {
if ($_POST['splitPart'] == 0) {
overWriteFile($fileName,$_POST['fileContents']);
$data['type'] = 'overWrite';
$data['details'] = 'P '.$_POST['splitPart'];
} else {
appendFile($fileName,$_POST['fileContents']);
$data['type'] = 'append';
$data['details'] = 'P '.$_POST['splitPart'];
}
$data['status'] = 'success';
}
} else {
$data['status'] = 'success';
$data['type'] = 'none';
error_log("success, no upload");
}
echo json_encode(array($data));
function overWriteFile($fName,$fContents) {
$fName = lookupFile($fName);
$file = fopen($fName,"w");
fwrite($file,$fContents);
fclose($file);
}
/*
Append to end of file
*/
function appendFile($fName,$fContents) {
$fName = lookupFile($fName);
//$file = fopen($fName,"a");
//fwrite($file,$fContents);
//fclose($file);
file_put_contents($fName,$fContents,FILE_APPEND);
}

how to put double variable data in ajax

i've case to use variable more the one like code bellow :
function upload(iddata,ring)
{
//variable 1
dataString ="iddata="+iddata+"&ring="+ring+""; // variable 1
//variable 2
var formData = new FormData();
for (var i = 0; i < filesToUpload.length; i++) {
var file = filesToUpload[i];
formData.append("file[]", file, file.name);
}
// i want put both variabel in ajax like this
$.ajax({
data: {formData,dataString}, //variable 1 and 2 in group
});
}
please how source is True
You should handle your whole data as an object only, and not mix it as a string and a array in a object. In this case you would not get the expected data in your receiver.
You could change it like this:
function upload(iddata, ring) {
var data = {
iddata: iddata,
ring: ring,
formData: new FormData()
};
for (var i = 0; i < filesToUpload.length; i++) {
var file = filesToUpload[i];
data.formData.append("file[]", file, file.name);
}
$.ajax({
data: data
});
}
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class MyBird extends CI_Controller {
/**
* Index Page for this controller.
function add_bird()
{
// get user id posted
$iddata=empty($_POST['iddata']) ? '' : $_POST['iddata'];
$ring=empty($_POST['ring']) ? '' : $_POST['ring'];
//echo json_encode(array('uploaded' => 'Oke Bos'));
if ( ! empty($_FILES))
{
$config['upload_path'] = "./assets/images/uploads";
$config['allowed_types'] = 'gif|jpg|png|mp4|ogv|zip';
$this->load->library('upload');
$files = $_FILES;
$number_of_files = count($_FILES['file']['name']);
$errors = 0;
// codeigniter upload just support one file
// to upload. so we need a litte trick
for ($i = 0; $i < $number_of_files; $i++)
{
$Random_Number = rand(0, 9999999999); //Random number to be added to name.
$NewFileName = "Ring".$ring._.$Random_Number; //new file name
$config['file_name'] = $NewFileName;
$_FILES['file']['name'] = $files['file']['name'][$i];
$_FILES['file']['type'] = $files['file']['type'][$i];
$_FILES['file']['tmp_name'] = $files['file']['tmp_name'][$i];
$_FILES['file']['error'] = $files['file']['error'][$i];
$_FILES['file']['size'] = $files['file']['size'][$i];
// we have to initialize before upload
$this->upload->initialize($config);
if (! $this->upload->do_upload("file")) {
$errors++;
}
}
if ($errors > 0) {
echo $errors . "File(s) cannot be uploaded";
}
}
}
}
in controller i put code like bellow, but the filen can't move upload

Upload chunked file using XHR and web workers

Hi everyone and happy new year :)
I want to upload a file using XHR and web workers, sending chunks of the file and merging at the end. The problem is that the end file is empty, I think that the issue is in the content type of XHR request that will should send a correct "multipart/form-data" (when uploading a chunk), since that PHP print_r($_FILES) returns an empty Array() but in the web worker it's not possible to use FormData(). Help me to resolve this trouble, please :'(
index.html
<form onsubmit="return false" enctype="multipart/form-data">
<input id="file" type="file">
<div id="filedrop">or drop files here</div>
</form>
<script>
window.addEventListener("load", function() {
var fileselect = document.getElementById("file");
fileselect.addEventListener("change", FileSelectHandler, false);
var filedrag = document.getElementById("filedrop");
filedrag.addEventListener("dragover", FileDragHover, false);
filedrag.addEventListener("dragleave", FileDragHover, false);
filedrag.addEventListener("drop", FileSelectHandler, false);
}, false);
function FileDragHover(e) {
e.stopPropagation();
e.preventDefault();
}
function FileSelectHandler(e) {
FileDragHover(e);
var blob = e.target.files[0] || e.dataTransfer.files[0];
worker = new Worker("upload.webworker.js");
worker.postMessage(blob);
worker.onmessage = function(e) {
console.log(e);
};
}
</script>
uploadFile.php
<?
if ($_GET['a'] == "chunk") {
$target = "upload/" . $_GET['name'] . '-' . $_GET['index'];
move_uploaded_file($_FILES['file']['tmp_name'], $target);
sleep(1);
} else {
$target = "upload/" . $_GET['name'];
$dst = fopen($target, 'wb');
$slices = (int)$_GET['slices'];
for ($i = 0; $i < $slices; $i++) {
$slice = $target . '-' . $i;
$src = fopen($slice, 'rb');
stream_copy_to_stream($src, $dst);
fclose($src);
unlink($slice);
}
fclose($dst);
}
?>
upload.webworker.js
function uploadChunk(blob, index, start, end, slices, slices2) {
var xhr = new XMLHttpRequest();
xhr.onload = function() {
slices--;
if (slices == 0) {
var xhrMerge = new XMLHttpRequest();
xhrMerge.open("POST", "uploadFile.php?a=merge&name=" + blob.name + "&slices=" + slices2);
xhrMerge.onload = function() {
self.close();
};
xhrMerge.send();
}
};
xhr.upload.onprogress = function(e) {
if (e.lengthComputable) self.postMessage(Math.round(100 / e.total * e.loaded)); //this doesn't work o.O
};
var chunk = blob.slice(start, end);
xhr.open("POST", "uploadFile.php?a=chunk&name=" + blob.name + "&index=" + index);
xhr.setRequestHeader("Content-Type", "multipart\/form-data; boundary=--------------------");
xhr.send(chunk);
}
self.onmessage = function(e) {
const BYTES_PER_CHUNK = 1024 * 1024 * 32;
var blob = e.data,
start = 0,
index = 0,
slices = Math.ceil(blob.size / BYTES_PER_CHUNK),
slices2 = slices;
while (start < blob.size) {
end = start + BYTES_PER_CHUNK;
if (end > blob.size) end = blob.size;
uploadChunk(blob, index, start, end, slices, slices2);
start = end;
index++;
}
};
PS: if you want, tell me please how to optimize the upload in general =)
PPS: I'll should take advantages using synchronous ajax requests (only in the web worker) ?
PPPS: and if I to use php://input for reading chunk, it's better ?
I resolved reading file from php://input with this code:
$putdata = fopen("php://input", "r");
$fp = fopen($target, "w");
while ($data = fread($putdata, 16384)) {
fwrite($fp, $data);
}
fclose($fp);
fclose($putdata);
In this way, I don't need to write the HTTP headers of the multipart/form-data

Categories