 |
本特性可以使用户上传文本和二进制文件。用 PHP
的认证和文件操作函数,可以完全控制允许哪些人上传以及文件上传后怎样处理。
PHP 能够接受任何来自符合 RFC-1867 标准的浏览器(包括
Netscape Navigator 3 及更高版本,打了补丁的
Microsoft Internet Explorer 3 或者更高版本)上传的文件。
请注意 PHP 也支持 PUT 方法的文件上传,Netscape Composer
和 W3C 的 Amaya 客户端使用这种方法。请参阅对 PUT 方法的支持以获取更多信息。
例 38-1. 文件上传表单
可以如下建立一个特殊的表单来支持文件上传:
<!-- The data encoding type, enctype, MUST be specified as below -->
<form enctype="multipart/form-data" action="__URL__" method="POST">
<!-- MAX_FILE_SIZE must precede the file input field -->
<input type="hidden" name="MAX_FILE_SIZE" value="30000" />
<!-- Name of input element determines name in $_FILES array -->
Send this file: <input name="userfile" type="file" />
<input type="submit" value="Send File" />
</form> |
以上范例中的 __URL__ 应该被换掉,指向一个真实的 PHP 文件。
MAX_FILE_SIZE
隐藏字段(单位为字节)必须放在文件输入字段之前,其值为接收文件的最大尺寸。这是对浏览器的一个建议,PHP
也会检查此项。在浏览器端可以简单绕过此设置,因此不要指望用此特性来阻挡大文件。实际上,PHP
设置中的上传文件最大值是不会失效的。但是最好还是在表单中加上此项目,因为它可以避免用户在花时间等待上传大文件之后才发现文件过大上传失败的麻烦。
|
注意:
要确保文件上传表单的属性是
enctype="multipart/form-data",否则文件上传不了。
全局变量 $_FILES
自 PHP 4.1.0 起存在(在更早的版本中用
$HTTP_POST_FILES 替代)。此数组包含有所有上传的文件信息。
以上范例中 $_FILES
数组的内容如下所示。我们假设文件上传字段的名称如上例所示,为
userfile。名称可随意命名。
$_FILES['userfile']['name']
客户端机器文件的原名称。
$_FILES['userfile']['type']
文件的 MIME 类型,如果浏览器提供此信息的话。一个例子是“image/gif”。不过此
MIME 类型在 PHP 端并不检查,因此不要想当然认为有这个值。
$_FILES['userfile']['size']
已上传文件的大小,单位为字节。
$_FILES['userfile']['tmp_name']
文件被上传后在服务端储存的临时文件名。
$_FILES['userfile']['error']
和该文件上传相关的错误代码。此项目是在
PHP 4.2.0 版本中增加的。
文件被上传后,默认地会被储存到服务端的默认临时目录中,除非
php.ini 中的 upload_tmp_dir
设置为其它的路径。服务端的默认临时目录可以通过更改 PHP
运行环境的环境变量 TMPDIR 来重新设置,但是在
PHP 脚本内部通过运行 putenv()
函数来设置是不起作用的。该环境变量也可以用来确认其它的操作也是在上传的文件上进行的。
例 38-2. 使文件上传生效
请查阅函数 is_uploaded_file() 和 move_uploaded_file() 以获取进一步的信息。以下范例处理由表单提供的文件上传。
<?php // In PHP versions earlier than 4.1.0, $HTTP_POST_FILES should be used instead // of $_FILES.
$uploaddir = '/var/www/uploads/'; $uploadfile = $uploaddir . basename($_FILES['userfile']['name']);
echo '<pre>'; if (move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile)) { echo "File is valid, and was successfully uploaded.\n"; } else { echo "Possible file upload attack!\n"; }
echo 'Here is some more debugging info:'; print_r($_FILES);
print "</pre>";
?>
|
|
接受上传文件的 PHP
脚本为了决定接下来要对该文件进行哪些操作,应该实现任何逻辑上必要的检查。例如可以用
$_FILES['userfile']['size']
变量来排除过大或过小的文件,也可以通过
$_FILES['userfile']['type']
变量来排除文件类型和某种标准不相符合的文件,但只把这个当作一系列检查中的第一步,因为此值完全由客户端控制而在
PHP 端并不检查。自 PHP 4.2.0 起,还可以通过
$_FILES['userfile']['error'] 变量来根据不同的错误代码来计划下一步如何处理。不管怎样,要么将该文件从临时目录中删除,要么将其移动到其它的地方。
如果表单中没有选择上传的文件,则 PHP 变量
$_FILES['userfile']['size'] 的值将为
0,$_FILES['userfile']['tmp_name'] 将为空。
如果该文件没有被移动到其它地方也没有被改名,则该文件将在表单请求结束时被删除。
例 38-3. 上传一组文件
PHP 的 HTML 数组特性甚至支持文件类型。
<form action="" method="post" enctype="multipart/form-data">
<p>Pictures:
<input type="file" name="pictures[]" />
<input type="file" name="pictures[]" />
<input type="file" name="pictures[]" />
<input type="submit" value="Send" />
</p>
</form> |
<?php foreach ($_FILES["pictures"]["error"] as $key => $error) { if ($error == UPLOAD_ERR_OK) { $tmp_name = $_FILES["pictures"]["tmp_name"][$key]; $name = $_FILES["pictures"]["name"][$key]; move_uploaded_file($tmp_name, "data/$name"); } } ?>
|
|
v dot galyanin at gmail dot com
21-Jul-2007 10:37
I try to set up file uploading under IIS 7 and PHP 5.
First problem was to set 2 variables in php.ini
file_uploads = On
upload_tmp_dir = "C:\Inetpub\wwwroot\uploads"
For some reasons such directory name works,
but "upload_tmp" won't work.
The second problem was to set correct user rigths for upload folders where you try to save your file. I set my upload folder rights for the "WORKGROUP/users" for the full access. You may experiment by yourselves if you not need execute access, for example.
sogdiev at gmail dot com
10-Jul-2007 08:19
Hi there.
As far as I understand IE has his own MIME types based on the values stored in a registry. To locate this "feature" I spent a lot of time and was granted with a perfect headache. :)
In my case I tried to upload a CSV file on a server and abort a script in case if the corresponding file isn't of a desired type.
And it work fine with Opera and stuck with IE.
So the workaround is that you should add this values in your windows registry (I have windows xp box and it works fine for me)
[HKEY_CLASSES_ROOT\.csv]
"Content Type"="application/vnd.ms-excel"
@="Excel.CSV"
[HKEY_CLASSES_ROOT\.csv\Excel.CSV]
[HKEY_CLASSES_ROOT\.csv\Excel.CSV\ShellNew]
NB:
add the value "application/vnd.ms-excel" if you plan to open it with excel.
Richard Davey rich at corephp dot co dot uk
22-Jun-2007 12:05
Beware the mime-types! Given the GIF security issue that has been doing the rounds recently you may be inclined to validate an update based on its reported mime-type from the $_FILES array. However be careful with this - it is set by the *browser*, not by PHP or the web server, and browsers are not consistent (what's new?!)
For example IE6/7 will upload a progressive JPEG as image/pjpeg, while Firefox and Opera will upload it as image/jpeg. More importantly IE will try and determine the mime type of the file by actually inspecting its contents. For example if you rename a perfectly valid PNG file to end with .zip instead, IE will still send a mime type of image/x-png, where-as Firefox and Opera will send application/x-zip-compressed and application/zip respectively, even though the file is a valid PNG.
In short if you are going to validate an upload on the mime-type, be sure to do some careful research and testing first!
djcassis gmail com
30-May-2007 03:33
Here is the fastest way to retreive an integer size from a value coming from your php.ini file :
<?php
function ini_get_size($sName)
{
$sSize = ini_get($sName);
$sUnit = substr($sSize, -1);
$iSize = (int) substr($sSize, 0, -1);
switch (strtoupper($sUnit))
{
case 'Y' : $iSize *= 1024; // Yotta
case 'Z' : $iSize *= 1024; // Zetta
case 'E' : $iSize *= 1024; // Exa
case 'P' : $iSize *= 1024; // Peta
case 'T' : $iSize *= 1024; // Tera
case 'G' : $iSize *= 1024; // Giga
case 'M' : $iSize *= 1024; // Mega
case 'K' : $iSize *= 1024; // kilo
};
return $iSize;
}
// example
echo ini_get_size('post_max_size'); // e.g. 8388608 instead of 8M
?>
Let's just hope PHP will live long enough to see petabytes and yottabytes ;o)
Peter Clarke
25-May-2007 01:24
To clarify a point made by Brian AW:
"Turns out php stores the uploaded file in memory until you do something with it"
this is not true. The file is written to the tmp directory as it is uploaded (just keep checking your tmp directory whilst uploading to confirm this for yourself). I just uploaded an 111M file with php restricted to 8M of memory.
Chaos Mixed aka Chaosmixed
28-Apr-2007 12:22
To people with IIS and php on a windows XP machine:
Php.ini:
upload_tmp_dir = "C:\Inetpub\wwwroot\upload_tmp\"
then create two folder in the wwwroot directory:
C:\Inetpub\wwwroot\upload
C:\Inetpub\wwwroot\upload_tmp
Make sure sharing, web sharing and ntfs security, is ok
------------------------
upload form
<html>
<body>
<form action="uploader.php" method="post" enctype="multipart/form-data">
<label for="file">Filename:<label>
<input type="file" name="file" id="file" >
<br >
<input type="submit" name="submit" value="Submit" >
</form>
</body>
</html>
------------------------
the uploader
<?php
if ($_FILES["file"]["error"] > 0)
{
echo "Error: " . $_FILES["file"]["error"] . "<br />";
}
else
{
echo "Upload: " . $_FILES["file"]["name"] . "<br />";
echo "Type: " . $_FILES["file"]["type"] . "<br />";
echo "Size: " . ($_FILES["file"]["size"] / 1024) . " Kb<br />";
echo "Stored in: " . $_FILES["file"]["tmp_name"];
echo "<br>";
}
if (file_exists("/php/upload/" . $_FILES["file"]["name"]))
{
echo $_FILES["file"]["name"] . " already exists. ";
}
else
{
move_uploaded_file($_FILES["file"]["tmp_name"],
"upload/" . $_FILES["file"]["name"]);
echo "Stored in: " . "upload/" . $_FILES["file"]["name"];
}
?>
svenr at selfhtml dot org
23-Apr-2007 10:13
Clarification on the MAX_FILE_SIZE hidden form field:
PHP has the somewhat strange feature of checking multiple "maximum file sizes".
The two widely known limits are the php.ini settings "post_max_size" and "upload_max_size", which in combination impose a hard limit on the maximum amount of data that can be received.
In addition to this PHP somehow got implemented a soft limit feature. It checks the existance of a form field names "max_file_size" (upper case is also OK), which should contain an integer with the maximum number of bytes allowed. If the uploaded file is bigger than the integer in this field, PHP disallows this upload and presents an error code in the $_FILES-Array.
The PHP documentation also makes (or made - see bug #40387 - http://bugs.php.net/bug.php?id=40387) vague references to "allows browsers to check the file size before uploading". This, however, is not true and has never been. Up til today there has never been a RFC proposing the usage of such named form field, nor has there been a browser actually checking its existance or content, or preventing anything. The PHP documentation implies that a browser may alert the user that his upload is too big - this is simply wrong.
Please note that using this PHP feature is not a good idea. A form field can easily be changed by the client. If you have to check the size of a file, do it conventionally within your script, using a script-defined integer, not an arbitrary number you got from the HTTP client (which always must be mistrusted from a security standpoint).
v3 (&) sonic-world.ru
09-Mar-2007 10:29
As said before if POST size exceeds server limit then $_POST and $_FILES arrays become empty. You can track this using $_SERVER['CONTENT_LENGTH'].
For example:
<?php
$POST_MAX_SIZE = ini_get('post_max_size');
$mul = substr($POST_MAX_SIZE, -1);
$mul = ($mul == 'M' ? 1048576 : ($mul == 'K' ? 1024 : ($mul == 'G' ? 1073741824 : 1)));
if ($_SERVER['CONTENT_LENGTH'] > $mul*(int)$POST_MAX_SIZE && $POST_MAX_SIZE) $error = true;
?>
Brian AW
02-Mar-2007 09:24
I spent a good 2 hours today wondering why I could not upload files larger than 15MB even after upping my post_max_size, max_execution_time, max_input_time, and upload_max_filesize. Turns out php stores the uploaded file in memory until you do something with it. SO you also NEED to set the memory_limit to the correct file size.
Buzer at buzer dot net
22-Feb-2007 08:18
Fairly easy way to generate MAX_FILE_SIZE:
<?php
$maxsize = ini_get('upload_max_filesize');
if (!is_numeric($maxsize)) {
if (strpos($maxsize, 'M') !== false)
$maxsize = intval($maxsize)*1024*1024;
elseif (strpos($maxsize, 'K') !== false)
$maxsize = intval($maxsize)*1024;
elseif (strpos($maxsize, 'G') !== false)
$maxsize = intval($maxsize)*1024*1024*1024;
}
print '<input type="hidden" name="MAX_FILE_SIZE" value="'.$maxsize.'" />';
?>
jkuckartz1984 at hotmail dot com
31-Dec-2006 06:01
When uploading on a windows system and working with different partitions and different drive letters, uploading might not work correctly with the shown example.
The workaround is include the current operating directory in the target dir, as follows:
<?php
//you can delete the 'echo' commands and the '$temploc='-line
$currentdir=getcwd();
$target_path = $currentdir . "/2nddir/" . basename($_FILES['uploadedfile']['name']);
echo "Target: $target_path<br>";
$temploc=$_FILES['uploadedfile']['tmp_name'];
echo "Temploc: $temploc<br>";
if (move_uploaded_file($_FILES['uploadedfile']['tmp_name'], $target_path)) {
echo "The file ". basename( $_FILES['uploadedfile']['name']). " has been uploaded<br><br>";
} else {
echo "There was an error uploading the file, please try again!<br><br>";
}
?>
If the $currentdir is omitted and not used in $target_path:
Target: /2nddir/file.ext
Temploc: /ms4w/tmp/\php12C.tmp
Warning: move_uploaded_file(/ms4w/tmp/\php12C.tmp) [function.move-uploaded-file]: failed to open stream: No such file or directory in D:\Public\www\dir\test.php
Warning: move_uploaded_file() [function.move-uploaded-file]: Unable to move '/ms4w/tmp/\php12C.tmp' to '/2nddir/file.ext' in D:\Public\www\dir\test.php
There was an error uploading the file, please try again!
If it's written as the code shown above, it works perfectly:
Target: D:\Public\www\dir/2nddir/file.ext
Temploc: /ms4w/tmp/\php12C.tmp
The file file.ext has been uploaded
My initial thought the "\" in front of the temporary file was causing the problem. But don't bother about the "\", it does no harm!
jazfresh at hotmail.com
06-Dec-2006 07:46
Sample code for the upload progress extension below.
<?php
// upload_progress.php
// When the form is submitted, call this:
// window.open('/upload_progress.php?id=' + upload_identifier, 'Upload_Meter','width=370,height=115,status=no', true);
// Where 'upload_identifier' is the value of the hidden element UPLOAD_IDENTIFIER.
$id = $_GET['id'];
$body = false; // HTML body to display.
$onload = false; // Javascript to run when the page loads.
$refresh = false; // Number of seconds to wait before refreshing the page. false/0 means don't refresh.
$info = false; // Table of information to display
$url = htmlentities($_SERVER['PHP_SELF']).'?id='.$id.'&e=1'; // URL to redirect to.
$ul_info = uploadprogress_get_info($id);
if(!$ul_info) {
if(isset($_GET['e'])) {
$onload = 'window.close()';
$body = "Invalid upload meter ID!";
} else {
$refresh = 2; // Wait 2 seconds, give the server time to create the progress file.
$body = "Waiting for upload to begin.";
}
} else {
if($ul_info['bytes_total'] > 1 && $ul_info['bytes_uploaded'] >= $ul_info['bytes_total'] && $ul_info['est_sec'] == 0) {
$onload = 'window.close()';
$body = 'Upload complete.'; // They won't see this if the javascript runs, but just in case they've disabled it.
} else {
$body = "Uploading, please wait.";
$refresh = 1;
$percent_complete = $ul_info['bytes_uploaded'] * 100 / $ul_info['bytes_total'];
$kb_per_sec = $ul_info['speed_last'] / 1000;
$info = array(
'meter' => round($percent_complete, 2),
'width' => round($percent_complete),
'eta' => sprintf("%02d:%02d", $ul_info['est_sec'] / 60, $ul_info['est_sec'] % 60),
'speed' => round($kb_per_sec, ($ul_info['speed_last'] < 10000) ? 2 : 0),
'upl' => nice_value($ul_info['bytes_uploaded']),
'total' => nice_value($ul_info['bytes_total']),
);
}
}
// The HTML meter display should follow here.
?>
jazfresh at hotmail.com
06-Dec-2006 07:45
Now that PHP 5.2 supports a hook that can be used to handle upload progress, you can use a PECL extension (uploadprogress) to display an upload progress meter. Since documentation for this PECL extension is a bit thin on the ground, here are the basics:
1) Run "pecl install uploadprogress", and add "extension=uploadprogress.so" in your php.ini file.
2) Tell the extension where to temporarily store information about each upload. By default, this is in "/tmp/upt_%s.txt" (where %s is replaced with the UPLOAD_IDENTIFIER, see below). You can change it with the following config line:
uploadprogress.file.filename_template = /path/to/some_name_%s.txt
It must have a single '%s' in it, or it will fail!
3) Add a hidden element at the very beginning (this is important) of your upload form, called UPLOAD_IDENTIFIER. The value of this should be match the expression "^[A-Za-z0-9_.-=]{1,64}$" and be unique for every upload.
4) When the form is submitted, pop open a window to display the progress information and continue submitting the form. This window should refresh itself every few seconds, calling a PHP script that will retrieve the progress information and generate a display meter.
This script calls the function uploadprogress_get_info($id), where $id is the UPLOAD_IDENTIFIER value. This will return false if there is no progress information, or an array of values about that upload. The array contains:
time_start - The time that the upload began (same format as time()).
time_last - The time that the progress info was last updated.
speed_average - Average speed. (bytes / second)
speed_last - Last measured speed. (bytes / second)
bytes_uploaded - Number of bytes uploaded so far.
bytes_total - The value of the Content-Length header sent by the browser.
files_uploaded - Number of files uploaded so far.
est_sec - Estimated number of seconds remaining.
The speed_average is measured based on the number of bytes uploaded since the upload began, whereas the speed_last is based on the number of bytes uploaded since the progress information was last updated. The progress information is updated whenever PHP reads in some more data from the client, so speed_last may not be very accurate.
Note 1) The bytes_total figure is NOT a reflection of the file size, but of the POST's size, so don't expect them to match.
Note 2) This module really only detects how much data the POST form has sent, and keeps a running count of how many POST variables of type 'file' that it encounters along the way. It has no way of knowing how many files are in the POST, so it is not possible to have a progress bar for each file, just one for all files (and the ability to display how many files have been uploaded so far).
nebileyimya at gmail dot com
29-Nov-2006 09:29
i never work all above code
and i try this work fine
$file_name = $_FILES['file']['tmp_name'];
$localfile = file_get_contents($file_name);
$handle = fopen($_FILES['file']['name'],"w");
if(fwrite($handle,$localfile)>-1)
{
echo "done";
}
else
{
echo "error";
}
frustrated dot user at fake dot com
29-Nov-2006 05:11
Before you even bother doing anything, verify the filesystem your permanent upload directory will reside on. Use NTFS before you even start! (Assuming you're on Windows.)
Unfortunately, because I've been preparing to switch to Linux on this machine, I thought it would be a *great* idea to use FAT32 so I could access my websites from both Windows and Linux (this is not a production box, but where I am creating a CMS).
This was a *bad* idea...since the filesystem is not NTFS, now the only permissions I can set are *share* permissions.
I *guess* that *share* permissions are not used through the webserver, because even though the user Apache runs as is one of the users given permissions "Read" and "Modify" share permissions, the file cannot be permanently saved via neither move_uploaded_file() nor copy().
I verified this by using conditionals with !is_readable() and !is_writable() to echo specific messages.
So, now I have to back up everything I've done, reformat that partition as NTFS, copy everything over again, and set all the permissions, including on databases that I am using with other pages. *(sigh)*
rburdick at wAppearances dot com
23-Oct-2006 05:44
I have an application with a file upload page. I notice that if I enter garbage in the filename text box associated with the <input type="file"> element associated with my multipart upload form, the form does not get submitted when the submit button is clicked. There is no custom form validation JavaScript code associated with the form. Can anyone tell me if multipart forms do any kind of default validation of their own?
Robert
jedi_aka at yahoo dot com
18-Oct-2006 07:12
For those of you trying to make the upload work with IIS on windows XP/2000/XP Media and alike here is a quick todo.
1) Once you have created subdirectories "uploads/" in the same directory wher you code is running use the code from oportocala above and to make absolutely sure sure that the file you are trying to right is written under that folder. ( I recomend printing it using echo $uploadfile; )
2) In windows explorer browse to the upload directory created above and share it. To do that execute the following substeps.
a) Right click the folder click "sharing and security..."
b) Check 'Share this folder on the network'
c) Check 'Allow network users to change my files' ( THIS STEP IS VERY IMPORTANT )
d) click 'ok' or 'apply'
3) you can then go in the IIS to set read and write permissions for it. To do that execute the followin substeps.
a) Open IIS (Start/Controp Panel (classic View)/ Admistrative tools/Internet Information Service
b) Browse to your folder (the one we created above)
c) right click and select properties.
d) in the Directory tab, make sure, READ, WRITE, AND DIRECTORY BROWSING are checked.
e) For the security freaks out there, You should also make sure that 'execute permissions:' are set to Script only or lower (DO NOT SET IT TO 'script and executable)'( that is because someone could upload a script to your directory and run it. And, boy, you do not want that to happen).
there U go.
Send me feed back it if worked for you or not so that I can update the todo.
jedi_aka@yahoo.com
PS: BIG thanks to oportocala
tom dot blom at helsinki dot fi
04-Sep-2006 01:02
I have been upgrading a web server. The current configuration is IIS 6.0 + PHP 5.2 build 3790 with SSL support.
File uploads failed with a blank screen if the file size exceeded ca. 49 Kb. I tried modifying different timeout parameters (PHP and IIS) and the POST_MAX_SIZE and UPLOAD_MAX_FILESIZE directives. Modifications had no effect.
After turning off SSL in IIS uploads were successful. Some experimenting showed that the "Accept client certificates" -setting was causing the problem. Now I am running IIS with SSL and "Ignore client certificates" -setting. Even large files can now be uploaded.
I didn't found anything about this feature on the web.
chris[underscore]lewis[AT]bellsouth.net
17-Aug-2006 03:16
Remember that upload sizes are also affected by the setting <i>post_max_size</i>. If the file size, or more accurately the post size (file size + header data) is exceeded, PHP seems to just die. From what I've found so far, there doesn't even seem to be any entries to the apache log regarding the failure! So basically if you exceed the post size, a user will have no idea that anything failed. If anyone can expand on this, please do so!
my dot ma-ma at googlesucks dot com
14-Jul-2006 07:41
$userfile_name
is disallowed when register_globals is deactivated
ivan DOT cukic AT gmail DOT com
20-May-2006 12:47
There was a post earlier about not using the $_FILES['userfile']['type'] for verification and that a malicious PHP file could be hidden under the image/gif mime type.
That is correct, but it is not enough just to use getImageSize() or exif_imagetype() because php code can be hidden in the comment part of the picture.
The only way you can be sure it will not be executed is to make sure that the extension is not recognized by the server as a php script.
If you want a proof of concept, open The GIMP and create a 10x10px blank GIF image. When saving, just enter <? phpinfo() ?> as GIF comment. Rename the saved file from something.gif into the something.php and ask the server for it.
(And make sure that you NEVER call include() with a file that was uploaded)
david at cygnet dot be
12-May-2006 12:14
If you are experiencing problems posting files from Internet Explorer to a PHP script over an SSL connection, for instance "Page can not be displayed" or empty $_FILES and $_POST arrays (described by jason 10-Jan-2006 02:08), then check out this microsoft knowledgebase article:
http://support.microsoft.com/?kbid=889334
This knowledgebase article explains how since service pack 2 there may be problems posting from IE over SSL. It is worth checking whether your problem is IE specific since this is definitely not a PHP problem!
07-Apr-2006 07:28
If you're having a problem with filenames because of magic quotes, use stripslashes() to...strip slashes.
kweechang at yahoo dot com
07-Apr-2006 07:34
I have the same problem with filename contains single quote. Currently, I am using web-hosting services, hence I am not able to switch magic_quotes_gpc on/off.
After some study, I have discovered that the cause of the problem is in function basename();
For example:
$windows_filename = "C:\tmpdir\ling's document.doc"
with magic_quotes_gpc on
it becames
$windows_filename = "C:\\tmpdir\\ling\'s document.doc"
if you do a
echo basename($windows_filename)
the result will be
"s document.doc"
which is not correct, but this is exactly what we get from $_FILES['userfile']['name'];
To work around with this problem, the following solution might work.
Step 1:
a) Include a hidden field "userfilename" in your form.
b) add a javascript to the "file" input. To objective is to dupilcate the full filename and post it to the host.
<form name="imgform" method="post" action="upload.php" enctype="multipart/form-data">
<input type="hidden" name="userfilename" id="userfilename">
<input type="file" name="userfile" id="userfile" size="64" maxlength="256" onChange="javascript:top.document.imgform.userfilename.value= top.imgform.userfile.value">
</form>
Step 2:
a) extract the filename from the "userfilename"
$fileName = $_FILES['userfile']['name'];
$tmpName = $_FILES['userfile']['tmp_name'];
$fileSize = $_FILES['userfile']['size'];
$fileType = $_FILES['userfile']['type'];
$fp = fopen($tmpName, 'r');
$content = fread($fp, $fileSize);
$content = addslashes($content);
fclose($fp);
if(!get_magic_quotes_gpc())
{
$fileName = addslashes($fileName);
} else {
$fileName = addslashes(basename(stripslashes($_POST['userfilename'])));
}
chaz_meister_rock at yahoo dot com
09-Mar-2006 06:09
As mentioned in this thread by mariodivece at bytedive dot com on 24-Aug-2005 12:33:
"when using base64_encode to store binary data in a database, you are increasing the size of the data by 1.33 times."
so, if you are receiving the dreaded "MySQL Server has gone away" error when uploading BLOB data encoded w/base64, remember that your MySQL "max_allowed_packet" configuration setting (default 1MB) needs to by 1.33 times the size of your PHP "upload_max_filesize" setting.
brett dot r dot brown at gmail dot com
12-Jan-2006 11:01
In regards to geert dot php at myrosoft dot com about having the filenames cut off if single quotes are present in the filename, I had this same problem. The solution to this, is to set magic_quotes_gpc to off.
When magic_quotes_gpc is on, whenever a single quote is present in any _post data, a \ is inserted before any single quotes as an escape character. This comes in handy if you're storing the post data into mySQL, but causes problems, as you've had, when receiving an upload.
jason
10-Jan-2006 01:08
Regarding empty $_FILES and $_POST arrays when uploading files larger than post_max_size:
Tucked away in http://us3.php.net/manual/en/ini.core.php#ini.post-max-size is this nugget:
"If the size of post data is greater than post_max_size, the $_POST and $_FILES superglobals are empty. This can be tracked in various ways, e.g. by passing the $_GET variable to the script processing the data, i.e. <form action="edit.php?processed=1">, and then checking if $_GET['processed'] is set."
This may seem like a bug. You'd expect something like UPLOAD_ERR_FORM_SIZE to be set. But you just two empty superglobals.
I've seen it submitted to bugs.php.net twice and it's been marked as bogus both times.
Marcus
04-Jan-2006 03:11
Many ppl uses a java applet for uploading files instead of a forms post. This way you can get a progress bar etc.
If you for some reason cannot upload very large files you can use the java applet to upload it in chunks. I made a small change to the free JUpload applet hosted at sourceforge to do this.
If you in the url you're posting to add the argument
juchunksize=integervalue
the upload will be done as several posts where max-size of one part is integervalue. So you can keep track of where you are, two more parameters are added:
jupart, which is 0..
jufinal, which is 0 when there is more left or 1 if finished.
address to project:
http://sourceforge.net/projects/jupload
link to this JUpload version is here:
http://tinyurl.com/a2j7t
geert dot php at myrosoft dot com
23-Dec-2005 08:16
When file names do contain single quote parts of the filename are being lost.
eg.: uploading a filename
startName 'middlepart' endName.txt
will be uploaded (and hence stored in the _Files ['userfile'] variable as
endName.txt
skipping everything before the second single quote.
pete at NOSPAMpetesmithcomputers dot com
08-Dec-2005 01:03
I find I often have to handle photos in CMSs, so I wrote this class. It doubtless needs improvements, but it works pretty well.
<?php
class picture
{
var $save_dir; //where file will be saved
var $filename="spacer.gif"; //default file name initially
var $error_message=""; //string to be output if neccesary
var $width; //height of final image
var $height; //width of final image
function picture($save_directory, $file_array, $max_width, $max_height)
{
$this->save_dir = $save_directory;
$this->width = $max_width;
$this->height = $max_height;
//--change filename to time - make it unique
$temp_filename = $file_array['name'];
$ext = explode('.',$temp_filename);
$ext = $ext[count($ext)-1];
$temp_filename = time().".".$ext;
//--check that it's a jpeg or gif
if (preg_match('/^(gif|jpe?g)$/',$ext)) {
// resize in proportion
list($width_orig, $height_orig) = getimagesize($file_array['tmp_name']);
if ($this->width && ($width_orig < $height_orig)) {
$this->width = ($this->height / $height_orig) * $width_orig;
} else {
$this->height = ($this->width / $width_orig) * $height_orig;
}
$image_p = imagecreatetruecolor($this->width, $this->height);
//handle gifs and jpegs separately
if($ext=='gif'){
$image = imagecreatefromgif($file_array['tmp_name']);
imagecopyresampled($image_p, $image, 0, 0, 0, 0, $this->width, $this->height, $width_orig, $height_orig);
imagegif($image_p, $this->save_dir.$temp_filename, 80);
}
else
{
$image = imagecreatefromjpeg($file_array['tmp_name']);
imagecopyresampled($image_p, $image, 0, 0, 0, 0, $this->width, $this->height, $width_orig, $height_orig);
imagejpeg($image_p, $this->save_dir.$temp_filename, 80);
}
imagedestroy($image_p);
imagedestroy($image);
$this->filename=$temp_filename;
}else{
$this->error_message.="<br> file is not a jpeg or gif picture <br>";
}
}
}
?>
djot at hotmail dot com
27-Nov-2005 10:02
-
Be carefull with setting max_file_size via
ini_get('upload_max_filesize');
ini_get might return values like "2M" which will result in non working uploads.
This was the "no no" in my case:
$form = '<input type="hidden" name="MAX_FILE_SIZE" value=".ini_get('upload_max_filesize')." />';
Files were uploaded to the server, but than there was not any upload information, not even an error message. $_FILES was completly empty.
djot
-
info at giel berkers dot com
10-Nov-2005 12:03
I had problems when uploading files using Internet Explorer and Firefox. I checked for extension and mime-type to make sure it was a jpeg-file my users uploaded. Firefox did it well, only Internet Explorer failed to accept the file saying it wasn't the correct mime-type. After some testing, I discovered that Firefox reads the mime-type of a Jpeg-image as:
image/jpeg
While Internet Explorer reads it as:
image/pjpeg
I hope this helps somebody to avoid an evening debugging. To contribute to php.net, I enclosed my secure upload script for Jpeg files:
<?php
function storefile($var, $location, $filename=NULL, $maxfilesize=NULL) {
$ok = false;
// Check file
$mime = $_FILES[$var]["type"];
if($mime=="image/jpeg" || $mime=="image/pjpeg") {
// Mime type is correct
// Check extension
$name = $_FILES[$var]["name"];
$array = explode(".", $name);
$nr = count($array);
$ext = $array[$nr-1];
if($ext=="jpg" || $ext=="jpeg") {
$ok = true;
}
}
if(isset($maxfilesize)) {
if($_FILES[$var]["size"] > $maxfilesize) {
$ok = false;
}
}
if($ok==true) {
$tempname = $_FILES[$var]['tmp_name'];
if(isset($filename)) {
$uploadpath = $location.$filename;
} else {
$uploadpath = $location.$_FILES[$var]['name'];
}
if(is_uploaded_file($_FILES[$var]['tmp_name'])) {
while(move_uploaded_file($tempname, $uploadpath)) {
// Wait for the script to finish its upload
}
}
return true;
} else {
return false;
}
}
?>
You can use this script as follow:
<?
if(isset($_FILES['name'])) {
if(storefile("name", $_SERVER['DOCUMENT_ROOT']."test/img")) {
echo("Upload succeeded!");
} else {
echo("Upload failed...");
}
}
?>
The last 2 parameters 'filename' and 'maxfilesize' are optional. Offcourse, you can change this script with other mime types and extensions to fit your needs.
ohdotoh at randomnoisebursts dot com
10-Oct-2005 07:33
my quick and simple don't overwrite files that exist solution:
if (file_exists($sysfolder . '/' . $filename)) {
// append a digit to the beginning of the name
$tmpVar = 1;
while(file_exists($sysfolder . '/' . $tmpVar . '-' . $filename)) {
// and keep increasing it until we have a unique name
$tmpVar++;
}
$filename= $tmpVar . '-' . $filename;
}
ded at in dot ua
04-Oct-2005 09:03
Generally, if you use
CharsetDisable on
or
CharsetSourceEnc off
in (russian) apache config file, and if your script which receives upload still have some html, use <meta http-equiv ...> tags so browser can correctly display the pages.
NO_lewis_SPAM at NOSPAM dot delta-hf dot co dot uk
03-Sep-2005 04:27
I have been having issues with putting data in to an MSSQL database from an uploaded file. Trying to INSERT a file in excess of 25MB caused "Insufficient memory" errors from the SQL server
I decided to chunk the data into the database rather than trying to spurt it all in at once. The memory management is much better and from Submit to in the DB takes about 1 second per MB. The machine has dual Pentium 3 933MHz and 2GB RAM.
First things first I had to write a stored procedure. I saw no point in attempting to return a TEXTPTR(), which is required for the UPDATETEXT function to work, back to PHP so I didn't even bother. This is my very first stored procedure since this is really the first time I've developed solely for MSSQL. The important thing is that it's functional.
Here's the code I used for my stored procedure. After writing this I need to execute it in a loop to get all the data in, in the correct order. I leave that bit to PHP of course. Please don't tell me I could have just written it all in the stored procedure using another language, this is PHP and MSSQL we're talking about. :)
The zipfile column data type is TEXT, I tried IMAGE but it was problematic dealing with HEX data.
///////////// SP//////////////////////
CREATE PROCEDURE dbo.dds_writeBlob @dataChunk AS TEXT, @refCode AS VARCHAR(50), @offSet AS INT
AS
DECLARE @dataPtr AS BINARY(16)
SELECT @dataPtr=TEXTPTR(zipfile)
FROM [dbo].[file] (UPDLOCK)
WHERE [dbo].[file].ref = @refCode
UPDATETEXT [dbo].[file].zipfile @dataPtr @offSet 0 @dataChunk
GO
//////////////////END SP/////////////////
Then of course comes the PHP code to do the chunking. Firstly I have to convert the binary data to a type that can be accepted by TEXT data type. base64_encode() comes in handy for this purpose but of course I need it in chunks so I used chunk_split() and split it in to chunks of 256000 bytes by declaring the optional [chunklen]. I then explode() it in to a numerically indexed array using the newlines ("\r\n") that are inserted every [chunklen] by chunk_split. I can then loop through with a for(), passing the data chunks, in order, one at a time to the stored procedure. Here's the code:
<?
$data = chunk_split(base64_encode(fread(fopen($file, "rb"), filesize($file))), 256000);
$arrData = explode("\r\n", $data);
unset($data); // Clear memory space
$num = count($arrData);
$offset = 0;
for ($i=0;$i<$num;$i++) {
$buffer = $arrData[$i];
$query = "EXEC [dds_writeBlob] '".$buffer."', '$reference', $offset";
@mssql_query($query) or die(mssql_get_last_message());
$offset = ($offset + strlen($buffer));
}
?>
Of course we then need to extract the data from the database. This, thankfully, is a lot easier! Simply base64_decode() the data before outputting to a browser.
<?
$query = "SELECT zipfile, job_code FROM [file] WHERE ref = '$ref'";
@$result = mssql_query($query) or die('File Download: Failed to get file from the database.');
$file = mssql_fetch_assoc($result);
header('Content-type: application/zip');
header('Content-Disposition: attachment; filename="job-'.trim($file['job_code']).'.zip"');
echo base64_decode($file['zipfile']);
?>
The benefit of storing in base64_encoded format is the simplicity with which you can now send mime emails with the data attached as an alternative to having people download it. A simple chunk_split() on the SELECTed data will have it in the right format for mime mails.
The draw back is the extra size required! Expect the base64_encoded data to be 1.33 times the size of the original data!
mariodivece at bytedive dot com
24-Aug-2005 06:33
Just wanted to point out a detail that might be of interest to some:
when using base64_encode to store binary data in a database, you are increasing the size of the data by 1.33 times. There is a nicer way of storing the data directly. Try the following:
<?php $data = mysql_real_escape_string($data); ?>
This will leave the data untouched and formatted in the correct way and ready to be inserted right into a MySQL statement without wasting space.
By the way, I'd like to thank therebechips for his excellent advice on data chunks.
warwickbarnes at yahoo dot co dot uk
18-Aug-2005 11:58
You may come across the following problem using PHP on Microsoft IIS: getting permission denied errors from the move_uploaded_file function even when all the folder permissions seem correct. I had to set the following to get it to work:
1. Write permissions on the the folder through the IIS management console.
2. Write permissions to IUSR_'server' in the folder's security settings.
3. Write permissions to "Domain Users" in the folder's security settings.
The third setting was required because my application itself lives in a secure folder - using authentication (either Basic or Windows Integrated) to identify the users. When the uploads happen IIS seems to be checking that these users have write access to the folder, not just whether the web server (IUSR_'server') has access.
Also, remember to set "Execute Permissions" to "None" in the IIS management console, so that people can't upload a script file and then run it. (Other checks of the uploaded file are recommended as well but 'Execute None' is a good start.)
myko AT blue needle DOT com
16-Aug-2005 04:13
Just a quick note that there's an issue with Apache, the MAX_FILE_SIZE hidden form field, and zlib.output_compression = On. Seems that the browser continues to post up the entire file, even though PHP throws the MAX_FILE_SIZE error properly. Turning zlib compression to OFF seems to solve the issue. Don't have time to dig in and see who's at fault, but wanted to save others the hassle of banging their head on this one.
muoihv at 1yt dot net
04-Aug-2005 08:47
// Split file Submit and HTML post
<?
$num_of_uploads=3;
$file_types_array=array("JPG");
$max_file_size=1048576;
$upload_dir="D:\AppServ\www";
function uploaderFILES($num_of_uploads=1, $file_types_array=array("JPG"), $max_file_size=1048576, $upload_dir=""){
if(!is_numeric($max_file_size)){
$max_file_size = 1048576;
}
foreach($_FILES["file"]["error"] as $key => $value)
{
if($_FILES["file"]["name"][$key]!="")
{
if($value==UPLOAD_ERR_OK)
{
$origfilename = $_FILES["file"]["name"][$key];
$filename = explode(".", $_FILES["file"]["name"][$key]);
$filenameext = $filename[count($filename)-1];
unset($filename[count($filename)-1]);
$filename = implode(".", $filename);
$filename = substr($filename, 0, 15).".".$filenameext;
$file_ext_allow = FALSE;
for($x=0;$x<count($file_types_array);$x++){
if($filenameext==$file_types_array[$x])
{
$file_ext_allow = TRUE;
}
} // for
if($file_ext_allow){
if($_FILES["file"]["size"][$key]<$max_file_size){
if(move_uploaded_file($_FILES["file"]["tmp_name"][$key], $upload_dir.$filename)){
echo("File uploaded successfully. - <a href='".$upload_dir.$filename."' target='_blank'>".$filename."</a><br />");
}
else { echo('<font color="#FF0000">'.$origfilename."</font> was not successfully uploaded - khong the upload duoc <br />");}
}
else { echo('<font color="#FF0000">'.$origfilename."</font> was too big, not uploaded - Kich thuoc file qua' lon <br />"); }
} // if
else{ echo('<font color="#FF0000">'.$origfilename." </font>had an invalid file extension, not uploaded - File nay khong ton tai <br />"); }
}
else{ echo('<font color="#FF0000">'.$origfilename." </font>was not successfully uploaded - khong the upload duoc <br />"); } // else
}
}
} // funtion
/////////////////////////////////////////
?>
<form action='<?=$PHP_SELF;?>' method='post' enctype='multipart/form-data'>Upload files:<br /><input type='hidden' name='submitted' value='TRUE' id='<?=time();?>' >
<input type='hidden' name='MAX_FILE_SIZE' value='<?=$max_file_size;?>' >
<? for($x=0;$x<$num_of_uploads;$x++){
$form .= "<input type='file' name='file[]'><br />";
}
$form .= "<input type='submit' value='Upload'><br />
<font color='red'>*</font>Maximum file length (minus extension) is 15 characters. Anything over that will be cut to only 15 characters. Valid file type(s): ";
for($x=0;$x<count($file_types_array);$x++){
if($x<count($file_types_array)-1){
$form .= $file_types_array[$x].", ";
}else{
$form .= $file_types_array[$x].".";
}
}
echo($form);
?>
</form>
//////////////////////////////////////
<?
if(isset($_POST["submitted"])){
uploaderFILES($num_of_uploads, $file_types_array, $max_file_size, $upload_dir);
}
?>
dan at DLC2 dot com
01-Aug-2005 08:19
Are you struggling to implement these code samples for "file upload" or "multiple file upload"? Are you still scratching your head why they don't work??
I tried everything, until I realized that my version of PHP was too old! Look at these tidbits (collected from pages at PHP.NET for your enjoyment):
Requires PHP v4.1 - The $_FILES array
Requires PHP v4.2 - The "error" sub-array - example: $_FILES["userfile"]["error"]
Requires PHP v4.3 - The constants for "error" sub-array - example: UPLOAD_ERR_OK (value 0)
So, if you have PHP v4.0.X or earlier, use the $HTTP_POST_FILES variable and just do without the error sub-array and its constants. It still works good!
If you found this article useful, send praise via email. If this was a life-saver, send chocolate.
hakimu @ gmail dot com
24-Jul-2005 08:06
This is a response to bryan dot fillmer at beasleyallen dot com about renamig files to avoid ovewriting. it's simple, since the only problem is the extention, there are other ways of doing the same heres mine
// Where the file is going to be placed temporarly
$target_path = "/";
$target_path = $target_path . basename( $_FILES['uploadedfile']['name']);
$_FILES['uploadedfile']['tmp_name']; // temp file
$target_path = "upload/";
$oldfile = basename($_FILES['uploadedfile']['name']);
// getting the extention
$pos = strpos($oldfile,".",0);
$ext = trim(substr($oldfile,$pos+1,strlen($oldfile))," ");
//new file name exmaple for a profile image of a user
$newfile = $username . "." . $ext;
// move the file to the final destination
$target_path = $target_path . basename($newfile);
if(move_uploaded_file($_FILES['uploadedfile']['tmp_name'], $target_path)) {
echo "The file ". basename( $_FILES['uploadedfile']['name']). " has been uploaded";
} else{
echo "There was an error uploading the file, please try again!";
}
pvollma at pcvsoftware dot net
13-Jul-2005 06:47
Note that in Example 38-2 above, the basename() function will NOT extract the file name from an upload submitted by MS IE to a PHP program running on a *nix server! See my explanation in the notes for the basename() function.
suri @ suribala dot com
28-Jun-2005 04:31
A geneic fileupload class is included here....
<?php
// author: Suri Bala
// freely distributable
class fileupload{
private $upload_tmp_dir = "/tmp/"; // leading and trailing slash required
private $file_upload_flag = "off";
private $upload_max_filesize = "100";
private $allowable_upload_base_dirs = array("/tmp/", "/web/dynawolf/uploads/");
private $allowable_upload_tmp_dirs = array( "/tmp/");
private $upload_dir= "/tmp/"; // leading and trailing slash required
private $upload_file_name;
function __construct($name) {
if( is_null($_FILES[$name]) ) {
echo "Specified file <strong> ".$name." </strong> does not exist in the FILES array. Please check if it exists";
echo "Exiting...";
exit;
}
$this->getConfigurationSettings();
if( $this->file_upload_flag == "off" ) {
echo "File upload capability in the configuration file is turned <strong> off </strong> . Please update the php.ini file.";
exit;
}
$this->upload_file_name = $name;
}
private function getConfigurationSettings() {
$this->file_upload_flag = ini_get('file_uploads');
|