章 38. 文件上传处理

POST 方法上传

本特性可以使用户上传文本和二进制文件。用 PHP 的认证和文件操作函数,可以完全控制允许哪些人上传以及文件上传后怎样处理。

PHP 能够接受任何来自符合 RFC-1867 标准的浏览器(包括 Netscape Navigator 3 及更高版本,打了补丁的 Microsoft Internet Explorer 3 或者更高版本)上传的文件。

相关的设置: 请参阅 php.inifile_uploadsupload_max_filesizeupload_tmp_dirpost_max_size 以及 max_input_time 设置选项。

请注意 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");
    }
}
?>

add a note add a note User Contributed Notes
rburdick at wAppearances dot com
24-Oct-2006 01: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 03: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 09: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.
steve at averageguysteve dot com
24-Aug-2006 04:36
Need some help.  I'm really new to php, but I'm trying to setup an upload page.  I ONLY want images to be allowed to upload.  I've used lots of script from this page already, and it works great, but after reading some comments I want to be sure no one can upload a script with a .jpg or .gif extension and cause any problems, however I can't figure out how to do that.  I've tried the getimagesize() and exif_imagetype() to verify that the file is truely an image, but I can't get either to work due to my total lack of experience with php.  I'm picking it up quick (only second day using it) and know html and css very well.  Below is my script, any help to exactly what code and where to put it would be MUCH apprecaited.  Thank you.

<?
$num_of_uploads
=1;
$file_types_array=array("jpg","gif");
$max_file_size=110000;
$upload_dir="images_public/";

function
uploaderFILES($num_of_uploads=1, $file_types_array=array("jpg","gif"), $max_file_size=110000, $upload_dir="images_public/"){
  if(!
is_numeric($max_file_size)){
  
$max_file_size = 110000;
  } 

$size = getimagesize($filename);
$fp=fopen($filename, "rb");
if (
$size && $fp) {
 
header("Content-type: {$size['mime']}");
 
fpassthru($fp);
  exit;
} else {
 
// error
}

  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;
        
if (
file_exists('images_public/' . $filename)) {
// append a digit to the beginning of the name
 
$tmpVar = 1;
  while(
file_exists('images_public/' . $tmpVar . '-' . $filename)) {
// and keep increasing it until we have a unique name
  
$tmpVar++;
   }
 
$filename= $tmpVar . '-' . $filename;
  }
        
         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(
"<center>File uploaded successfully. Your image can be found at <a href='".$upload_dir.$filename."' target='_blank'>".$upload_dir.$filename."</a><br /><br /><br /><img src='".$upload_dir.$filename."' border='0' alt=''></center>");
             }
             else { echo(
'<center><font color="#FF0000">'.$origfilename."</font> was not successfully uploaded.<br /></center>");}
           }
           else  { echo(
'<center><font color="#FF0000">'.$origfilename."</font> was too big and was not uploaded. Max file size is 100k!<br /></center>"); }
         }
// if
        
else{ echo('<center><font color="#FF0000">'.$origfilename." </font>had an invalid file extension and was not uploaded. Valid file types are .jpg or .gif.<br /></center>");  }
       }
       else{ echo(
'<center><font color="#FF0000">'.$origfilename." </font>was not successfully uploaded.<br /></center>");  } // else
    
}
  }
}

?>

<?
if(isset($_POST["submitted"])){
  
uploaderFILES($num_of_uploads, $file_types_array, $max_file_size, $upload_dir);
}
?>

*had to trim down the html code, but it works.
chris[underscore]lewis[AT]bellsouth.net
17-Aug-2006 11: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
15-Jul-2006 03:41
$userfile_name
is disallowed when register_globals is deactivated
ivan DOT cukic AT gmail DOT com
20-May-2006 08: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 08: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!
08-Apr-2006 03: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 03: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
10-Mar-2006 02: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
13-Jan-2006 07: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 09: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 11: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 04: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 09: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
28-Nov-2005 06: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 08: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
11-Oct-2005 03: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 05: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.
andrea at 3site dot it
21-Sep-2005 11:05
Hello, here is an experimental PHP / AJAX method to monitoring upload status.
http://www.devpro.it/upload_progress/

Thank you, PHP !!!
NO_lewis_SPAM at NOSPAM dot delta-hf dot co dot uk
04-Sep-2005 12: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!
Dave Koopman
25-Aug-2005 10:22
Upload progress bar by patching PHP source code.  Uses file or MySQL table to log progress.  MySQL is useful in load balanced situations. 

http://www.modphp.org/viewtopic.php?t=324
mariodivece at bytedive dot com
25-Aug-2005 02: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
19-Aug-2005 07: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
17-Aug-2005 12: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 04: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
02-Aug-2005 04: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 04: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
14-Jul-2005 02: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
29-Jun-2005 12: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');
    
$this->upload_tmp_dir = ini_get('upload_tmp_dir');
    
$this->upload_max_filesize = ini_get('upload_max_filesize');
    
$this->upload_max_filesize = preg_replace('/M/', '000000', $this->upload_max_filesize);
  }

 
public function getErrors() {
     return
$_FILES[$this->upload_file_name]['error'];
  }

 
public function getFileSize() {
     return
$_FILES[$this->upload_file_name]['size'];
  }

 
public function getFileName() {
     return
$_FILES[$this->upload_file_name]['name'];
  }

 
public function getTmpName() {
     return
$_FILES[$this->upload_file_name]['tmp_name'];
  }

 
public function setUploadDir($upload_dir) {
  
trim($upload_dir);
   if(
$upload_dir[strlen($upload_dir)-1] != "/" ) $upload_dir .= "/"; // add trailing slash
  
$can_upload = false;
   foreach(
$this->allowable_upload_base_dirs as $dir ) {
       if(
$dir == $upload_dir ) {
    
$can_upload = true;
         break;
       }
   }
   if( !
$can_upload ) {
       echo
"Cannot upload to the dir ->".$upload_dir;
       return;
   }else{
      
$this->upload_dir = $upload_dir;
       echo
$this->upload_dir;
   }
  }

 
public function setTmpUploadDir($upload_tmp_dir) {
  
trim($upload_tmp_dir);
   if(
$upload_tmp_dir[strlen($upload_tmp_dir)-1] != "/" ) $upload_tmp_dir .= "/"; // add trailing slash
  
$can_upload = false;
   foreach(
$this->allowable_upload_base_dirs as $dir ) {
       if(
$dir == $upload_tmp_dir ) {
    
$can_upload = true;
     return;
       }
   }
   if( !
$can_upload ) {
       echo
"Cannot upload to the dir ->".$uplaod_tmp_dir;
       return;
   }
  
$this->upload_tmp_dir = $upload_dir;
  }

 
public function uploadFile() {
   if(
$this->checkMaxMemorySizeLimit() ) {
       echo
"File size of ".$this->getFileSize()." greater than allowable limit of ".$this->upload_max_filesize."Please change the configuration setting.";
       return;
   }else{
     if( !
move_uploaded_file($this->getTmpName(), $this->upload_dir.$this->getFileName()) ) {
         echo
"Failed to upload file ".$this->getTmpName();
     }
   }
  }

 
public function checkMaxMemorySizeLimit() {
   if(
$this->getFileSize() >  $this->upload_max_filesize ) {
     return
true;
   }else{
     return
false;
   }
  }

}
sgarner(a)expio.co.nz
21-Jun-2005 12:52
Internet Explorer has problems with submitting forms with enctype="multipart/form-data" (e.g. uploading files) when combined with text fields containing non-standard ASCII characters (e.g. smart quotes or em-dashes) and a non-Windows-1255 character encoding.

Many Apache 2.0 default configs (e.g. Fedora 3) ship with "AddDefaultCharset UTF-8" in the httpd.conf. Commenting out this line will resolve the problem (assuming no charset is defined using <meta> tags in the page as well).

The problem manifests when submitting the form; the input field containing characters like , , ,  (common in text copied from MS Word) will appear blank to the receiving page.
keith at phpdiary dot org
24-May-2005 07:14
Caution: *DO NOT* trust $_FILES['userfile']['type'] to verify the uploaded filetype; if you do so your server could be compromised.  I'll show you why below:

The manual (if you scroll above) states: $_FILES['userfile']['type'] -  The mime type of the file, if the browser provided this information. An example would be "image/gif".

Be reminded that this mime type can easily be faked as PHP doesn't go very far in verifying whether it really is what the end user reported!

So, someone could upload a nasty .php script as an "image/gif" and execute the url to the "image".

My best bet would be for you to check the extension of the file and using exif_imagetype() to check for valid images.  Many people have suggested the use of getimagesize() which returns an array if the file is indeed an image and false otherwise, but exif_imagetype() is much faster. (the manual says it so)
20-May-2005 10:47
In case you want to show live upload progress bar or detect file size before upload completes this might help:

http://bluga.net/projects/uploadProgressMeter/
ceo at l-i-e dot com
20-May-2005 11:25
Using /var/www/uploads in the example code is just criminal, imnsho.

One should *NOT* upload untrusted files into your web tree, on any server.

Nor should any directory within your web tree have permissions sufficient for an upload to succeed, on a shared server. Any other user on that shared server could write a PHP script to dump anything they want in there!

The $_FILES['userfile']['type'] is essentially USELESS.
A. Browsers aren't consistent in their mime-types, so you'll never catch all the possible combinations of types for any given file format.
B. It can be forged, so it's crappy security anyway.

One's code should INSPECT the actual file to see if it looks kosher.

For example, images can quickly and easily be run through imagegetsize and you at least know the first N bytes LOOK like an image.  That doesn't guarantee it's a valid image, but it makes it much less likely to be a workable security breaching file.

For Un*x based servers, one could use exec and 'file' command to see if the Operating System thinks the internal contents seem consistent with the data type you expect.

I've had trouble in the past with reading the '/tmp' file in a file upload.  It would be nice if PHP let me read that file BEFORE I tried to move_uploaded_file on it, but PHP won't, presumably under the assumption that I'd be doing something dangerous to read an untrusted file.  Fine.  One should move the uploaded file to some staging directory.  Then you check out its contents as thoroughly as you can.  THEN, if it seems kosher, move it into a directory outside your web tree.  Any access to that file should be through a PHP script which reads the file.  Putting it into your web tree, even with all the checks you can think of, is just too dangerous, imnsho.

There are more than a few User Contributed notes here with naive (bad) advice.  Be wary.
Andrea_1
01-May-2005 12:47
I want to share a really easy way of uploading files to your web host from a form!

Someone suggested it way down in these notes. I tried it and it's working!!

Go to http://pear.php.net/index.php

Then go to:
http://pear.php.net/HTTP_Upload

You download the main pear.php files and then the HTTP_Upload file. Upload it to your web host and within minutes, the example will be working!

I'm going to look further on that pear.php.net site too - it looks like they have a lot of good addition for complex PHP functions!

Yahoo!
Steven
29-Apr-2005 07:30
Need to get around your PHP.ini file upload limit?
Use a bit of clever JavaScript to get the value of your <input type="file"> (the location of the file on the client's machine), copy it to a hidden text box then try and upload traditionally, if you get a PHP error UPLOAD_ERR_INI_SIZE, then use the value of your hidden text box to initiate a PHP-FTP connection and upload to your heart's content. 
No more limits :)
Sa_Estahbanati at yahoo dot com
27-Apr-2005 08:47
In the above notes there was solution to the server-side code that would use move_uploaded_file() to save the temporary file into a permanent path.
A pal maitaned that relative path would have problem in windows servers and you should indicate the complete path instead. While it is almost impossible to ge to know the exact path when you are not the host, I tried to test other ways and finally found out that a simple "." (dot) before tha path would solve the relative pathing problem.
instead of '/var/www/uploads/' or 'E:/sitebase/var/www/uploads/' you should type './var/www/uploads/'
Here is the complete source:
       <?php
          
// In PHP versions earlier than 4.1.0, $HTTP_POST_FILES should be used instead
           // of $_FILES.

           //$uploaddir = 'E:/sitebase/var/www/uploads/';
           //$uploaddir = '/var/www/uploads/';
          
$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>";

      
?>
dmsuperman at comcast dot net
27-Apr-2005 02:00
I needed a file uploader for a client a little while ago, then the client didn't want it, so I'll share with all of you. I know I hated coding it, it was confusing (for me anyway), but I made it fairly simple to use:

<?
function uploader($num_of_uploads=1, $file_types_array=array("txt"), $max_file_size=1048576, $upload_dir=""){
  if(!
is_numeric($max_file_size)){
  
$max_file_size = 1048576;
  }
  if(!isset(
$_POST["submitted"])){
  
$form = "<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[]'><font color='red'>*</font><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].".";
     }
   }
  
$form .= "</form>";
   echo(
$form);
  }else{
   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;
           }
         }
         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(
$origfilename." was not successfully uploaded<br />");
             }
           }else{
             echo(
$origfilename." was too big, not uploaded<br />");
           }
         }else{
           echo(
$origfilename." had an invalid file extension, not uploaded<br />");
         }
       }else{
         echo(
$origfilename." was not successfully uploaded<br />");
       }
     }
   }
  }
}
?>

uploader([int num_uploads [, arr file_types [, int file_size [, str upload_dir ]]]]);

num_uploads = Number of uploads to handle at once.

file_types = An array of all the file types you wish to use. The default is txt only.

file_size = The maximum file size of EACH file. A non-number will results in using the default 1mb filesize.

upload_dir = The directory to upload to, make sure this ends with a /

This functions echo()'s the whole uploader, and submits to itself, you need not do a thing but put uploader(); to have a simple 1 file upload with all defaults.
oportocala at DON'T GET SPAM dot yahoo dot comerce
22-Apr-2005 09:59
I had some problems with uploading files because of the path .So to save you some problems. if you get an error like "failed to open stream: No such file or directory in ..." it's likely because your not specifing the full path of the directory you want to upload.In other words you need to specify the full path and not the relative path in the second parameter.
Here is a small function to upload stuff. The files get their new name via last index from a database.
<?php
 
  $name_tmp
= $HTTP_POST_FILES['file']['tmp_name'];
//read temporay filename
 
$name = $HTTP_POST_FILES['file']['name'];
//read initial filename
 
if(is_uploaded_file($name_tmp)){
  
$ext =explode('.', $name);
  
$ext = $ext[count($ext)-1];
  
//get extension of uploaded file
      
$index=getLastIndex();//costum function
  
$path_parts = pathinfo(__file__);//get path info of curent php script
  
$dir = $path_parts['dirname']."\\uploads"; //full path of upload directory
  
$new = $dir."\\".$index.".".$ext;
     if(
move_uploaded_file($name_tmp,$new))
       echo
"File transfer succesfull.";
   }

?>
Jared
20-Apr-2005 10:07
If you want a unique filenames to prevent overwritting is to use sha1_file().

<?php
if ($_SERVER['REQUEST_METHOD'] == 'POST')
   {
       if (isset(
$_FILES['pic']))
       {
          
$file = $_FILES['pic'];

           if (
$file['error'] == UPLOAD_ERR_OK)
           {
              
$name = sha1_file($file['tmp_name']);
              
$pathInfo = pathinfo($file['name']);
               if (isset(
$pathInfo['extension']))
                  
$name .= '.'.$pathInfo['extension'];

               if (
move_uploaded_file($file['tmp_name'], $uploaddir.$name))
               {
                  
// ..ok..
              
}
               else
               {
                  
// ..failed..
              
}
           }
       }
   }
?>

Its unlikely 2 files will ever get the same filename, unless they are the same.
trog at swmud dot pl
12-Apr-2005 07:07
The best way of getting unique filenames, in order to
prevent accidental file overwriting during e.g. upload is
to use functions:
- uniqid
- time
- md5

It is not recommended to use just time() function.
The example code will explain.
The code:
<?php
  
for( $i = 0; $i < 10; $i++ )
       echo
'Time: '.time().' ID: '.md5(uniqid(time())).".\n";
?>

Example output:
Time: 1113310579 ID: 79955ae9bc455a33aed92de6b83e1a4a.
Time: 1113310579 ID: 45de8a2173b53c33659cafa1d564f49a.
Time: 1113310579 ID: 9377a0e655efd206ca5583b6530706cd.
Time: 1113310579 ID: 06a86b2c9678adb7f222984019a14c10.
Time: 1113310579 ID: 25fe1664af35ebb3347f04cf703be968.
Time: 1113310579 ID: dca7397eb4d7d5c44e34b0a16a6fc89e.
Time: 1113310579 ID: fa01b6a2a83fbaefd89306d27835101f.
Time: 1113310579 ID: cf20ada42f2248c3c8b67f46b1d97401.
Time: 1113310579 ID: 9ab1a24c7f576fe0e6add43b848660ac.
Time: 1113310579 ID: c6aa6e9e314b4392c94432ece411824e.

The ids are unique and this is what we want.
Now just append or prepend this id to the original filename and we
have unique filename every time.

Example:

<?php
   $unique_id
= md5(uniqid(time()));
  
$filename = $unique_id.'_'.$filename;
?>
bryan dot fillmer at beasleyallen dot com
04-Apr-2005 11:08
I looked high and low for a simple way to rename files as they are uploaded to prevent users from overwriting each others files. Below is the script I came up with.

<?php

  
foreach( $_FILES["screenshots"]["error"] as $key => $error ) {
  
       if(
$error == UPLOAD_ERR_OK ) {
          
$tmp_name = $_FILES["screenshots"]["tmp_name"][$key];
          
$original_name = $_FILES["screenshots"]["name"][$key];
          
$tmp_array = preg_split( "/\\\/",$tmp_name );
          
$file_name = $tmp_array[count($array)-1];
          
$extension = preg_replace( "/(.*)\.([a-z]+)$/","$2",$original_name );
          
$new_name = preg_replace( "/(.*)\.([a-z]+)$/","$1.$extension",$file_name );
          
move_uploaded_file( $tmp_name,"./screenshots/$new_name" );
       }
  
   }

?>

I started with the generic multiple file upload script in the documentation above and worked from there.
robpet at tds dot net
03-Apr-2005 02:35
People have remarked that incorrect permissions on the upload directory may prevent photos or other files from uploading.  Setting the Apache owner of the directory incorrectly will also prevent files from uploading -- I use a PHP script that creates a directory (if it doesn't exist already) before placing an uploaded file into it.  When the script creates the directory and then copies the uploaded file into the directory there is no problem because the owner of the file is whatever Apache is running as, typically "nobody". However, lets say that I've moved the site to a new server and have copied over existing file directories using FTP.  In this case the owner will have a different name from the Apache owner and files will not upload. The solution is to TelNet into the site and reset the owner to "nobody" or whatever Apache is running as using the CHOWN command.
javasri at yahoo dot com
31-Mar-2005 06:34
On windows XP, SP2, Explorer at times fails to upload files without extensions.

$_FILES array is null in that case. Microsoft says its a security feature(!)

The only solution we could comeup is to enforce uploaded file  to have an extention.
james [at] adultphpsolutions [dot] com
25-Mar-2005 04:21
Well let me ask you this?  Why would you compromise security doing this?  Might as well just turn off IP tables, you're basically allowing any connections to any port on your server, bad move.  I don't have those and have no problem uploading files. 

"The iptables rules for this are as follows:
# allow all fragments
-A INPUT -f -j ACCEPT
# allow icmp traffic
-A INPUT -p icmp -j ACCEPT" <-- From guy below me...

On the bright side, this will also fix some issues with ssh and NFS =)

On another note, a smart programmer would also do checking on the files that are uploaded, making sure that they are the correct file, it is another breach in security not by checking the files you are uploading are in a web viewable area.  Just my two cents...
thisisroot at gmail dot com
14-Mar-2005 11:33
If you're having problems uploading large files but small files go through fine, here are some things to try:

- In the HTML form, make sure you've set MAX_FILE_SIZE to an acceptable value.
- In php.ini, be sure you've set the the upload_max_filesize and post_max_size to be large enough to handle your upload.
- In your httpd.conf (if you're using apache), check that the LimitRequestBody directive isn't set too low (it's optional, so it may not be there at all).

If those settings don't work, you can check your firewall configuration. When uploading large files, packets have to be split into fragments of varying size depending on your systems MTU (maximum transmission unit), which is typically 1500 bytes.

Because some systems send the packets with the headers last (or the header packet may be received after some of the data packets), firewalls can't filter this traffic based on destination port and address. Many firewalls (including iptables) have to be configured to allow fragments separately from standard traffic. Unfortunately, it's an all-or-nothing thing in these cases, and exploits based on packet fragmentation have been a problem in the past (teardrop, boink, etc.). Note that ICMP may be used to notify the host (your server) of oncoming fragmentation, so you may need to allow ICMP traffic as well.

The iptables rules for this are as follows:
# allow all fragments
-A INPUT -f -j ACCEPT
# allow icmp traffic
-A INPUT -p icmp -j ACCEPT

On the bright side, this will also fix some issues with ssh and NFS =)
28-Feb-2005 02:06
Just to remind everyone, if you are wanting to upload larger files, you will need to change the value of both upload_max_filesize and post_max_size to the largest filesize you would like to allow.  Then restart apache and everything should work.
infoworks-tn dot com at david dot hamilton dot nospam
28-Feb-2005 12:52
Regarding the post by therebechips below about saving attachments to a MySQL database.  Here are some items that he forgot to mention:

Make sure your datafield is type text for storing the chunks in.  base64_encode translates binary into text (in a nutshell). 

Also, the max size for a text field is 65535.  In his example he used 50,000 for the MAX_SQL chunks to put into the database.  If you do this, the encoded data is too large to fit in the field and is truncated.  Use 40,000 instead to be safe.  It took me a few hours to realize why my images were coming back out of the database corrupted.

Other than that, his example worked very well. 
If you want to email me, flip the two parts of my email address around and remove .nospam

David Hamilton
Leevi at izilla dot com dot au
09-Feb-2005 02:52
This may help a newbie to file uploads.. it took advice from a friend to fix it..

If you are using
-windows xp
-iis 5
-php 5

If you keep getting permission errors on file uploads... and you have sworn you set the permissions to write to the directory in iis...

double check that
a) in windows explorer under tools > folder options
click the view tab
scroll down all the way to "use simple file sharing (recommended)"
uncheck this box

b) find the folder you wish to upload to on your server
c) click properties and then the security tab
d) make sure the appropriate write settings are checked.

you may want to test by setting "everyone" to have full permission....

BEWARE doing this will open up big security holes on your server....

hope this helps

Leevi Graham
phpnoob at adam dot net dot nz
09-Feb-2005 09:57
Just thought I'd add this since I had to search forever to find an answer. 

When I used the enctype attribute on a form to process a file upload I had a problem redirecting back to an anchor point on the original page my code looked like this on the upload page:

header("Location: original_page.php#image_gal");

and the resulting url looked like this:

http://www.url.com/original_page.php

note the missing anchor reference.  So my work around was to pass the anchor point as a variable and then redirect again when I got to the original page.  A little bit chunky but it worked.  Hope this helps someone.
Tyfud
08-Jan-2005 12:44
It's important to note that when using the move_uploaded_file() command, that some configurations (Especially IIS) will fail if you prefix the destination path with a leading "/". Try the following:

move_uploaded_file($tmpFileName,'uploads/'.$fileName);

Setting up permissions is also a must. Make sure all accounts have write access to your upload directory, and read access if you wish to view these files later. You might have to chmod() the directory or file afterwards as well if you're still getting access errors.
lobo235 at gmail dot com
05-Jan-2005 06:48
Be sure to be careful with the $_FILES['userfile']['name'] array element. If the client uploads a file that has an apostrophe in the filename it WILL NOT get set to the full name of the file from the client's machine.

For example, if the client uploads a file named george's car.jpg the $_FILES['userfile']['name'] element will be set to s car.jpg because PHP appears to cut off everything before the apostrophe as well as the apostrophe itself.

This did not happen in some of the previous versions of PHP but I know that it happens in version 4.3.10 so watch out for this.

I thought this was a bug so I submitted it but it turns out that it is a "security measure"
ryan dot baclit at gmail dot com
15-Dec-2004 03:15
Hello everyone. I want to share to you that uploading will never work out of the box if you didn't set the upload_tmp_dir directive in your php.ini file in the first place. If you just compiled the source files as is and tried to upload, you're in for a big mess. I don't know the flags to pass to the configure script to tell php about the default temporary directory to place the uploaded files.

In case your php upload code won't do as expected, open up the php.ini file and set the upload_tmp_dir. Then restart the Apache server and you're set.

By the way, I'm using Linux Mandrake 10.1 Official and PHP 4.3.9 on Apache 2.0.49.
captlid
08-Nov-2004 02:37
mime_content_type() is better to use if you want to find if a file sent is really a jpeg or a plain text file. :)
therhinoman at hotmail dot com
28-Aug-2004 04:20
If your upload script is meant only for uploading images, you can use the image function getimagesize() (does not require the GD image library) to make sure you're really getting an image and also filter image types.

<?php getimagesize($file); ?>

...will return false if the file is not an image or is not accessable, otherwise it will return an array...

<?php
$file
= 'somefile.jpg';

# assuming you've already taken some other
# preventive measures such as checking file
# extensions...

$result_array = getimagesize($file);

if (
$result_array !== false) {
  
$mime_type = $result_array['mime'];
   switch(
$mime_type) {
       case
"image/jpeg":
           echo
"file is jpeg type";
           break;
       case
"image/gif":
           echo
"file is gif type";
           break;
       default:
           echo
"file is an image, but not of gif or jpeg type";
   }
} else {
   echo
"file is not a valid image file";
}
?>

using this function along with others mentioned on this page, image ploading can be made pretty much fool-proof.

See http://php.net/manual/en/function.getimagesize.php for supported image types and more info.
olijon, iceland
19-Jun-2004 11:24
When uploading large images, I got a "Document contains no data" error when using Netscape and an error page when using Explorer. My server setup is RH Linux 9, Apache 2 and PHP 4.3.

I found out that the following entry in the httpd.conf file was missing:

<Files *.php>
  SetOutputFilter PHP
  SetInputFilter PHP
  LimitRequestBody 524288 (max size in bytes)
</Files>

When this had been added, everything worked smoothly.

- Oli Jon, Iceland
brion at pobox dot com
11-May-2004 09:08
Note that with magic_quotes_gpc on, the uploaded filename has backslashes added *but the tmp_name does not*. On Windows where the tmp_name path includes backslashes, you *must not* run stripslashes() on the tmp_name, so keep that in mind when de-magic_quotes-izing your input.
steve dot criddle at crd-sector dot com
17-Apr-2004 02:43
IE on the Mac is a bit troublesome.  If you are uploading a file with an unknown file suffix, IE uploads the file with a mime type of "application/x-macbinary".  The resulting file includes the resource fork wrapped around the file.  Not terribly useful.

The following code assumes that the mime type is in $type, and that you have loaded the file's contents into $content.  If the file is in MacBinary format, it delves into the resource fork header, gets the length of the data fork (bytes 83-86) and uses that to get rid of the resource fork.

(There is probably a better way to do it, but this solved my problem):

<?php
if ($type == 'application/x-macbinary') {
   if (
strlen($content) < 128) die('File too small');
  
$length = 0;
   for (
$i=83; $i<=86; $i++) {
      
$length = ($length * 256) + ord(substr($content,$i,1));
         }
  
$content = substr($content,128,$length);
}
?>
hisham
02-Mar-2004 02:54
On a similar note to jim dot dam at sympatico dot ca 27-Feb-2002 09:13

Browsers intepret png upload type differently too eg.

print_r() output from Mozilla 1.6
Array ( [name] => eg1.png [type] => image/png [tmp_name] => /var/tmp/phpIJd4FL [error] => 0 [size] => 66614 )

print_r() output from IE 6
Array ( [name] => eg1.png [type] => image/x-png [tmp_name] => /var/tmp/phpHJ04Dh [error] => 0 [size] => 66614 )

Note the difference of image/png and image/x-png type intepretation of the same image file.

Further note:
http://www.cti.ecp.fr/documents/tests/png.html
~caetin~ ( at ) ~hotpop~ ( dot ) ~com~
11-Feb-2004 12:37
From the manual:

     If no file is selected for upload in your form, PHP will return $_FILES['userfile']['size'] as 0, and $_FILES['userfile']['tmp_name'] as none.

As of PHP 4.2.0, the "none" is no longer a reliable determinant of no file uploaded. It's documented if you click on the "error codes" link, but you need to look at the $_FILES['your_file']['error']. If it's 4, then no file was selected.
maya_gomez ~ at ~ mail ~ dot ~ ru
06-Feb-2004 09:20
when you upload the file, $_FILES['file']['name'] contains its original name converted into server's default charset.
if a name contain characters that aren't present in default charset, the conversion fails and the $_FILES['file']['name'] remains in original charset.

i've got this behavior when uploading from a windows-1251 environment into koi8-r. if a filename has the number sign "" (0xb9), it DOES NOT GET CONVERTED as soon as there is no such character in koi8-r.

Workaround i use:

<?php
if (strstr ($_FILES['file']['name'], chr(0xb9)) != "")
{
  
$_FILES['file']['name'] = iconv (
      
"windows-1251",
      
"koi8-r",
      
str_replace (chr(0xb9), "N.", $_FILES['file']['name']));
};
?>
srikanth at ideaworks3d dot com
20-Jan-2004 02:18
If the file is empty (0 bytes) it is treated as if no file
is uploaded. $_FILES['userfile']['tmp_name'] returns "none".
therebechips
07-Sep-2003 05:02
Re: Handling uploads and downloads of large files and storing in MySQL.

Use two tables to store data about the file and the file data itself. ***Important: to preserve the integrity of the data use base64_encode() NOT addslashes().

<?php
// Max packet size
  
define("MAX_SQL",50000);
  
$filehandle = fopen($tmp, "rb") or die( "Can't open file!" );
  
$query=    "INSERT INTO files (name, type, size) VALUES(".
            
$DB->quote($name).", ".
            
$DB->quote($type).", ".
            
$DB->quote($size).
            
")";

  
// Execute Query
  
$result = $DB->query($query);
  
$file_id = mysql_insert_id();

// Copy the binary file data to the filedata table in sequential rows each containing MAX_SQL bytes
// Your table should have an index set to auto_increment
// Store the file_id to identify the data fragments
  
while (!feof ($filehandle)) {
      
$data = base64_encode(fread($filehandle,MAX_SQL));
      
$query = "INSERT INTO filedata (file_id, data) VALUES($file_id,\"".$data."\")";
      
$result = $DB->query($query);
   }
  
fclose ($filehandle);
?>

Decode the data fragments and recombine them:
<?php
   $file_id
=$_GET ['file_id'];
  
$query ="select file_id, name, type, size from files where file_id='$file_id'";
  
$result = $DB->query($query);
  
$row= mysql_fetch_array ($result);
  
$type = $row ["type"];
  
$name = $row ["name"];
  
$size = $row ["size"];
  
$file_id = $row ["file_id"];

  
// get the file data
  
$query = "select id, data from filedata where file_id='$file_id' ORDER by id";
  
$result = $DB->query($query);

// decode the fragments and recombine the file
  
$data = "";
   while (
$row = mysql_fetch_array($result)) {
      
$data .= base64_decode($row ["data"]); 
   }
  
// output the file
  
header ("Content-type: $type");
  
header ("Content-length: $size");
  
header ("Content-Disposition: attachment; filename=$name");
  
header ("Content-Description: PHP Generated Data");
   echo
$data;
?>
e4c5 at raditha dot com
13-Aug-2003 05:22
Progress bar support has been a recurring theme in many a PHP mailing list over the years. You can find a free progress monitor component for PHP file uploads at http://sourceforge.net/projects/megaupload/

The advantage of this system is that you do not have to apply any patches to PHP to make use of it.
diegoful at yahoo dot com
25-Mar-2003 04:22
SECURITY CONSIDERATION: If you are saving all uploaded files to a directory accesible with an URL, remember to filter files not only by mime-type (e.g. image/gif), but also by extension. The mime-type is reported by the client, if you trust him, he can upload a php file as an image and then request it, executing malicious code.
I hope I am not giving hackers a good idea anymore than I am giving it to good-intended developers. Cheers.
garyds at miraclemedia dot ca
16-Mar-2003 10:12
As it has been mentioned above, Windows-based servers have trouble with the path to move the uploaded file to when using move_uploaded_file()... this may also be the reason copy() works and not move_uploaded_file(), but of course move_uploaded_file() is a much better method to use. The solution in the aforementioned note said you must use "\\" in the path, but I found "/" works as well. So to get a working path, I used something to the effect of:

"g:/rootdir/default/www/".$_FILES['userfile']['name']

...which worked like a charm.

I am using PHP 4.3.0 on a win2k server.

Hope this helps!
ov at xs4all dot nl
09-Mar-2003 11:08
This took me a few days to find out: when uploading large files with a slow connection to my WIN2K/IIS5/PHP4 server the POST form kept timing out at exactly 5 minutes. All PHP.INI settings were large enough to accomodate huge file uploads. Searched like hell with keywords like "file upload php timeout script" until I realised that I installed PHP as CGI and added that as a keyword. This was the solution:

To set the timeout value:
1. In the Internet Information Services snap-in, select the computer icon and open its property sheets.
2. Under Master Properties, select WWW Service, and then click the Edit button
3. Click the Home Directory tab.
4. Click the Configuration button.
5. Click the Process Options tab, and then type the timeout period in the CGI Script Timeout box.
mccorkle+php at devteam dot org
08-Jan-2003 03:59
To anyone that is trying to use values="foo" to set a default value in a input type of ``file'', I found this out from http://www.blooberry.com/indexdot/html/tagpages/i/inputfile.htm:

*  Internet Explorer, Netscape and Opera do not use the VALUE attribute as the default contents of the input area. Any default value set via HTML is not usable via scripting and the DOM as well (hence it is not listed as 'supported' in any of the browsers.) If a user enters text in the field however, that value is then reachable via the DOM as it normally would be for a normal INPUT field (via the .value property.) The reason for this behavior would presumably be to ensure the security/safety of users against malicious authors.

Tooke me a bit to find this, so I figured I'd share.
travis dot lewis at amd dot com
05-Dec-2002 04:58
If you we dumb like me you installed Redhat 8.0 and kept the default install of packages for Apache 2.0 and PHP4.2.2.  I could not upload any files larger than 512kB and all the php directorives were set to 32MB or higher.
memory_limit = 128M
post_max_size = 64M
upload_max_filesize = 32M

And my upload web page was set to 32MB as well:
<Form ID="frmAttach" Name="frmAttach" enctype="multipart/form-data" action="attachments.php" method="POST">
<input type="hidden" name="MAX_FILE_SIZE" value="33554432" />

However, the insiduous php.conf (/etc/httpd/conf.d/php.conf) file used by default RPM install of Redhat httpd has a LimitRequestBody set to 512kB ("524288" ).  Adjusting this to 32MB ("33554432") got things going for the larger files.  Here is my php.conf file in its entirety.  Hope this helps someone.  L8er.

#
# PHP is an HTML-embedded scripting language which attempts to make it
# easy for developers to write dynamically generated webpages.
#

LoadModule php4_module modules/libphp4.so

#
# Cause the PHP interpreter handle files with a .php extension.
#
<Files *.php>
   SetOutputFilter PHP
   SetInputFilter PHP
   LimitRequestBody 33554432
</Files>

#
# Add index.php to the list of files that will be served as directory
# indexes.
#
solja at gci dot net
04-May-2002 09:11
Just another way I found to keep an uploaded file from overwriting an already exisiting one - I prefix each uploaded file with time() timestamp. Something like:
$unique_id = time();

Then when I give the new name to move the file to, I use something like:
$unique_id."-".$filename

So I get a fairly unique filename each time a file is uploaded. Obviously, if two files are uploaded at once, both will have the same timestamp, but I don't worry too much about that. Hope this might help someone.
am at netactor dot NO_SPAN dot com
15-Mar-2002 02:20
Your binary files may be uploaded incorrectly if you use modules what recode characters. For example, for Russian Apache, you should use
<Files ScriptThatReceivesUploads.php>
CharsetDisable On
</Files>