Retrieving external image and saving locally results in distorted image - php

Stuck on this one. I have this function below that simply takes $ImageSrc which is an external image from anywhere, eg imgur, and then saves it locally (this is not a scraper, I'm allowing people to attach images to their profiles)
public function UploadScreenshot($ImageSrc, $Title, $Description = false) {
$RandomName = substr(md5($Title . time()), 0, 20);
$UploadDir = "/home/vanrust/public_html/Screenshots/";
$file = pathinfo($ImageSrc);
$ext = $file["extension"];
if (!in_array($ext, array('jpg','png','bmp','jpeg'))) return array("error" => "Invalid File Type");
$RandomName = "{$RandomName}.{$ext}";
$image = file_get_contents($ImageSrc);
file_put_contents($UploadDir . $RandomName, $image);
}
The result of the file no matter what is unrecognizable.
The image:
After UploadScreenshot() has retrieved it:

Try to use rename() to move the original file to the new location and rename it.
$file = pathinfo($ImageSrc);
$ext = $file["extension"];
if (!in_array($ext, array('jpg','png','bmp','jpeg'))) return array("error" => "Invalid File Type");
$RandomName = "{$RandomName}.{$ext}";
rename($UploadDir . $RandomName, $ImageSrc);
}
Alternatively, you can use move_uploaded_file() if your $ImageSrc does contain a valid upload file (meaning that it was uploaded via PHP's HTTP POST upload mechanism).
file_put_contents() needs to be used with caution. A single offset (in binary codes) at the beginning or at the end of the file will significantly alter the picture. It requires a validation at the end to compare both files bytes.

Related

Cannot upload image via move_uploaded_file

I am using code below to upload image via drag an drop system. Everything is ok, JS part is working well. But PHP part of code shows me error:
PHP Warning: move_uploaded_file(assets/img/photos/1657614494809.jpg): failed to open stream: No such file or directory in /www_root/_inc/upload.php on line 29, referer: https://smartobchod.sk/bazar/add.php
[Sat Jul 23 12:56:18 2022] [error] [client 78.99.32.1] PHP Warning: move_uploaded_file(): Unable to move '/home/gm016900/tmp/phpKaBL6m' to 'assets/img/photos/1657614494809.jpg' in /www_root/_inc/upload.php on line 29, referer: https://smartobchod.sk/bazar/add.php
EDITED 25.07.2022 (added some security checks)
My PHP code in upload.php is:
<?php
// Get reference to uploaded image
$image_file = $_FILES["file"];
// Get image name
$image_name = $_FILES["file"]["name"];
// Get file size
$image_size = $_FILES["file"]["size"];
// Exit if no file uploaded or image name contains unvalid characters /, \\
if ( ( !strpos($image_name, '/') || !strpos($image_name, '\\') ) && isset($image_file) ) {
$errors = array();
$maxsize = 10485760;
$acceptable = array(
'image/jpeg',
'image/jpg',
'image/gif',
'image/png'
);
} else {
die('No image uploaded.');
}
// Exit if image file is zero bytes or if image size is more then 10 MB
if (getimagesize($image_file["tmp_name"]) <= 0) {
die('Uploaded file has no contents.');
} elseif ($image_size >= $maxsize) {
die('Image is too large. Image must be less than 10 megabytes.');
}
// Exit if is not a valid image file or image has not supported type
$image_type = exif_imagetype($image_file["tmp_name"]);
if (!$image_type) {
die('Uploaded file is not an image.');
} elseif ( !in_array($image_file["type"], $acceptable) ) {
die('Image has not supported type JPG, PNG, GIF.');
} else {
$src = "default.png";
}
// Get file extension based on file type, to prepend a dot we pass true as the second parameter
$image_extension = image_type_to_extension($image_type, true);
// Create a unique image name
$image_name = bin2hex(random_bytes(16)) . $image_extension;
// Location
$relative_location = "/bazar/assets/img/photos/".$image_name;
$absolute_location = dirname(__DIR__, 2).$relative_location;
$return_arr = array();
// transfer file created in tmp folder to location of pictures with name saved in address of $location
// in $image_file is stored $_FILES["file"]["tmp_name"]
if (move_uploaded_file($image_file["tmp_name"], $absolute_location)) {
$src = $relative_location;
$return_arr = array("name" => $image_name,"size" => $image_size, "src"=> $src);
}
echo json_encode($return_arr);
This is sent in header to upload.php:
Content-Disposition: form-data; name="file"; filename="1657614494809.jpg"
Content-Type: image/jpeg
Can you advice me what can be problem?
Here I think your tmp/ folder permission is not proper so it is not able to read that folder or write just check /home/gm016900/tmp permission is should be gm016900 or it may not be created.
Based on your comments, you just need to keep track of relative vs absolute paths.
I wasn't able to test this code, but hopefully you should get the general gist if it fails. Instead of a single $location variable, I'm using two variables $relativeLocation and $absoluteLocation. The latter isn't actually needed, but it makes debugging much easier.
/* Getting file name */
$filename = $_FILES['file']['name'];
/* Getting File size */
$filesize = $_FILES['file']['size'];
/* Location */
$relativeLocation = "assets/img/photos/".$filename;
$absoluteLocation = __DIR__.'/'.$relativeLocation;
$return_arr = array();
/* Upload file */
if (move_uploaded_file($_FILES['file']['tmp_name'], $absoluteLocation)) {
$src = "default.png";
// checking file is image or not
if (is_array(getimagesize($absoluteLocation))) {
$src = $relativeLocation;
}
$return_arr = array("name" => $filename, "size" => $filesize, "src" => $src);
}
echo json_encode($return_arr);
Depending on where your function lives relative to the storage, you might need to go update a directory or two, or possibly go up and then back down:
// Up one directory
$absoluteLocation = dirname(__DIR__).'/'.$relativeLocation;
// Up two directories
$absoluteLocation = dirname(__DIR__, 2).'/'.$relativeLocation;
// Up one directory then over to a sibling directory
$absoluteLocation = dirname(__DIR__).'/files/'.$relativeLocation;
As a personal preference, I always have a constant (or equivalent) in my projects that represents a known location on disk, and if I'm using a router script I define it there. That way, no matter how deep I get in nesting I can build my paths relative to that constant.
I also go one step further and use this library (merged into Symfony here) to avoid string concatenation, not worry about whether something ends with a slash or not, and to be more cross platform. So my actual code looks like:
$absoluteLocation = Path::join(PROJECT_ROOT, 'files', $relativeLocation);
Some people might think that's overkill, but once again it is just a personal preference.

Trying to resize uploaded files as they are saved to server

I am using Glide to deliver image content from one of my sites. This is working well and I have now built a file upload so that admins can upload images to the site for subsequent download.
Some of the images that admins will upload will be much larger than I need (or want the overhead of storing on the server), so I want to downsize them, preferably during the upload routine or failing that, just after they have been saved to their new location (storage/app/images)
So, I've been hacking around with intervention for instance without much success because of my poor understanding of the file names and paths available from getClientOriginalName/Extension etc.
Could anyone show me a pattern for this which would work well. Ideally I'd love to include something like I've seen on others' examples like...
$img = Image::make('foo.jpg')->resize(300, 200);
... in the correct place in my code
foreach($files as $file) {
$fileExtension = $file->getClientOriginalExtension();
$fileMimeType = $file->getMimeType();
if(in_array($fileExtension, $allowableExtensions)) {
if(in_array($fileMimeType, $allowableMimes)) {
array_push($dbFileList, $file->getClientOriginalName());
$newImage = '/images/' . $propertyCode . '/' . $file->getClientOriginalName();
Storage::put('/images/' . $propertyCode . '/' . $file->getClientOriginalName(), file_get_contents($file));
}else{
$errorMessage = 'At least one file was not an image, check your results...';
}
}else{
$errorMessage = 'At least one file was not an image, check your results...';
}
}
Update 1:
Storage::put('/images/' . $propertyCode . '/' . $file->getClientOriginalName(), file_get_contents($file));
$img = Image::make($file);
Storage::put('/images/new/' . $file->getClientOriginalName(), $img);
This updated code outputs the files to the /new directory and all looks fine, but the output files have 'zero bytes'. What am I missing?
Update 2: Final code
The final answer (after using the proper code provided by contributors) was that:
I had to move my app from virtual box on to the dev machine (iMac) to prevent extra confusion with paths
The path for the images must exist prior to making the ->save()
The path variable must be set in advance of the ->save()
I don't need the Storage::put at all, so the larger file never ends up on the server.
Then this final code started to work.
$path = storage_path('app/smallpics/')."/".$file->getClientOriginalName();
$img = Image::make($file)->resize(300,200)->save($path);
Much thanks to all of you. You make my Laravel learning curve a bit less terrifiying!!
You can use Intervention to manipulate your image (resize etc.) as
$new_image = Image::make($file)->resize(300,200)->save('/path/to/save');
The image upload and resize work flow is like:
Upload the image from tmp to your directory.
Make a copy of that image by setting the height, width, quality and save it in the same or some other directory.
Delete the original image.
So as per your code flow:
Storage::put('/images/' . $propertyCode . '/' . $file->getClientOriginalName(), file_get_contents($file));
after this code, put the image compress code and after that delete the original image.
you can use Intervention or just use imagemagick convert command line command for resize or convert.
Pay attention to comments :
public function saveUploadPic(Request $request)
{
$pic = $request->file('<NAME_OF_FILE_INPUT_IN_HTML_FORM>');
#check for upload correctly
if(!$pic->isValid())
{
throw new Exception("IMAGE NOT UPLOADED CORRECTLY");
}
#check for mime type and extention
$ext = $pic->getClientOriginalExtension();
$mime = $pic->getMimeType();
if(!in_array($mime, $allowedMimeTypeArray) || !in_array($ext, $allowedExtArray))
{
throw new Exception("This Image Not Support");
}
#check for size
$size = $pic->getClientSize() / 1024 / 1024;
if($size > $allowedSize)
{
throw new Exception("Size Of Image Is More Than Support Size");
}
########################YOU HAVE TWO OPTION HERE###################
#1- save image in a temporary location with random hash for name if u need orginal image for other process
#below code save image in <LARAVEL_APP_PATH>/storage/app/tmp/pics/
$hash = md5(date("YmdHis").rand(1,10000));
$pic->storeAs('tmp/pics', $hash.'.'.$ext);
#Then resize or convert it
$img = Image::make(storage_path('app/tmp/pics/'.$hash.'.'.$ext))->resize(300, 200);
#save new image whatever u want
$img->save('<PATH_TO_SAVE_IMAGE>');
#after u finish with orginal image delete it
Storage::delete(storage_path('app/tmp/pics/'.$hash.'.'.$ext);
#2- Or just use below for resize and save image witout need to save in temporary location
$img = Image::make($pic->getRealPath())->resize(300,200);
$img->save('<PATH_TO_SAVE_IMAGE>');
}
if you want to use convert see this link.

How to encode the image file in PHP

i am using one file upload form field,from that field i want to encode the filename,don't to want to move the any temporary folder,directly i want to insert the database,while display the image directly fetch DB and display the website,I want encode like data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIA
$path = basename($_FILES['file']['name']);//here i am getting filename
$im = file_get_contents($path);//here i am getting false
$imdata = base64_encode($im); // so can't here encode the filename
$horoscope = array("encode" => $imdata,"path" =>$path,"getcontents" =>$im);
echo json_encode($horoscope);
PHP uploads the file into a temporary file and tells you where that is in the $_FILES['file']['tmp_name'] the $_FILES['file']['name'] holds the file name that the user called the file on their system, the one they select in the browser.
So if you want to grab the file before you have done a move_uploaded_file() on the file use $_FILES['file']['tmp']
$path = $_FILES['file']['tmp_name'];
$im = file_get_contents($path);
$imdata = base64_encode($im);
$horoscope = array("encode" => $imdata,
"path" =>$path,
"getcontents" =>$im
);
echo json_encode($horoscope);
I was not sure what you were passing back the $path for, so that may still need to be $_FILES['file']['name'].
$horoscope = array("encode" => $imdata,
"path" => $_FILES['file']['name'],
"getcontents" => $im
);
RE:Comment 1
I assume you want to store the base64encoded version of the file to the database so that will be $imdata
RE:Comment 2
To get the extension of the incoming file use pathinfo()
$path = $_FILES['file']['tmp_name'];
$file_parts = pathinfo($path);
$extn = $path_parts['extension'];
But beware, just because the extension says .png is not actual guarantee that it is a .png file

php return image from url

I want to return an image over an URL like http://placehold.it/500x500.
I have my URL http://example.inc/assets/image/35345, which calls an action on controller. The controller get some data (name, id, etc.) from database and also a binary string of the image content.
On the frontend site, i have my img tag, where i want to call the url in my src attribute.
<img src="http://example.inc/assets/image/35345">
Some more information, i use slim PHP Framework and my server is an ubuntu 13.x system (vagrant etc.). I am an typically frontend developer and dont have good skills # PHP.
Following snippets works:
$file = fopen($name, 'wb');
fwrite($file, $binaryData);
fclose($file);
but I dont want to generate files in a directory. Is this possible?
EDIT: Content-Type and Content-Length Headers are set, that is not the problem.
Grab the contents of the image, base_64 encode it, then return a a base64 image.
$file = file_get_contents($name);
list($width, $height, $type, $attr) = getimagesize($file);
echo '<img src="data:image/'.$type.';'.base64_encode($file).'"/>';
You should upload images in directory by using something like this. This code will upload your image in directory.
if ($_FILES['file']['name'] != "") {
$filename = $_FILES['file']['name']; //getting name of the file from form
$filesize = $_FILES['file']['size'];
$info = new SplFileInfo($filename);
$ext = $info->getExtension();
$filesize1 = ($filesize * .0009765625) * .0009765625;
if (!($ext == 'jpg' || $ext == 'png' || $ext == 'jpeg')) {
//set some error message and redirect
}
if ($filesize1 >= 5.0) {
//set message image size should be less than 5 mb
}
$target_path = $_SERVER['DOCUMENT_ROOT'] . "../images/profile_images/";
move_uploaded_file($_FILES['file']['tmp_name'], "$target_path" . $_FILES['file']['name']) or
die("Could not copy file!");
}
Insert image name(with extension) in database.($filename here)
Fetch image name from database and store in variable($profile_image here),use it in img src.
<a href='../images/profile_images/$profile_image'><img alt='Avatar' src='../images/profile_images/$profile_image'></a>
You can use only Anchor tag to redirect user on image in another tab in browser.
hope this answer will help you.
Because i had an mssql database with iso charset i have converted all of my results to utf-8, the problem was, that the bytestring also converted to utf-8.
after non converting the bytestring i also returned the bytestring and set the header content type to image/extension

restore base64_encode image to a file system from database

I'm saving some of my image in to mysql database using base64_encode.
now I want to restore them back to file system.
How can I do that?
Edit...!
Ok, I did not explain enough.
I use this code to encode my image and save them in to a blob table:
function base64_encode_image ($imagefile) {
$imgtype = array('jpg', 'gif', 'png');
$filename = file_exists($imagefile) ? htmlentities($imagefile) : die('Image file name does not exist');
$filetype = pathinfo($filename, PATHINFO_EXTENSION);
if (in_array($filetype, $imgtype)){
$imgbinary = fread(fopen($filename, "r"), filesize($filename));
} else {
die ('Invalid image type, jpg, gif, and png is only allowed');
}
return 'data:image/' . $filetype . ';base64,' . base64_encode($imgbinary);
}
and use this code to show my image in browser:
if (!isset($_GET['id']) && !ctype_digit($_GET['id'])){
die('Error');
} else {
require_once( addslashes(dirname(dirname(__FILE__)) . '/config.php') );
require_once( addslashes(dirname(__FILE__) . '/Functions.php'));
$row = mysql_fetch_array(mysql_query ("SELECT `id`,`cover_small` FROM `om_manga` WHERE `Active` = '1' AND `id` = '".sql_quote($_GET['id'])."'"));
if (isset($row['id'])){
header("Content-type: image/jpeg");
readfile($row['cover_small']);
} else {
die('Error');
}
}
Now i want them back to a jpg file.
The size of all those image are less then 3kb.
Use PHP's base64_decode() function to convert the encoded data back to binary.
Since base64_decode returns a string, you can use file_put_contents() to write the decoded contents to a file.
It makes me wonder why you're storing the image base64 encoded if you're not using it in that format. You could just as easily store the image in binary format in a binary blob column.
Base64 encoding adds a 33% character overhead (not bytes).
Edit for revised question
The answer to your second question is subjective without context. Without knowing the details of your system, I can't recommend whether you should extract the images.
Decode it the same way you encoded it...
base64_decode()
You might want to store the file extension of the image when writing it to the database so that you can restore it accurately. Just concatenate the new name with the existing extension.
What you should so is something similar to this :
// Retrieved values from database
$encodedImg = $sqlResult['encoded_img'];
$ext = $sqlResult['encoded_img_ext'];
// Concatenate new file name with existing extention
// NOTE : This parameter is a full path. Make sure that the folder
// you are writing the file to has the correct permissions allowing the
// script write access.
$newImagePath = "/some/path/on/the/servers/filesystem/";
$newImageName = $newImagePath."decoded_image.".$ext;
// Saving the decoded file with the new file name.
file_put_contents($newImageName, base64_decode($encodedImg));

Categories