Laravel, FineUploader and FileReader API: compare hash of file before uploading - php

I'd like to compare the hashed version of the file against the data stored in the database, so I can abort duplicate uploads.
Being quite "new" to the subjects of hashing and the FileReader API, I'm finding the process a bit confusing.
I am using the frontend library SparkMD5.js
Test n1 compare two strings:
// frontend
$.post(url, {sparkMD5('random text')})
// backend
$input->hash == md5('random text') // outputs true
Test n2 - inside fineuploader onSubmit event handler
// frontend
var file = this.getFile(id);
var reader = new FileReader();
//
reader.onload = function(e) {
var data = reader.result;
var hexHash = SparkMD5.hash(data);
console.log(hexHash);
}
var res = reader.readAsDataURL(file); // or reader.readAsBinaryString(file)
......
//backend
$file = Input::file('qqfile');
$hash = md5(file ) // or md5_file(file )
$hash == $input->hexHash // outputs false
My questions:
1) Why is the result of md5($file) == md5_file($file) \\ false ?
2) What's the correct way of reading a file readAsDataURL or readAsBinaryString ?
3) Is there a way to read and hash the result of fineuploader's getFile(id) which returns File or Blob, without using filereader api?
Answers to the above questions may be considered broad and out of scope of my immediate problem, but i'd like to understand the subject as best as possible.
Thanks.

Input::file() returns an instance of Illuminate\Http\UplaodedFile, so to get the hash of its contents you can do the following:
// md5_file takes in the file path as argument
$hash = md5_file(Input::file('yourfile')->path());
readAsDataURL() is much safer because the file is encoded as base64. Just make sure that the server is aware of the encoding, meaning remove the first characters up to the comma then decode the rest as base64. More information here: fileReader.readAsBinaryString to upload files
According to this you still have to use filereader: How to include Content-MD5 header in FineUploader Azure request?.

Thanks to #alio f I was able to come up with a solution. Here's the code.
frontend
var t = this;
var reader = new FileReader();
var file = this.getFile(id);
reader.addEventListener("load", function () {
var base64result = reader.result.split(',')[1]; // only get base64 string
var hash = SparkMD5.hash(base64result);
}, false);
var data = reader.readAsDataURL(file);
Refer to FineUploader docs for the onSubmit handler.
backend
$this->file = Input::file('qqfile');
$base64 = base64_encode(file_get_contents($this->file));
$hash = md5($base64);
Comparing frontend's md5 and backend's md5 now returns true

Related

Can't upload images to the server on mobile

I am looking for any pointers what I am doing wrong here?
Situation:
I created a simple form on my website that requires the user to enter some text data and an image. This information is then stored on the server - picture separately, and the text data in a json file.
I have tested it with multiple image formats on my desktop (I allow only the most common types, such as jpg, png, or bmp). All seems to be fine. However, it isn't so smooth on mobile (iOS). When I attempt to upload a screenshot (shows as a png format), or a picture I just took (jpg), the response that comes back says Missing picture, which means that no data was received on the server. The thing is that this is not always the case, some screenshots come through, some don't.
I am encoding the picture as a base64 string on the client. I tried logging it to make sure it goes through, and it seems to be fine. However, when I log the received parameters on the server, in these failed cases, the picture string is really empty! All I do is I read the $_REQUEST or $_POST parameters (it's in PHP).
This error has been replicated only on mobile so far. On the front side, I guess this is the most relevant piece of code, but if you need more let me know! You can also inspect that website I included, but it will be slightly more difficult as it's minified.
function init_form_submit () {
var button = document.getElementById('form-submit-btn'),
image_upload_button = document.getElementById('picture');
if (!button || !image_upload_button) return;
image_upload_button.addEventListener('change', function (event) {
preview_image(this);
});
button.addEventListener('click', function (event) {
// Hijack the form submit.
event.preventDefault();
show_form_loader();
var form_validator = FormValidator(GLOB.form_node);
if (!form_validator.valid) {
form_submitted_callback();
}
else {
var http = new XMLHttpRequest();
var formData = new FormData();
formData.append("about", form_validator.fd.about);
formData.append("email", form_validator.fd.email || '');
formData.append("handle", form_validator.fd.handle);
formData.append("name", form_validator.fd.name);
formData.append("picture", GLOB.picture);
http.open('POST', 'https://lmen.us/royalkitten/api/apply-royal-kitten/index.php', true);
http.setRequestHeader('Content-type', 'multipart/form-data');
http.onreadystatechange = function () {
var response;
if (http.readyState !== 4 || http.status !== 200) return;
try {
response = JSON.parse(http.responseText);
}
catch (error) {
response = http.responseText;
console.log(response);
}
form_submitted_callback(response);
}
http.send(formData);
}
});
}
Here is how I store the image data in the GLOB.picture variable. This function is called only twice - once in the code above when the user chooses an image, and once in a callback after a successful submission as a way to reset the form to its original state.
function preview_image (input) {
var preview_label = document.getElementById('picture-label'),
preview_element = document.getElementById('picture-preview');
if (!input.files.length) {
if (preview_label) {
preview_label.innerHTML = 'Select a file';
}
if (preview_element) {
preview_element.src = './images/image-placeholder-600x600.jpg';
}
GLOB.picture = null;
return;
}
var reader = new FileReader(),
file = input.files[0];
if (!file) return;
reader.onload = function (event) {
var image_data = event.target.result;
if (preview_label) {
preview_label.innerHTML = file.name;
}
if (preview_element) {
preview_element.src = image_data;
}
GLOB.picture = image_data;
}
reader.readAsDataURL(file);
}
I guess, the problem is that your image is probably bigger than PHP max size allowed for a POST request. You should send your form as multipart/form-data, and send your image as a file. On the server side, you should get it via $_FILE instead of $_POST...
To send your image as a file, there are multiple solutions. First, you could send a base-64 encoded Blob, but it would be 30% larger than the original file (because of base-64 encoding).
What I would recommend to you is that you send the file in its original binary format, which is easier to implement and faster to upload.
To do so, you just need to send as-is the content of input.files[0]. let's say you set a GLOB.pictureFile=input.files[0] in your preview_image() function. you then just send it in the form like this :
formData.append("picture", GLOB.pictureFile);

Upload bitmap data from flash to laravel route

I have a video player built in AS3. I take a snapshot of the video player using this code:
var uploadUrl = 'http://localhost:8000/assets/uploadframegrab';
var bitmap = new Bitmap();
var graphicsData : Vector.<IGraphicsData>;
graphicsData = container.graphics.readGraphicsData();
bitmap.bitmapData = GraphicsBitmapFill(graphicsData[0]).bitmapData;
var jpgEncoder:JPGEncoder = new JPGEncoder(85);
var jpgStream:ByteArray = jpgEncoder.encode(bitmap.bitmapData);
var loader:URLLoader = new URLLoader();
var header:URLRequestHeader = new URLRequestHeader("Content-type", "application/octet-stream");
var csrf:URLRequestHeader = new URLRequestHeader("X-CSRF-Token", csrfToken);
var request:URLRequest = new URLRequest(uploadUrl);
request.requestHeaders.push(header);
request.requestHeaders.push(csrf);
request.method = URLRequestMethod.POST;
request.data = jpgStream;
loader.load(request);
I need to upload the encoded to JPG using one of my Laravel routes. My route looks like:
Route::post('assets/uploadframegrab', 'AssetController#uploadFramegrab');
When I run the AS3 code, it calls the laravel route, but my $request variable appears to be empty. The Request Payload property on the network info tab that shows all my headers and stuff contains what looks like the source of the image file.
If I do a return Response::json(['filedata' => $request]); all I get is this:
filedata: {
attributes: {},
request: {},
query: {},
server: {},
files: {},
cookies: {},
headers: {}
}
My uploadFramegrab function is simply this for now:
public function uploadFramegrab(Request $request)
{
if ($request)
{
return Response::json(['filedata' => $request]);
}
else
{
return Response::json(['error' => 'no file uploaded']);
}
}
I've searched online but I cannot find anything specifically for uploading from flash to laravel. I've done it javascript to laravel no problem. Anyone know what this could be? If you'd like more information please ask.
To do that, you can use the Multipart.as ( AS3 multipart form data request generator ) from Jonas Monnier. It's really very easy to use it, take a look on this example ( using the basic example from the github project's page ) :
var upload_url:String = 'http://www.example.com/upload';
// create an orange square
var bmp_data:BitmapData = new BitmapData(400, 400, false, 0xff9900);
// compress our BitmapData as a jpg image
var image:ByteArray = new JPGEncoder(75).encode(bmp_data);
// create our Multipart form
var form:Multipart = new Multipart(upload_url);
// add some fields if you need to send some informations
form.addField('name', 'bmp.jpg');
form.addField('size', image.length.toString());
// add our image
form.addFile('image', image, 'image/jpeg', 'bmp.jpg');
var loader:URLLoader = new URLLoader();
loader.load(form.request);
Then, in the PHP side, you do as you have usually did :
public function upload(\Illuminate\Http\Request $request)
{
if($request->hasFile('image'))
{
$file = $request->file('image');
$upload_success = $file->move($your_upload_dir, $file->getClientOriginalName());
if($upload_success)
{
return('The file "'.$request->get('name').'" was successfully uploaded');
}
else
{
return('An error has occurred !');
}
}
return('There is no "image" file !');
}
Hope that can help.
Based on the doc for AS3 (emphasis mine):
The way in which the data is used depends on the type of object used:
If the object is a ByteArray object, the binary data of the ByteArray object is used as POST data. For GET, data of ByteArray type is not supported. Also, data of ByteArray type is not supported for FileReference.upload() and FileReference.download().
If the object is a URLVariables object and the method is POST, the variables are encoded using x-www-form-urlencoded format and the resulting string is used as POST data. An exception is a call to FileReference.upload(), in which the variables are sent as separate fields in a multipart/form-data post.
You're clearly in the first case here.
From the Laravel Requests doc:
To obtain an instance of the current HTTP request via dependency injection, you should type-hint the Illuminate\Http\Request class on your controller constructor or method. The current request instance will automatically be injected by the service container.
The Request class API:
string|resource getContent(bool $asResource = false)
Returns the request body content.
Putting it together:
public function uploadFramegrab(Request $request) {
$content = $request->getContent();
$fileSize = strlen($content);
}
In Laravel 4:
$csrf = Request::header('X-CSRF-Token');
// Add a header like this if you want to control filename from AS3
$fileName = Request::header('X-File-Name');
$content = Request::getContent(); // This the raw JPG byte array
$fileSize = strlen($content);
Last time I checked Laravel uses php://input to read the request body. See this answer for more info.

Upload Image using ajax (json)

I need to upload image using ajax. Elaborating my point:
I need to pass my image data to a PHP page and check the type, size, name and all other attributes of the file. If all attributes matches, then only I need to transfer the file. Problem here is passing of data should be done in JSON(AJAX) format only. One more important thing is that I don't have to convert it to base64.
If you can help me in this, You are most welcome.
Thanks in advance.
The idea in SO is to work on the OP current code. I mean, we are not here to make all the job, because it should have a price. Anyway, here is a workaround for your issue:
Convert your image to base64 using javascript. This useful method works like a charm:
// Code taken from MatthewCrumley (http://stackoverflow.com/a/934925/298479)
function getBase64Image(img) {
// Create an empty canvas element
var canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
// Copy the image contents to the canvas
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
// Get the data-URL formatted image
// Firefox supports PNG and JPEG. You could check img.src to guess the
// original format, but be aware the using "image/jpg" will re-encode the image.
var dataURL = canvas.toDataURL("image/png");
return dataURL.replace(/^data:image\/(png|jpg);base64,/, "");
}
Then just pass the returned string as base64 through ajax:
$.ajax({
url: 'path/to/your/script.php',
type: 'post',
data: { paramName: imagedata } // the returned data from above js method,
/*...*/
});
And, in PHP side, just return the string to an image file:
// Code taken from Austin Brunkhorst (http://stackoverflow.com/a/15153931/3648578)
function base64_to_jpeg($base64_string, $output_file) {
$ifp = fopen($output_file, "wb");
$data = explode(',', $base64_string);
fwrite($ifp, base64_decode($data[1]));
fclose($ifp);
return $output_file;
}

Javascript calling Flash function to upload file

I was wondering if i could get som suggestions on what the best approach to do the following would be...
I am currently calling a Flash AS3 function from Javascript (using jQuery) this function uploads a file which has already been selected in this flash file. Flash then uploads the file and calls a php file (upload.php) which handles the processed file. This all works fine. However there are other details that are filled out that pertain to the file uploaded (entered by the user in textboxes) All this data including the file path to where it has been uploaded must then be saved to a DB. Which can be done through an ajax call to another php file (processData.php). My issue is that when i upload the file i cant send the other details along with the file through flash (atleast not that i know of) which causes 2 different php scripts to execute. Secondly the other php script called through ajax doesnt have the file information to add to the DB. I can store this info in a session if i want but it just doesnt seem to convince me as the best way to go about this. Any other ideas or suggestions?
There is quite a bit of code I have so to avoid making this a HUGE question ill post the JS part that i think is important and some snippets of flash so you can have an idea of whats going on... If theres anyhting else youd like to see of the code feel free to ask and ill post it...
JS:
$("#uploadAudio").submit(function(event) {
event.preventDefault();
var form = $(this);
var title = form.find("#title").val();
var desc = form.find("#desc").val();
var flash = $("#flash");
var flashFileSet = flash.get(0).jsIsFileSelected();
if(flashFileSet)
{
$.ajax({
type: "POST",
url: "processData.php",
dataType: "text",
data: "title=" + title + "&desc=" + desc,
async: false,
success: function() {
audFile.get(0).jsUploadFile();
}
});
}
});
Flash
public function fUploader(){
req = new URLRequest();
req.url = ( stage.loaderInfo.parameters.f )? stage.loaderInfo.parameters.f : 'http://virtualmanagementonline.com/ajax/audUpload.php';
pFilterType = ( stage.loaderInfo.parameters.filterType )? stage.loaderInfo.parameters.filterType : 'Images';
pFileFilters = ( stage.loaderInfo.parameters.fileFilters )? stage.loaderInfo.parameters.fileFilters : '*.jpg;*.jpeg;*.gif;*.png';
file = new FileReference();
setup( file );
select_btn.addEventListener( MouseEvent.CLICK, browse );
progress_mc.bar.scaleX = 0;
tm = new Timer( 1000 );
tm.addEventListener( TimerEvent.TIMER, updateSpeed );
cancel_btn.addEventListener( MouseEvent.CLICK, cancelUpload );
cancel_btn.visible = false;
ExternalInterface.addCallback("jsUploadFile", uploadFile);
ExternalInterface.addCallback("jsIsFileSelected", IsFileSelected);
}
public function browse( e:MouseEvent ){
filefilters = [ new FileFilter(pFilterType, pFileFilters) ]; file.browse( filefilters );
}
private function selectHandler( e:Event ){
var tf = new TextFormat();
tf.color = 0x000000;
label_txt.defaultTextFormat = tf;
label_txt.text = file.name;
//file.upload( req );
}
public function IsFileSelected():Boolean{
if(label_txt.text != "")
{
return true;
}
else
{
return false;
}
}
public function uploadFile():void{
file.upload(req);
}
**Note: NOT all the flash code is shown since there is alot. I put up what i thought was needed to get an understanding of what exactly is going on.
If there is anything i can add for further details please let me know. Thanks in advance!
You can send as many data as you want to flash, since ExternalInterface is available.
ActionScript 3 Reference states the following about ExternalInterface:
From JavaScript on the HTML page, you can:
- Call an ActionScript function.
- Pass arguments using standard function call notation.
- Return a value to the JavaScript function.
All you have to do is register an ActionScript function/method as callable from the container:
ActionScript
...
ExternalInterface.addCallback("jsUploadFile", uploadFile);
...
public function uploadFile (title:String, desc:String):void
{
var infos:URLVariables = new URLVariables();
infos.desc = desc;
infos.title = title;
/* When you pass the URLVariables to data property of URLRequest,
all variables associated with the URLVariables object will be
sent to the server along with the image uploaded. */
req.data = infos;
file.upload(req);
}
Then, call it from the container (HTML) passing the additional information as parameters.
JavaScript
$("#uploadAudio").submit(function(event) {
event.preventDefault();
var form = $(this);
var title = form.find("#title").val();
var desc = form.find("#desc").val();
var flash = $("#flash");
var flashFileSet = flash.get(0).jsIsFileSelected();
if(flashFileSet)
{
/* Instead of sending title and desc to the server via ajax, pass
them as parameters to the jsUploadFile method. So
you can handle everything in one place */
audFile.get(0).jsUploadFile(title, desc);
}
});
Hope it helps.

Returning a value from php to swf

I am trying to transfer a movie-clip staged in a swf(on local machine) to a remote server. Below is a part of the action-script code concerned with it;
function createJPG(mc:MovieClip, n:Number, fileName:String) {
trace("sdf:");
var jpgSource:BitmapData = new BitmapData(mc.width,mc.height);
jpgSource.draw(mc);
var jpgEncoder:JPGEncoder = new JPGEncoder(n);
var jpgStream:ByteArray = jpgEncoder.encode(jpgSource);
trace("jpegStream::"+jpgStream);
var header:URLRequestHeader = new URLRequestHeader("Content-type","application/octet-stream");
var jpgURLRequest:URLRequest = new URLRequest("http://example.com/arts/savefile.php?name=" + fileName + ".jpg");
jpgURLRequest.requestHeaders.push(header);
jpgURLRequest.method = URLRequestMethod.POST;
jpgURLRequest.data = jpgStream;
var loader:URLLoader = new URLLoader();
trace("navigatetoURL:");
sendToURL(jpgURLRequest); }
The php script in the remote server to save the file is;
<?php
set_time_limit(0);
if( isset($GLOBALS['HTTP_RAW_POST_DATA']) ) {
$imageFile='images/'.$_GET['name'];
$fp = fopen($imageFile, 'w+');
// get bytearray
fwrite($fp, $GLOBALS['HTTP_RAW_POST_DATA']);
fclose($fp);
if( file_exists($imageFile) ) {
echo 'File saved.';
}
else {
echo 'Error: Problem writing the file.';
}
}
else {
echo 'Error: Not data available to write the file.';
}
?>
I want to close the swf when the upload is complete., I would like to know how to return a value(may be a number which I intend to use to indicate completion of file transfer) from the php script to the swf and how to receive that value in the swf?
Would really appreciate any help.
Thanks!.
The URLLoader supports an Event.COMPLETE event, which is fired when your remote operation returns data and that data has been stored in URLRequest.data.
From PHP, the data that you echo is what Flash will receive, so you might want to consider a more complex data type than a single string, maybe use json and pass an object that looks like:
{result:1,message:"some message"};
This gives you a simple 1/0 result value for easy checking and a string message for output to the user. You would do that in PHP by creating an associative array and encoding it with JSON.encode().
Then on the Flash side you will add an event listener to your URLLoader to detect the COMPLETE event, and use the JSON class from the Adobe core library to decode your PHP output to a useable object.
I always use AMFPHP nowadays http://sourceforge.net/projects/amfphp/ . Gotoandlearn has a nice free tutorial on it http://www.gotoandlearn.com/ .

Categories