I want to use PHP in GAE to upload file image files.
Before storing, I want to convert each file to JPEG and reduce it to thumbnail quality.
Using the following code (which fully works in a normal PHP environment, less the bucket-specific adjustments), I am able to receive the uploads and determine the temporary file name and location, but getimagesize produces an error when attempting to access the CloudStorage.
$bucket = CloudStorageTools::getDefaultGoogleStorageBucketName();
$bucketPath = "gs://" . $bucket . "/" . $_SERVER["REQUEST_ID_HASH"] . "/";
$counter = 0;
foreach($_FILES["file"]["name"] as $idx => $tempFile) {
$counter++;
$sourceFile = $bucketPath . $tempFile;
syslog(LOG_DEBUG, $sourceFile);
$photoInfo = getimagesize($sourceFile);
if ($photoInfo["mime"] == "image/jpeg") {
$photoImage = imagecreatefromjpeg($sourceFile);
$valid = true;
}
elseif ($photoInfo["mime"] == "image/gif") {
$photoImage = imagecreatefromgif($sourceFile);
$valid = true;
}
elseif ($photoInfo["mime"] == "image/png") {
$photoImage = imagecreatefrompng($sourceFile);
$valid = true;
}
if (isset($valid)) {
$date = date("Y-m-d H:i:s");
$photoFolder = rtrim($photoFolder, "/") . "/";
$photoFile = "Test {$counter} {$date}.jpg";
$imageSaved = imagejpeg($photoImage, $photoFolder.$photoFile, 50);
syslog(LOG_DEBUG, "File saved is " . $imageSaved);
}
}
The first syslog entry confirms the file path and name...
gs://[myappid].appspot.com/AC3E3530/IMG_20160701_120144.jpg
The error log shows an error in attempting to open the stream, but I don't know how to address it.
PHP Warning: getimagesize(gs://[myappid].appspot.com/AC3E3530/IMG_20160701_120144.jpg): failed to open stream: "\google\appengine\ext\cloud_storage_streams\CloudStorageStreamWrapper::stream_open" call failed in /base/data/home/apps/s~[myappid]/v1.394746390020376247/code/server.php on line 169
I already have a variation of this functionality working on GAE with photos that my server receives through Twilio (where processPhoto() is a function identical to the code I excerpted above). In this case, I'm using getimagesize and imagecreate with a URL. I just don't know how to do the same with CloudStorage.
if ($fetch && $numMedia > 0) {
for ($x = 0; $x < $numMedia; $x++) {
$sourceFile = $_REQUEST["MediaUrl" . $x];
$sid = $_REQUEST["MessageSid"];
processPhoto("sms", $projectID, $sourceFile, $caption, $sid, $mobile, $message);
}
}
I think the problem was that the temporary file was removed before I could process it. So, I...
Removed the functionality of processing multiple files (which I
didn't need anyhow).
Immediately move the file to another bucket.
Examine the file for its type.
Save it as desired.
Remove the temporary file.
This is the form that I generate in PHP. There's no Submit button because I watch for a file change with jQuery.
<form id='form_uploadPhotos' method='post' enctype='multipart/form-data' action='{$websiteURL}?action=uploadPhotos'>
<input type='file' id='input_uploadPhoto' name='file'>
<input type='hidden' name='projectID' value='{$projectID}'>
</form>
This is the uploadPhotos function that's called when the form is submitted:
if ($action == "uploadPhotos") {
$projectID = preg_replace("/\D/", "", $_REQUEST["projectID"]);
$bucket = CloudStorageTools::getDefaultGoogleStorageBucketName();
$bucketPath = "gs://" . $bucket . "/" . $_SERVER["REQUEST_ID_HASH"] . "/";
$date = date("Y-m-d H:i:s");
$time = time();
$photoFile = sprintf("%08d", $projectID) . "." . $date . "." . $time . ".TEMP";
$sourceFile = $photoFolder.$photoFile; // The default photo folder is defined elsewhere.
move_uploaded_file($_FILES["file"]["tmp_name"], $sourceFile);
processPhoto("upload", $projectID, $sourceFile, null, null, null, null);
}
This is the function that processes the photo. It's called by other processes that also receive photos (e.g., SMS attachments via Twilio).
function processPhoto($via, $projectID, $sourceFile, $caption, $twilioMessageID, $smsMobile, $smsMessage) {
global $photoFolder;
$photoInfo = getimagesize($sourceFile);
if ($photoInfo["mime"] == "image/jpeg") {
$photoImage = imagecreatefromjpeg($sourceFile);
$valid = true;
}
elseif ($photoInfo["mime"] == "image/gif") {
$photoImage = imagecreatefromgif($sourceFile);
$valid = true;
}
elseif ($photoInfo["mime"] == "image/png") {
$photoImage = imagecreatefrompng($sourceFile);
$valid = true;
}
if (isset($valid)) {
$date = date("Y-m-d H:i:s");
$time = time();
$photoFile = sprintf("%08d", $projectID) . "." . $date . "." . $time . ".JPEG";
$photoImage = imagecreatefromjpeg($sourceFile);
list($width, $height) = getimagesize($sourceFile);
if (max($width, $height) > 800) {
$scale = 800/max($width, $height);
$newWidth = floor($width * $scale);
$newHeight = floor($height * $scale);
$saveImage = imagecreatetruecolor($newWidth, $newHeight);
imagecopyresampled($saveImage, $photoImage, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height);
}
$imageSaved = imagejpeg($photoImage, $photoFolder.$photoFile);
imagedestroy($photoImage);
if ($imageSaved) {
if (isset($twilioMessageID)) {
$twilioMediaID = substr($sourceFile, strrpos($sourceFile, "/") + 1);
purgeTwilioMedia($twilioMessageID, $twilioMediaID);
}
elseif (substr($sourceFile, strrpos($sourceFile, ".")) == ".TEMP") {
unlink($sourceFile);
}
<Additional processing (e.g., adding entry to database.)
.
.
.
}
}
}
Note: The image scaling code between "list($width, $height..." and "imagecopyresampled..." is based on Dano's answer to another question.
I'm guessing that you're hitting a limitation of the Standard Environment where the getimagesize native function in the Standard runtime doesn't work with file extensions like gs:// URLs.
I am making an image upload function which I can re-use in my code, which has to be 100% secure. Please tell me if you can spot and security holes in my initial code;
function Upload($file)
{
list($width,$height,$type,$attr) = getimagesize($file);
$mime = image_type_to_mime_type($type);
if(($mime != "image/jpeg") && ($mime != "image/pjpeg") && ($mime != "image/png"))
{
return 'Error3: Upload file type un-recognized. Only .JPG or .PNG images allowed';
}else{
$Newname = md5('sillysalt'.time());
if (move_uploaded_file($file, 'images/'.$Newname.$type))
{
return 'Uploaded!';
}else{
return 'Server Error!';
}
}
}
UPDATE This is how far I've gotten with your help and some research, please tell me what you think. I don't mind much about the speed, for me it's all about being 100% secure, or as close to.
function Upload($file)
{
list($width,$height,$type,$attr) = getimagesize($file);
$mime = image_type_to_mime_type($type);
$folder = 'images/';
// mime checks add a layer of security that keeps out less sophisticated attackers
if(($mime != "image/jpeg") && ($mime != "image/pjpeg") && ($mime != "image/png"))
{
return 'Error3: Upload file type un-recognized. Only .JPG or .PNG images allowed';
}else{
// If the file has no width its not a valid image
if(!$width)
{
$Newname = md5('sillysalt'.time());
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime2 = finfo_file($finfo, $folder.$Newname);
// Should I remove this second mime check? since the info comes form the same spoofable source in the image
if(($mime != "image/jpeg") && ($mime != "image/pjpeg") && ($mime != "image/png"))
{
$fileType = exif_imagetype($file);
$allowed = array(IMAGETYPE_JPEG, IMAGETYPE_PNG);
if(!in_array($fileType, $allowed))
{
// don't overwrite an existing file
$i = 0;
$parts = pathinfo($file);
while(file_exists($folder . $name))
{
$i++;
$name = $Newname."-".$i.".".$parts["extension"];
}
if(move_uploaded_file($file, $folder.$name))
{
// set good permissions for the file
chmod($name, 0644);
return 'Uploaded!';
}else{
return 'Server Error!';
}
}
}
}
}
}
As long as you don't use the FileInfo (http://www.php.net/manual/en/ref.fileinfo.php) extensions from php to check the mime type, your function is not secure at all (think later you'll want to upload pdf's, excels, etc).
Also, md5 over md5 does nothing than increasing the collision chances.
L.E: Something as simple as the following should do it:
function getExtensionToMimeTypeMapping() {
return array(
'ai'=>'application/postscript',
'aif'=>'audio/x-aiff',
'aifc'=>'audio/x-aiff',
'aiff'=>'audio/x-aiff',
'anx'=>'application/annodex',
'asc'=>'text/plain',
'au'=>'audio/basic',
'avi'=>'video/x-msvideo',
'axa'=>'audio/annodex',
'axv'=>'video/annodex',
'bcpio'=>'application/x-bcpio',
'bin'=>'application/octet-stream',
'bmp'=>'image/bmp',
'c'=>'text/plain',
'cc'=>'text/plain',
'ccad'=>'application/clariscad',
'cdf'=>'application/x-netcdf',
'class'=>'application/octet-stream',
'cpio'=>'application/x-cpio',
'cpt'=>'application/mac-compactpro',
'csh'=>'application/x-csh',
'css'=>'text/css',
'csv'=>'text/csv',
'dcr'=>'application/x-director',
'dir'=>'application/x-director',
'dms'=>'application/octet-stream',
'doc'=>'application/msword',
'drw'=>'application/drafting',
'dvi'=>'application/x-dvi',
'dwg'=>'application/acad',
'dxf'=>'application/dxf',
'dxr'=>'application/x-director',
'eps'=>'application/postscript',
'etx'=>'text/x-setext',
'exe'=>'application/octet-stream',
'ez'=>'application/andrew-inset',
'f'=>'text/plain',
'f90'=>'text/plain',
'flac'=>'audio/flac',
'fli'=>'video/x-fli',
'flv'=>'video/x-flv',
'gif'=>'image/gif',
'gtar'=>'application/x-gtar',
'gz'=>'application/x-gzip',
'h'=>'text/plain',
'hdf'=>'application/x-hdf',
'hh'=>'text/plain',
'hqx'=>'application/mac-binhex40',
'htm'=>'text/html',
'html'=>'text/html',
'ice'=>'x-conference/x-cooltalk',
'ief'=>'image/ief',
'iges'=>'model/iges',
'igs'=>'model/iges',
'ips'=>'application/x-ipscript',
'ipx'=>'application/x-ipix',
'jpe'=>'image/jpeg',
'jpeg'=>'image/jpeg',
'jpg'=>'image/jpeg',
'js'=>'application/x-javascript',
'kar'=>'audio/midi',
'latex'=>'application/x-latex',
'lha'=>'application/octet-stream',
'lsp'=>'application/x-lisp',
'lzh'=>'application/octet-stream',
'm'=>'text/plain',
'man'=>'application/x-troff-man',
'me'=>'application/x-troff-me',
'mesh'=>'model/mesh',
'mid'=>'audio/midi',
'midi'=>'audio/midi',
'mif'=>'application/vnd.mif',
'mime'=>'www/mime',
'mov'=>'video/quicktime',
'movie'=>'video/x-sgi-movie',
'mp2'=>'audio/mpeg',
'mp3'=>'audio/mpeg',
'mpe'=>'video/mpeg',
'mpeg'=>'video/mpeg',
'mpg'=>'video/mpeg',
'mpga'=>'audio/mpeg',
'ms'=>'application/x-troff-ms',
'msh'=>'model/mesh',
'nc'=>'application/x-netcdf',
'oga'=>'audio/ogg',
'ogg'=>'audio/ogg',
'ogv'=>'video/ogg',
'ogx'=>'application/ogg',
'oda'=>'application/oda',
'pbm'=>'image/x-portable-bitmap',
'pdb'=>'chemical/x-pdb',
'pdf'=>'application/pdf',
'pgm'=>'image/x-portable-graymap',
'pgn'=>'application/x-chess-pgn',
'png'=>'image/png',
'pnm'=>'image/x-portable-anymap',
'pot'=>'application/mspowerpoint',
'ppm'=>'image/x-portable-pixmap',
'pps'=>'application/mspowerpoint',
'ppt'=>'application/mspowerpoint',
'ppz'=>'application/mspowerpoint',
'pre'=>'application/x-freelance',
'prt'=>'application/pro_eng',
'ps'=>'application/postscript',
'qt'=>'video/quicktime',
'ra'=>'audio/x-realaudio',
'ram'=>'audio/x-pn-realaudio',
'ras'=>'image/cmu-raster',
'rgb'=>'image/x-rgb',
'rm'=>'audio/x-pn-realaudio',
'roff'=>'application/x-troff',
'rpm'=>'audio/x-pn-realaudio-plugin',
'rtf'=>'text/rtf',
'rtx'=>'text/richtext',
'scm'=>'application/x-lotusscreencam',
'set'=>'application/set',
'sgm'=>'text/sgml',
'sgml'=>'text/sgml',
'sh'=>'application/x-sh',
'shar'=>'application/x-shar',
'silo'=>'model/mesh',
'sit'=>'application/x-stuffit',
'skd'=>'application/x-koan',
'skm'=>'application/x-koan',
'skp'=>'application/x-koan',
'skt'=>'application/x-koan',
'smi'=>'application/smil',
'smil'=>'application/smil',
'snd'=>'audio/basic',
'sol'=>'application/solids',
'spl'=>'application/x-futuresplash',
'spx'=>'audio/ogg',
'src'=>'application/x-wais-source',
'step'=>'application/STEP',
'stl'=>'application/SLA',
'stp'=>'application/STEP',
'sv4cpio'=>'application/x-sv4cpio',
'sv4crc'=>'application/x-sv4crc',
'swf'=>'application/x-shockwave-flash',
't'=>'application/x-troff',
'tar'=>'application/x-tar',
'tcl'=>'application/x-tcl',
'tex'=>'application/x-tex',
'texi'=>'application/x-texinfo',
'texinfo'=>'application/x-texinfo',
'tif'=>'image/tiff',
'tiff'=>'image/tiff',
'tr'=>'application/x-troff',
'tsi'=>'audio/TSP-audio',
'tsp'=>'application/dsptype',
'tsv'=>'text/tab-separated-values',
'txt'=>'text/plain',
'unv'=>'application/i-deas',
'ustar'=>'application/x-ustar',
'vcd'=>'application/x-cdlink',
'vda'=>'application/vda',
'viv'=>'video/vnd.vivo',
'vivo'=>'video/vnd.vivo',
'vrml'=>'model/vrml',
'wav'=>'audio/x-wav',
'wrl'=>'model/vrml',
'xbm'=>'image/x-xbitmap',
'xlc'=>'application/vnd.ms-excel',
'xll'=>'application/vnd.ms-excel',
'xlm'=>'application/vnd.ms-excel',
'xls'=>'application/vnd.ms-excel',
'xlw'=>'application/vnd.ms-excel',
'xml'=>'application/xml',
'xpm'=>'image/x-xpixmap',
'xspf'=>'application/xspf+xml',
'xwd'=>'image/x-xwindowdump',
'xyz'=>'chemical/x-pdb',
'zip'=>'application/zip',
);
}
function getMimeType($filePath) {
if (!is_file($filePath)) {
return false;
}
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $filePath);
finfo_close($finfo);
return $mime;
}
function upload($filePath, $destinationDir = 'images', array $allowedMimes = array()) {
if (!is_file($filePath) || !is_dir($destinationDir)) {
return false;
}
if (!($mime = getMimeType($filePath))) {
return false;
}
if (!in_array($mime, $allowedMimes)) {
return false;
}
$ext = null;
$extMapping = getExtensionToMimeTypeMapping();
foreach ($extMapping as $extension => $mimeType) {
if ($mimeType == $mime) {
$ext = $extension;
break;
}
}
if (empty($ext)) {
$ext = pathinfo($filePath, PATHINFO_EXTENSION);
}
if (empty($ext)) {
return false;
}
$fileName = md5(uniqid(rand(0, time()), true)) . '.' . $ext;
$newFilePath = $destinationDir.'/'.$fileName;
if(!rename($filePath, $newFilePath)) {
return false;
}
return $fileName;
}
// use it
if (isset($_FILES['something']['tmp_name'])) {
$file = $_FILES['something']['tmp_name'];
$storagePath = 'images'; // this is relative to this script, better use absolute path.
$allowedMimes = array('image/png', 'image/jpg', 'image/gif', 'image/pjpeg');
$fileName = upload($file, $storagePath, $allowedMimes);
if (!$fileName) {
exit ('Your file type is not allowed.');
} else {
// check if file is image, optional, in case you allow multiple types of files.
// $imageInfo = #getimagesize($storagePath.'/'.$fileName);
exit ("Your uploaded file is {$fileName} and can be found at {$storagePath}/{$fileName}");
}
}
Stop filtering it by mime type it is not safe!
Client can send different mime types with different file extensions. So, you need to check file extension.
edit:
I think I have been misunderstood, I wrote the answer to tell that checking mime type to determine file type is not a good way, the best way to determine the file type is checking file extension. So, I don't mean that checking file extension is enough. Either checking only file extension or mime type is not safe way.
What to do?
1-Check mime type
2-Check file extension
3- decode file name
4- check file content consistency (if possible)
5- regenerate file content (if possible)
I know that attackers can bypass first and second way by using "null byte hack" and "mime type bypass"
So, 3,4 and 5 is so important for security.
I wrote a function to resize and upload images...it works, but only for one image. So if I call the function three times, I end up with 3 copies of the last image.....
function uploadImage($name,$width,$height,$size,$path='content/user_avatars/')
{
//===================================================
//Handle image upload
$upload_error=0;
//Picture
$img = $_FILES[$name]['name'];
if($img)
{
$file = stripslashes($_FILES[$name]['name']);
$ext = strtolower(getExt($file));
if($ext!='jpg' && $ext!='jpeg' && $ext!='png' && $ext!='gif')
{
$error_msg = "Unknown extension";
$upload_error = 1;
return array($upload_error,$error_msg);
}
if(filesize($_FILES[$name]['tmp_name'])>$size*1024)
{
$error_msg = "Max file size of ".($size*1024)."kb exceeded";
$upload_error = 2;
return array($upload_error,$error_msg);
}
$newFile = time().'.'.$ext;
resizeImg($_FILES[$name]['tmp_name'],$ext,$width,$height);
$store = copy($_FILES[$name]['tmp_name'],$path.$newFile);
if(!$store)
{
$error_msg = "Uploading failed";
$upload_error = 3;
return array($upload_error,$error_msg);
}
else
{
return array($upload_error,$newFile);
}
}
}
//=========================================================================================
//Helper Functions
function getExt($str)
{
$i = strpos($str,".");
if(!$i)
{
return "";
}
$l = strlen($str)-$i;
$ext = substr($str,$i+1,$l);
return $ext;
}
function resizeImg($file,$ext,$width,$height)
{
list($aw,$ah) = getimagesize($file);
$scaleX = $aw/$width;
$scaleY = $ah/$height;
if($scaleX>$scaleY)
{
$nw = round($aw*(1/$scaleX));
$nh = round($ah*(1/$scaleX));
}
else
{
$nw = round($aw*(1/$scaleY));
$nh = round($ah*(1/$scaleY));
}
$new_image = imagecreatetruecolor($nw,$nh);
imagefill($new_image,0,0,imagecolorallocatealpha($new_image,255,255,255,127));
if($ext=='jpg'||$ext=='jpeg')
{
$src_image = imagecreatefromjpeg($file);
}
else if($ext=='gif')
{
$src_image = imagecreatefromgif($file);
}
else if($ext=='png')
{
$src_image = imagecreatefrompng($file);
}
imagecopyresampled($new_image,$src_image,0,0,0,0,$nw,$nh,$aw,$ah);
if($ext=='jpg'||$ext=='jpeg')
{
imagejpeg($new_image,$file,100);
}
else if($ext=='gif')
{
imagegif($new_image,$file);
}
else if($ext=='png')
{
imagepng($new_image,$file,9);
}
imagedestroy($src_image);
imagedestroy($new_image);
}
I have a form with two upload fields, 'face_pic' and 'body_pic', and I want to upload these two to the server and resize them before storing. Any ideas?
You use the current time to determine the resulting name of the file. The function executes so fast, that time() yields the same result for both images.
Use some other means to disambiguate the resulting name of the file. Best choice would be to pass the resulting name as a parameter. That way, the environment can determine how the file is named. Candidates are primary key of the meta information (in case they are stored in the database), original file name, universally unique identifier.
In any case, check whether the resulting name is a legal filename on your platform and that you do not accidentally overwrite files.
Also consider using move_uploaded_file to move the file from the temporary location to the destination. That function does some security checking.
md5(microtime())
Try naming it like this.
Or try something like this...
function slikeAvatar($slika,$id = 0){
copy($slika, "avatari/{$id}l.jpg");
$gfx = new Thumbnail($slika, 200);
$gfx->save("avatari/{$id}.jpg");
unset($gfx);
$gfx = new Thumbnail($slika, 75);
$gfx->save("avatari/{$id}s.jpg");
unset($gfx);
slikeCrop("avatari/{$id}s.jpg","avatari/{$id}s.jpg");
}
slike = images