PHP get actual maximum upload size - php

When using
ini_get("upload_max_filesize");
it actually gives you the string specified in the php.ini file.
It is not good to use this value as a reference for the maximum upload size because
it is possible to use so-called shorthandbytes like 1M and so on which needs alot of additional parsing
when upload_max_filesize is for example 0.25M, it actually is ZERO, making the parsing of the value much harder once again
also, if the value contains any spaces like it is interpreted as ZERO by php, while it shows the value without spaces when using ini_get
So, is there any way to get the value actually being used by PHP, besides the one reported by ini_get, or what is the best way to determinate it?

Drupal has this implemented fairly elegantly:
// Returns a file size limit in bytes based on the PHP upload_max_filesize
// and post_max_size
function file_upload_max_size() {
static $max_size = -1;
if ($max_size < 0) {
// Start with post_max_size.
$post_max_size = parse_size(ini_get('post_max_size'));
if ($post_max_size > 0) {
$max_size = $post_max_size;
}
// If upload_max_size is less, then reduce. Except if upload_max_size is
// zero, which indicates no limit.
$upload_max = parse_size(ini_get('upload_max_filesize'));
if ($upload_max > 0 && $upload_max < $max_size) {
$max_size = $upload_max;
}
}
return $max_size;
}
function parse_size($size) {
$unit = preg_replace('/[^bkmgtpezy]/i', '', $size); // Remove the non-unit characters from the size.
$size = preg_replace('/[^0-9\.]/', '', $size); // Remove the non-numeric characters from the size.
if ($unit) {
// Find the position of the unit in the ordered string which is the power of magnitude to multiply a kilobyte by.
return round($size * pow(1024, stripos('bkmgtpezy', $unit[0])));
}
else {
return round($size);
}
}
The above functions are available anywhere in Drupal, or you can copy it and use it in your own project subject to the terms of the GPL license version 2 or later.
As for parts 2 and 3 of your question, you will need to parse the php.ini file directly. These are essentially configuration errors, and PHP is resorting to fallback behaviors. It appears you can actually get the location of the loaded php.ini file in PHP, although trying to read from it may not work with basedir or safe-mode enabled:
$max_size = -1;
$post_overhead = 1024; // POST data contains more than just the file upload; see comment from #jlh
$files = array_merge(array(php_ini_loaded_file()), explode(",\n", php_ini_scanned_files()));
foreach (array_filter($files) as $file) {
$ini = parse_ini_file($file);
$regex = '/^([0-9]+)([bkmgtpezy])$/i';
if (!empty($ini['post_max_size']) && preg_match($regex, $ini['post_max_size'], $match)) {
$post_max_size = round($match[1] * pow(1024, stripos('bkmgtpezy', strtolower($match[2])));
if ($post_max_size > 0) {
$max_size = $post_max_size - $post_overhead;
}
}
if (!empty($ini['upload_max_filesize']) && preg_match($regex, $ini['upload_max_filesize'], $match)) {
$upload_max_filesize = round($match[1] * pow(1024, stripos('bkmgtpezy', strtolower($match[2])));
if ($upload_max_filesize > 0 && ($max_size <= 0 || $max_size > $upload_max_filesize) {
$max_size = $upload_max_filesize;
}
}
}
echo $max_size;

Here is the full solution. It takes care of all traps like the shorthand byte notation and also considers post_max_size:
/**
* This function returns the maximum files size that can be uploaded
* in PHP
* #returns int File size in bytes
**/
function getMaximumFileUploadSize()
{
return min(convertPHPSizeToBytes(ini_get('post_max_size')), convertPHPSizeToBytes(ini_get('upload_max_filesize')));
}
/**
* This function transforms the php.ini notation for numbers (like '2M') to an integer (2*1024*1024 in this case)
*
* #param string $sSize
* #return integer The value in bytes
*/
function convertPHPSizeToBytes($sSize)
{
//
$sSuffix = strtoupper(substr($sSize, -1));
if (!in_array($sSuffix,array('P','T','G','M','K'))){
return (int)$sSize;
}
$iValue = substr($sSize, 0, -1);
switch ($sSuffix) {
case 'P':
$iValue *= 1024;
// Fallthrough intended
case 'T':
$iValue *= 1024;
// Fallthrough intended
case 'G':
$iValue *= 1024;
// Fallthrough intended
case 'M':
$iValue *= 1024;
// Fallthrough intended
case 'K':
$iValue *= 1024;
break;
}
return (int)$iValue;
}

This is what I use:
function asBytes($ini_v) {
$ini_v = trim($ini_v);
$s = [ 'g'=> 1<<30, 'm' => 1<<20, 'k' => 1<<10 ];
return intval($ini_v) * ($s[strtolower(substr($ini_v,-1))] ?: 1);
}

Looks like it isn't possible.
Because of this, I am going to continue using this code:
function convertBytes( $value ) {
if ( is_numeric( $value ) ) {
return $value;
} else {
$value_length = strlen($value);
$qty = substr( $value, 0, $value_length - 1 );
$unit = strtolower( substr( $value, $value_length - 1 ) );
switch ( $unit ) {
case 'k':
$qty *= 1024;
break;
case 'm':
$qty *= 1048576;
break;
case 'g':
$qty *= 1073741824;
break;
}
return $qty;
}
}
$maxFileSize = convertBytes(ini_get('upload_max_filesize'));
Originally from this helpful php.net comment.
STILL OPEN TO ACCEPT BETTER ANSWERS

I don't think so, at least not in the way you have defined it. There are so many other factors that come into consideration for maximum file upload size, most notably the connection speed of the user as well as the timeout setting for the web server as well as the PHP process(es).
A more useful metric for you might be to decide what is a reasonable maximum file size for the types of files you expect to receive for a given input. Make the decision on what is reasonable for your use case and set a policy around that.

Well you can always use this syntax, which will give you correct numbers from PHP ini file:
$maxUpload = (int)(ini_get('upload_max_filesize'));
$maxPost = (int)(ini_get('post_max_size'));
Mart

Related

Getting memory error with GD

i'm having trouble with a script i modified, i used this class https://github.com/thenakos/compare-images since i wanted to check if in a determined folder there were only uniques photos.
public function scanDir($d)
{
/*function to find same photos in a dir*/
$tabImg = array();
$bitsList = array();
if(is_dir($d))
{
$dir = opendir($d);
$i = 0;
while($file = readdir($dir))
{
$path_parts = pathinfo($file);
if($file != '.' && $file != '..' && isset($path_parts['extension']) && $path_parts['extension'] == 'jpg')
{
$tabImg[] = $file;
$i++;
}
}
}
$i=0;
foreach($tabImg as $keyA => $imgA)
{
if($i<700) {
if(file_exists($d.$imgA))
{
$i1 = $this->createImage($d.$imgA);
if(!$i1){return false;}
$i1 = $this->resizeImage($i1,$d.$imgA);
imagefilter($i1, IMG_FILTER_GRAYSCALE);
$colorMean1 = $this->colorMeanValue($i1);
$bits1 = $this->bits($colorMean1);
$bitsList[$keyA] = $bits1;
imagedestroy($i1);
$i++;
}
}
}
$bitsListToCompare = $bitsList;
foreach($bitsList as $keyList => $valueList)
{
foreach($bitsListToCompare as $keyListToCompare => $valueListToCompare)
{
if($keyList != $keyListToCompare)
{
$hammeringDistance = 0;
for($b = 0;$b<64;$b++)
{
if($valueList[$b] != $valueListToCompare[$b])
{
$hammeringDistance++;
}
}
if($hammeringDistance < 5)
{
if(isset($arraySame[$tabImg[$keyList]])) $arraySame[$tabImg[$keyList]] = $arraySame[$a[$keyList]].';'.$tabImg[$keyListToCompare]; else $arraySame[$tabImg[$keyList]] = $tabImg[$keyListToCompare];
}
}
}
unset($bitsListToCompare[$keyList]);
}
return $arraySame;
}
i've added this function wich basically returns an array of duplicates images. This way it works fine, i'm checking 700 images. But if i don't limit the number of photos to check, i'm getting an error.
Warning: getimagesize() [function.getimagesize]: Read error!
This error is about the following function ( getimagesize )
private function mimeType($i)
{
/*returns array with mime type and if its jpg or png. Returns false if it isn't jpg or png*/
$mime = getimagesize($i);
$return = array($mime[0],$mime[1]);
switch ($mime['mime'])
{
case 'image/jpeg':
$return[] = 'jpg';
return $return;
case 'image/png':
$return[] = 'png';
return $return;
default:
return false;
}
}
i think it's something about the memory but i don't know how to make it work !
Thanks
As for memory - this line seems suspicious:
$i1 = $this->resizeImage($i1,$d.$imgA);
I don't know what's inside resizeImage() but it could be that it takes one GD resource as first argument, doesn't destroy it and returns another GD resource. Reference to the new resource replaces reference to the old resource that stays in memory. While resource without references to it will be eventually freed by garbage collector, it's not guaranteed it will do it in time.
So I would do:
$i2 = $this->resizeImage($i1,$d.$imgA);
imagedestroy($i1);
But there may be simpler reason. As PHP manual states on getimagesize():
If accessing the filename image is impossible getimagesize() will generate an error of level E_WARNING. On read error, getimagesize() will generate an error of level E_NOTICE.
Then in changelog:
5.2.3 Read errors generated by this function downgraded to E_NOTICE from E_WARNING.
So perhaps some photo has permission issue or something like that?

How do you create a .gz file using PHP?

I would like to gzip compress a file on my server using PHP. Does anyone have an example that would input a file and output a compressed file?
This code does the trick
// Name of the file we're compressing
$file = "test.txt";
// Name of the gz file we're creating
$gzfile = "test.gz";
// Open the gz file (w9 is the highest compression)
$fp = gzopen ($gzfile, 'w9');
// Compress the file
gzwrite ($fp, file_get_contents($file));
// Close the gz file and we're done
gzclose($fp);
The other answers here load the entire file into memory during compression, which will cause 'out of memory' errors on large files. The function below should be more reliable on large files as it reads and writes files in 512kb chunks.
/**
* GZIPs a file on disk (appending .gz to the name)
*
* From http://stackoverflow.com/questions/6073397/how-do-you-create-a-gz-file-using-php
* Based on function by Kioob at:
* http://www.php.net/manual/en/function.gzwrite.php#34955
*
* #param string $source Path to file that should be compressed
* #param integer $level GZIP compression level (default: 9)
* #return string New filename (with .gz appended) if success, or false if operation fails
*/
function gzCompressFile($source, $level = 9){
$dest = $source . '.gz';
$mode = 'wb' . $level;
$error = false;
if ($fp_out = gzopen($dest, $mode)) {
if ($fp_in = fopen($source,'rb')) {
while (!feof($fp_in))
gzwrite($fp_out, fread($fp_in, 1024 * 512));
fclose($fp_in);
} else {
$error = true;
}
gzclose($fp_out);
} else {
$error = true;
}
if ($error)
return false;
else
return $dest;
}
UPDATE: Gerben has posted an improved version of this function that is cleaner and uses exceptions instead of returning false on an error. See https://stackoverflow.com/a/56140427/195835
Also, you could use php's wrappers, the compression ones. With a minimal change in the code you would be able to switch between gzip, bzip2 or zip.
$input = "test.txt";
$output = $input.".gz";
file_put_contents("compress.zlib://$output", file_get_contents($input));
change compress.zlib:// to compress.zip:// for zip compression (see comment to this answer about zip compression), or to compress.bzip2:// to bzip2 compression.
Simple one liner with gzencode():
gzencode(file_get_contents($file_name));
If you are looking to just unzip a file, this works and doesn't cause issues with memory:
$bytes = file_put_contents($destination, gzopen($gzip_path, r));
It's probably obvious to many, but if any of the program execution functions is enabled on your system (exec, system, shell_exec), you can use them to simply gzip the file.
exec("gzip ".$filename);
N.B.: Be sure to properly sanitize the $filename variable before using it, especially if it comes from user input (but not only). It may be used to run arbitrary commands, for example by containing something like my-file.txt && anothercommand (or my-file.txt; anothercommand).
Here's an improved version. I got rid of all the nested if/else statements, resulting in lower cyclomatic complexity, there's better error handling through exceptions instead of keeping track of a boolean error state, some type hinting and I'm bailing out if the file has a gz extension already. It got a little longer in terms of lines of code, but it's much more readable.
/**
* Compress a file using gzip
*
* Rewritten from Simon East's version here:
* https://stackoverflow.com/a/22754032/3499843
*
* #param string $inFilename Input filename
* #param int $level Compression level (default: 9)
*
* #throws Exception if the input or output file can not be opened
*
* #return string Output filename
*/
function gzcompressfile(string $inFilename, int $level = 9): string
{
// Is the file gzipped already?
$extension = pathinfo($inFilename, PATHINFO_EXTENSION);
if ($extension == "gz") {
return $inFilename;
}
// Open input file
$inFile = fopen($inFilename, "rb");
if ($inFile === false) {
throw new \Exception("Unable to open input file: $inFilename");
}
// Open output file
$gzFilename = $inFilename.".gz";
$mode = "wb".$level;
$gzFile = gzopen($gzFilename, $mode);
if ($gzFile === false) {
fclose($inFile);
throw new \Exception("Unable to open output file: $gzFilename");
}
// Stream copy
$length = 512 * 1024; // 512 kB
while (!feof($inFile)) {
gzwrite($gzFile, fread($inFile, $length));
}
// Close files
fclose($inFile);
gzclose($gzFile);
// Return the new filename
return $gzFilename;
}
Compress folder for anyone needs
function gzCompressFile($source, $level = 9)
{
$tarFile = $source . '.tar';
if (is_dir($source)) {
$tar = new PharData($tarFile);
$files = scandir($source);
foreach ($files as $file) {
if (is_file($source . '/' . $file)) {
$tar->addFile($source . '/' . $file, $file);
}
}
}
$dest = $tarFile . '.gz';
$mode = 'wb' . $level;
$error = false;
if ($fp_out = gzopen($dest, $mode)) {
if ($fp_in = fopen($tarFile, 'rb')) {
while (!feof($fp_in))
gzwrite($fp_out, fread($fp_in, 1024 * 512));
fclose($fp_in);
} else {
$error = true;
}
gzclose($fp_out);
unlink($tarFile);
} else {
$error = true;
}
if ($error)
return false;
else
return $dest;
}
copy('file.txt', 'compress.zlib://' . 'file.txt.gz');
See documentation

PHP: How do you determine if a file is a shortcut on Windows platforms?

Excluding any 3rd party extension not already bundled with PHP, Is there any method to determine if a given file on a Windows machine is a shortcut/link? The built-in function is_link works only on *nix platforms, so the following ran on a Windows machine will not return what might be expected:
$filePath = 'C:\somefile.lnk'; // path to shortcut file
var_dump(is_file($filePath)); // returns true
var_dump(is_link($filePath)); // returns false
This would cause problems when trying to work with the target file of the shortcut, and wind up operating on the shortcut file itself. The first four bytes of a shortcut seems to be typically 4c 00 00 00. But it doesn't seem reliable that this opening byte sequence is always limited to shortcuts.
Side question: how do you extract the target path from a shortcut file? (Note: readlink() seems to return the path of the shortcut file itself).
Edit: just to save some trouble, the mime-type of shortcut files according to PHP using finfo or mime_content_type is "application/octet-stream", which really won't help.
Just as fill answer. Wrote that years ago, not sure if it works with current Windows versions.
For checking the magic bytes just use:
$bin = file_get_contents("file.lnk", 2048);
if (substr($bin, 0, 20) == "L\000\000\000\001\024\002\000\000\000\000\000\300\000\000\000\000\000\000F") {
And if you want to extract the path(s):
function decode_windows_visual_shortcut($bin) {
# taken from "The Windows Shortcut File Format.pdf" V1.0 as
# reverse-engineered by Jesse Hager <jessehager#iname.com>
if (!defined("WIN_LNK_F_ITEMLIST")) {
define("WIN_LNK_F_ITEMLIST", 1);
define("WIN_LNK_F_FILE", 2);
define("WIN_LNK_F_DESC", 4);
define("WIN_LNK_F_RELATIVE", 8);
define("WIN_LNK_F_WORKDIR", 16);
define("WIN_LNK_F_CMDARGS", 32);
define("WIN_LNK_F_ICON", 64);
define("WIN_LNK_F2_DIR", 16);
function bread(&$bin, &$p, $bytes=4) {
$h = bin2hex( strrev($s = substr($bin, $p, $bytes)) );
$v = base_convert($h, 16, 10);
$p += $bytes;
return($v);
}
}
$res = array();
$p = 0x14;
$fl=$res["flags"] = bread($bin,$p);
$res["t_attr"] = bread($bin,$p);
$p = 0x4C;
if ($fl & WIN_LNK_F_ITEMLIST) {
#-- don't need this
$p += bread($bin,$p,2);
}
if ($fl & WIN_LNK_F_FILE) {
#-- File Location Info
$p0 = $p;
$p = $p0 + 0x10;
$p_path = $p0 + bread($bin,$p);
$p = $p0 + 0x18;
$p_file = $p0 + bread($bin,$p);
$path = substr($bin, $p_path, 704);
$path = substr($path, 0, strpos($path, "\000"));
$file = substr($bin, $p_file, 704);
$file = substr($file, 0, strpos($file, "\000"));
$res["path"] = $path;
$res["file"] = $file;
}
return($res);
}
http://code.google.com/p/8bits/downloads/detail?name=The_Windows_Shortcut_File_Format.pdf

PHP Image upload problem

I'm having trouble to get the following code to upload large images. It works great with images less than 1000px by 1000px, but breaks on anything bigger. Any help/ideas greatly appreciated.
Note: I have tried increasing the '$memoryNeeded>=10000000' to '7700000000' but still no joy.
if (!$error && is_uploaded_file($_FILES['galleryFile']['tmp_name'])) {
$format = strtolower(substr(strrchr($_FILES['galleryFile']['name'],"."),1));
$str = strtolower(trim($_FILES['galleryFile']['name']));
$str = preg_replace('/[^a-z0-9-]/', '-', $str);
$str = preg_replace('/-+/', "-", $str);
$filename=$str.'.'.$format;
$uploadGallery=$origFileDir.$filename;
foreach ($allowedImgFormats as $key => $value) {
$value==$format ? $imgFormatOK='1' : NULL;
}
$imgFormatOK=='0' ? $error='You are attempting to upload an image with an invalid format!<br />Please only upload images with ".gif", ".jpg" or ".jpeg" extensions.' : NULL;
if (!$error && move_uploaded_file($_FILES['galleryFile']['tmp_name'], $uploadGallery)){
$galleryW='944'; $galleryH='733';
$galleryInfo = getimagesize($uploadGallery);
$memoryNeeded = Round(($galleryInfo[0] * $galleryInfo[1] * $galleryInfo['bits'] * $galleryInfo['channels'] / 8 + Pow(2, 16)) * 1.65);
if ($memoryNeeded>=10000000) {
unlink($uploadGallery); $error='The chosen image is too large to process.<br />Please upload a smaller image (lower dimensions and/or resolution).';
} else {
list($wOrig, $hOrig) = getimagesize($uploadGallery);
$ratio_orig = $wOrig/$hOrig;
if ($wOrig > $galleryW) { $galleryW = $galleryH*$ratio_orig; $galleryH = $galleryW/$ratio_orig; } else { $galleryW=$wOrig; $galleryH=$hOrig; }
if ($galleryH > $galleryH) { $galleryH = $galleryW*$ratio_orig; $galleryW = $galleryH/$ratio_orig; }
$galleryP = imagecreatetruecolor($galleryW, $galleryH);
switch($format) {
case 'gif' : $thisGallery = imagecreatefromgif($uploadGallery); break;
case 'jpg' : $thisGallery = imagecreatefromjpeg($uploadGallery); break;
}
imagecopyresampled($galleryP, $thisGallery, 0, 0, 0, 0, $galleryW, $galleryH, $wOrig, $hOrig);
switch($format) {
case 'gif' : $createGallery=imagegif($galleryP, $galleryFileDir.$filename, 88); break;
case 'jpg' : $createGallery=imagejpeg($galleryP, $galleryFileDir.$filename, 88); break;
}
imagedestroy($galleryP); imagedestroy($thisGallery); unlink($uploadGallery);
if (!$createGallery) {
$error='The chosen image failed to transfer correctly.<br />Please try again, or attempt to upload an alternative image.';
file_exists($galleryFileDir.'/'.$filename) ? unlink($galleryFileDir.'/'.$filename) : NULL;
} else {
$_POST['imagename']=$filename;
mysql_query("INSERT INTO isgallery(imagename, assoc_object) VALUES('".$_POST['imagename']."', '".$_POST['id']."')");
}
}
} else {
!$error ? $error='The chosen image failed to upload correctly.<br />Please try again, or attempt to upload an alternative image.' : NULL;
file_exists($uploadGallery) ? unlink($uploadGallery) : NULL;
}
}
A 1000x1000 image requires at LEAST 3,000,000 bytes of memory if you're dealing with true color. and 4,000,000 if you're doing alpha transparency. Your $memoryNeeded variable is useless if it's set to something larger than PHP's memory_limit. It'll happily try to create an image and fail due to exceeded the limit.
You can check what the limit is with ini_get('memory_limit'), though you most likely won't be able to directly use this value for calculations without some massaging, as it'll likely return something like '32M' (32 megabyte limit), instead of 33554432.

Cakephp file upload problem

I am using Cakephp as my framework. I have a problem in uploading my files through a form. I am using an Uploader plugin from THIS website.
My php ini file has this piece of code.
upload_max_filesize = 10M
post_max_size = 8M
this is from uploader.php --> plugin file has
var $maxFileSize = '5M'; //default max file size
In my controller.php file, i use this code to set max file size as 1 MB at runtime.
function beforeFilter() {
parent::beforeFilter();
$this->Uploader->maxFileSize = '1M';
}
In the uploader.php, we perform this ...
if (empty($this->maxFileSize)) {
$this->maxFileSize = ini_get('upload_max_filesize'); //landmark 1
}
$byte = preg_replace('/[^0-9]/i', '', $this->maxFileSize);
$last = $this->bytes($this->maxFileSize, 'byte');
if ($last == 'T' || $last == 'TB') {
$multiplier = 1;
$execTime = 20;
} else if ($last == 'G' || $last == 'GB') {
$multiplier = 3;
$execTime = 10;
} else if ($last == 'M' || $last == 'MB') {
$multiplier = 5;
$execTime = 5;
} else {
$multiplier = 10;
$execTime = 3;
}
ini_set('memore_limit', (($byte * $multiplier) * $multiplier) . $last);
ini_set('post_max_size', ($byte * $multiplier) . $last); //error suspected here
ini_set('upload_tmp_dir', $this->tempDir);
ini_set('upload_max_filesize', $this->maxFileSize); //landmark 2
EXPECTED RESULT:
When i try uploading a file that is 2MB of size, it shouldn't take place because maxFileSize is 1MB at run time. So upload should fail.
THE PROBLEM IS :
But it is getting uploaded.
Landmark 1 does not get executed. (in comments)... land mark 2 does not seem to work...
upload_max_filesize does not get the value from maxFileSize.
Please help me... thank you
Setting upload_max_filesize during the script execution is rather pointless, since by the time the script executes the file is already uploaded and accepted by the server. If you need to reject the file based on size in your script (as opposed to Apache or PHP rejecting it), you need to evaluate the size of the uploaded file and "manually" ignore it if it's too big.
pointless or not it's not even possible to change upload_max_filesize with ini_set.
upload_max_filesize has the changable flagPHP_INI_PERDIR wich means Entry can be set in php.ini, .htaccess, httpd.conf or .user.ini (since PHP 5.3)
as a additional comment remember that post_max_size should be equal or greater then upload_max_filesize

Categories