sync
[pear] / HTTP / Upload.php
1 <?php\r
2 // **********************************************\r
3 //\r
4 // This software is licensed by the LGPL\r
5 // -> http://www.gnu.org/copyleft/lesser.txt\r
6 // (c) 2001-2004 by Tomas Von Veschler Cox\r
7 //\r
8 // **********************************************\r
9 //\r
10 // $Id: Upload.php,v 1.42 2004/08/08 09:37:50 wenz Exp $\r
11 \r
12 /*\r
13  * Pear File Uploader class. Easy and secure managment of files\r
14  * submitted via HTML Forms.\r
15  *\r
16  * Leyend:\r
17  * - you can add error msgs in your language in the HTTP_Upload_Error class\r
18  *\r
19  * TODO:\r
20  * - try to think a way of having all the Error system in other\r
21  *   file and only include it when an error ocurrs\r
22  *\r
23  * -- Notes for users HTTP_Upload >= 0.9.0 --\r
24  *\r
25  *  Error detection was enhanced, so you no longer need to\r
26  *  check for PEAR::isError() in $upload->getFiles() or call\r
27  *  $upload->isMissing(). Instead you'll\r
28  *  get the error when do a check for $file->isError().\r
29  *\r
30  *  Example:\r
31  *\r
32  *  $upload = new HTTP_Upload('en');\r
33  *  $file = $upload->getFiles('i_dont_exist_in_form_definition');\r
34  *  if ($file->isError()) {\r
35  *      die($file->getMessage());\r
36  *  }\r
37  *\r
38  *  --\r
39  *\r
40  */\r
41 \r
42 require_once 'PEAR.php';\r
43 \r
44 /**\r
45  * defines default chmod\r
46  */\r
47 define('HTTP_UPLOAD_DEFAULT_CHMOD', 0660);\r
48 \r
49 /**\r
50  * Error Class for HTTP_Upload\r
51  *\r
52  * @author  Tomas V.V.Cox <cox@idecnet.com>\r
53  * @see http://vulcanonet.com/soft/index.php?pack=uploader\r
54  * @package HTTP_Upload\r
55  * @category HTTP\r
56  * @access public\r
57  */\r
58 class HTTP_Upload_Error extends PEAR\r
59 {\r
60     /**\r
61      * Selected language for error messages\r
62      * @var string\r
63      */\r
64     var $lang = 'en';\r
65 \r
66     /**\r
67      * Whether HTML entities shall be encoded automatically\r
68      * @var boolean\r
69      */\r
70     var $html = false;\r
71 \r
72     /**\r
73      * Constructor\r
74      *\r
75      * Creates a new PEAR_Error\r
76      *\r
77      * @param string $lang The language selected for error code messages\r
78      * @access public\r
79      */\r
80     function HTTP_Upload_Error($lang = null, $html = false)\r
81     {\r
82         $this->lang = ($lang !== null) ? $lang : $this->lang;\r
83         $this->html = ($html !== false) ? $html : $this->html;\r
84         $ini_size = preg_replace('/m/i', '000000', ini_get('upload_max_filesize'));\r
85 \r
86         if (function_exists('version_compare') &&\r
87             version_compare(phpversion(), '4.1', 'ge')) {\r
88             $maxsize = (isset($_POST['MAX_FILE_SIZE'])) ?\r
89                 $_POST['MAX_FILE_SIZE'] : null;\r
90         } else {\r
91             global $HTTP_POST_VARS;\r
92             $maxsize = (isset($HTTP_POST_VARS['MAX_FILE_SIZE'])) ?\r
93                 $HTTP_POST_VARS['MAX_FILE_SIZE'] : null;\r
94         }\r
95 \r
96         if (empty($maxsize) || ($maxsize > $ini_size)) {\r
97             $maxsize = $ini_size;\r
98         }\r
99         // XXXXX Add here error messages in your language\r
100         $this->error_codes = array(\r
101             'TOO_LARGE' => array(\r
102                 'es'    => "Fichero demasiado largo. El maximo permitido es: $maxsize bytes.",\r
103                 'en'    => "File size too large. The maximum permitted size is: $maxsize bytes.",\r
104                 'de'    => "Datei zu gro&szlig;. Die zul&auml;ssige Maximalgr&ouml;&szlig;e ist: $maxsize Bytes.",\r
105                 'nl'    => "Het bestand is te groot, de maximale grootte is: $maxsize bytes.",\r
106                 'fr'    => "Le fichier est trop gros. La taille maximum autoris&eacute;e est: $maxsize bytes.",\r
107                 'it'    => "Il file &eacute; troppo grande. Il massimo permesso &eacute: $maxsize bytes.",\r
108                 'pt_BR' => "Arquivo muito grande. O tamanho m&aacute;ximo permitido &eacute; $maxsize bytes."\r
109                 ),\r
110             'MISSING_DIR' => array(\r
111                 'es'    => 'Falta directorio destino.',\r
112                 'en'    => 'Missing destination directory.',\r
113                 'de'    => 'Kein Zielverzeichnis definiert.',\r
114                 'nl'    => 'Geen bestemmings directory.',\r
115                 'fr'    => 'Le r&eacute;pertoire de destination n\'est pas d&eacute;fini.',\r
116                 'it'    => 'Manca la directory di destinazione.',\r
117                 'pt_BR' => 'Aus&ecirc;ncia de diret&oacute;rio de destino.'\r
118                 ),\r
119             'IS_NOT_DIR' => array(\r
120                 'es'    => 'El directorio destino no existe o es un fichero regular.',\r
121                 'en'    => 'The destination directory doesn\'t exist or is a regular file.',\r
122                 'de'    => 'Das angebene Zielverzeichnis existiert nicht oder ist eine Datei.',\r
123                 'nl'    => 'De doeldirectory bestaat niet, of is een gewoon bestand.',\r
124                 'fr'    => 'Le r&eacute;pertoire de destination n\'existe pas ou il s\'agit d\'un fichier r&eacute;gulier.',\r
125                 'it'    => 'La directory di destinazione non esiste o &eacute; un file.',\r
126                 'pt_BR' => 'O diret&oacute;rio de destino n&atilde;o existe ou &eacute; um arquivo.'\r
127                 ),\r
128             'NO_WRITE_PERMS' => array(\r
129                 'es'    => 'El directorio destino no tiene permisos de escritura.',\r
130                 'en'    => 'The destination directory doesn\'t have write perms.',\r
131                 'de'    => 'Fehlende Schreibrechte f&uuml;r das Zielverzeichnis.',\r
132                 'nl'    => 'Geen toestemming om te schrijven in de doeldirectory.',\r
133                 'fr'    => 'Le r&eacute;pertoire de destination n\'a pas les droits en &eacute;criture.',\r
134                 'it'    => 'Non si hanno i permessi di scrittura sulla directory di destinazione.',\r
135                 'pt_BR' => 'O diret&oacute;rio de destino n&atilde;o possui permiss&atilde;o para escrita.'\r
136                 ),\r
137             'NO_USER_FILE' => array(\r
138                 'es'    => 'No se ha escogido fichero para el upload.',\r
139                 'en'    => 'You haven\'t selected any file for uploading.',\r
140                 'de'    => 'Es wurde keine Datei f&uuml;r den Upload ausgew&auml;hlt.',\r
141                 'nl'    => 'Er is geen bestand opgegeven om te uploaden.',\r
142                 'fr'    => 'Vous n\'avez pas s&eacute;lectionn&eacute; de fichier &agrave; envoyer.',\r
143                 'it'    => 'Nessun file selezionato per l\'upload.',\r
144                 'pt_BR' => 'Nenhum arquivo selecionado para upload.'\r
145                 ),\r
146             'BAD_FORM' => array(\r
147                 'es'    => 'El formulario no contiene method="post" enctype="multipart/form-data" requerido.',\r
148                 'en'    => 'The html form doesn\'t contain the required method="post" enctype="multipart/form-data".',\r
149                 'de'    => 'Das HTML-Formular enth&auml;lt nicht die Angabe method="post" enctype="multipart/form-data" '.\r
150                            'im &gt;form&lt;-Tag.',\r
151                 'nl'    => 'Het HTML-formulier bevat niet de volgende benodigde '.\r
152                            'eigenschappen: method="post" enctype="multipart/form-data".',\r
153                 'fr'    => 'Le formulaire HTML ne contient pas les attributs requis : '.\r
154                            ' method="post" enctype="multipart/form-data".',\r
155                 'it'    => 'Il modulo HTML non contiene gli attributi richiesti: "'.\r
156                            ' method="post" enctype="multipart/form-data".',\r
157                 'pt_BR' => 'O formul&aacute;rio HTML n&atilde;o possui o method="post" enctype="multipart/form-data" requerido.'\r
158                 ),\r
159             'E_FAIL_COPY' => array(\r
160                 'es'    => 'Fallo al copiar el fichero temporal.',\r
161                 'en'    => 'Failed to copy the temporary file.',\r
162                 'de'    => 'Tempor&auml;re Datei konnte nicht kopiert werden.',\r
163                 'nl'    => 'Het tijdelijke bestand kon niet gekopieerd worden.',\r
164                 'fr'    => 'L\'enregistrement du fichier temporaire a &eacute;chou&eacute;.',\r
165                 'it'    => 'Copia del file temporaneo fallita.',\r
166                 'pt_BR' => 'Falha ao copiar o arquivo tempor&aacute;rio.'\r
167                 ),\r
168             'E_FAIL_MOVE' => array(\r
169                 'es'    => 'No puedo mover el fichero.',\r
170                 'en'    => 'Impossible to move the file.',\r
171                 'de'    => 'Datei kann nicht verschoben werden.',\r
172                 'nl'    => 'Het bestand kon niet verplaatst worden.',\r
173                 'fr'    => 'Impossible de d&eacute;placer le fichier.',\r
174                 'pt_BR' => 'N&atilde;o foi poss&iacute;vel mover o arquivo.'\r
175                 ),\r
176             'FILE_EXISTS' => array(\r
177                 'es'    => 'El fichero destino ya existe.',\r
178                 'en'    => 'The destination file already exists.',\r
179                 'de'    => 'Die zu erzeugende Datei existiert bereits.',\r
180                 'nl'    => 'Het doelbestand bestaat al.',\r
181                 'fr'    => 'Le fichier de destination existe d&eacute;j&agrave;.',\r
182                 'it'    => 'File destinazione gi&agrave; esistente.',\r
183                 'pt_BR' => 'O arquivo de destino j&aacute; existe.'\r
184                 ),\r
185             'CANNOT_OVERWRITE' => array(\r
186                 'es'    => 'El fichero destino ya existe y no se puede sobreescribir.',\r
187                 'en'    => 'The destination file already exists and could not be overwritten.',\r
188                 'de'    => 'Die zu erzeugende Datei existiert bereits und konnte nicht &uuml;berschrieben werden.',\r
189                 'nl'    => 'Het doelbestand bestaat al, en kon niet worden overschreven.',\r
190                 'fr'    => 'Le fichier de destination existe d&eacute;j&agrave; et ne peux pas &ecirc;tre remplac&eacute;.',\r
191                 'it'    => 'File destinazione gi&agrave; esistente e non si pu&ograve; sovrascrivere.',\r
192                 'pt_BR' => 'O arquivo de destino j&aacute; existe e n&atilde;o p&ocirc;de ser sobrescrito.'\r
193                 ),\r
194             'NOT_ALLOWED_EXTENSION' => array(\r
195                 'es'    => 'Extension de fichero no permitida.',\r
196                 'en'    => 'File extension not permitted.',\r
197                 'de'    => 'Unerlaubte Dateiendung.',\r
198                 'nl'    => 'Niet toegestane bestands-extensie.',\r
199                 'fr'    => 'Le fichier a une extension non autoris&eacute;e.',\r
200                 'it'    => 'Estensione del File non permessa.',\r
201                 'pt_BR' => 'Extens&atilde;o de arquivo n&atilde;o permitida.'\r
202                 ),\r
203             'PARTIAL' => array(\r
204                 'es'    => 'El fichero fue parcialmente subido',\r
205                 'en'    => 'The file was only partially uploaded.',\r
206                 'de'    => 'Die Datei wurde unvollst&auml;ndig &uuml;bertragen.',\r
207                 'nl'    => 'Het bestand is slechts gedeeltelijk geupload.',\r
208                 'pt_BR' => 'O arquivo não foi enviado por completo.'\r
209                 ),\r
210             'ERROR' => array(\r
211                 'es'    => 'Error en subida:',\r
212                 'en'    => 'Upload error:',\r
213                 'de'    => 'Fehler beim Upload:',\r
214                 'nl'    => 'Upload fout:',\r
215                 'pt_BR' => 'Erro de upload:'\r
216                 ),\r
217             'DEV_NO_DEF_FILE' => array(\r
218                 'es'    => 'No está definido en el formulario este nombre de fichero como &lt;input type="file" name=?&gt;.',\r
219                 'en'    => 'This filename is not defined in the form as &lt;input type="file" name=?&gt;.',\r
220                 'de'    => 'Dieser Dateiname ist im Formular nicht als &lt;input type="file" name=?&gt; definiert.',\r
221                 'nl'    => 'Deze bestandsnaam is niett gedefineerd in het formulier als &lt;input type="file" name=?&gt;.'\r
222                 )\r
223         );\r
224     }\r
225 \r
226     /**\r
227      * returns the error code\r
228      *\r
229      * @param    string $e_code  type of error\r
230      * @return   string          Error message\r
231      */\r
232     function errorCode($e_code)\r
233     {\r
234         if (!empty($this->error_codes[$e_code][$this->lang])) {\r
235             $msg = $this->html ?\r
236                 html_entity_decode($this->error_codes[$e_code][$this->lang]) :\r
237                 $this->error_codes[$e_code][$this->lang];\r
238         } else {\r
239             $msg = $e_code;\r
240         }\r
241 \r
242         if (!empty($this->error_codes['ERROR'][$this->lang])) {\r
243             $error = $this->error_codes['ERROR'][$this->lang];\r
244         } else {\r
245             $error = $this->error_codes['ERROR']['en'];\r
246         }\r
247         return $error.' '.$msg;\r
248     }\r
249 \r
250     /**\r
251      * Overwrites the PEAR::raiseError method\r
252      *\r
253      * @param    string $e_code      type of error\r
254      * @return   object PEAR_Error   a PEAR-Error object\r
255      * @access   public\r
256      */\r
257     function raiseError($e_code)\r
258     {\r
259         return PEAR::raiseError($this->errorCode($e_code), $e_code);\r
260     }\r
261 }\r
262 \r
263 /**\r
264  * This class provides an advanced file uploader system\r
265  * for file uploads made from html forms\r
266 \r
267  *\r
268  * @author  Tomas V.V.Cox <cox@idecnet.com>\r
269  * @see http://vulcanonet.com/soft/index.php?pack=uploader\r
270  * @package  HTTP_Upload\r
271  * @category HTTP\r
272  * @access   public\r
273  */\r
274 class HTTP_Upload extends HTTP_Upload_Error\r
275 {\r
276     /**\r
277      * Contains an array of "uploaded files" objects\r
278      * @var array\r
279      */\r
280     var $files = array();\r
281     \r
282     /**\r
283      * Contains the desired chmod for uploaded files\r
284      * @var int\r
285      * @access private\r
286      */\r
287     var $_chmod = HTTP_UPLOAD_DEFAULT_CHMOD;\r
288 \r
289     /**\r
290      * Constructor\r
291      *\r
292      * @param string $lang Language to use for reporting errors\r
293      * @see Upload_Error::error_codes\r
294      * @access public\r
295      */\r
296     function HTTP_Upload($lang = null)\r
297     {\r
298         $this->HTTP_Upload_Error($lang);\r
299         if (function_exists('version_compare') &&\r
300             version_compare(phpversion(), '4.1', 'ge'))\r
301         {\r
302             $this->post_files = $_FILES;\r
303             if (isset($_SERVER['CONTENT_TYPE'])) {\r
304                 $this->content_type = $_SERVER['CONTENT_TYPE'];\r
305             }\r
306         } else {\r
307             global $HTTP_POST_FILES, $HTTP_SERVER_VARS;\r
308             $this->post_files = $HTTP_POST_FILES;\r
309             if (isset($HTTP_SERVER_VARS['CONTENT_TYPE'])) {\r
310                 $this->content_type = $HTTP_SERVER_VARS['CONTENT_TYPE'];\r
311             }\r
312         }\r
313     }\r
314 \r
315     /**\r
316      * Get files\r
317      *\r
318      * @param mixed $file If:\r
319      *    - not given, function will return array of upload_file objects\r
320      *    - is int, will return the $file position in upload_file objects array\r
321      *    - is string, will return the upload_file object corresponding\r
322      *        to $file name of the form. For ex:\r
323      *        if form is <input type="file" name="userfile">\r
324      *        to get this file use: $upload->getFiles('userfile')\r
325      *\r
326      * @return mixed array or object (see @param $file above) or Pear_Error\r
327      * @access public\r
328      */\r
329     function &getFiles($file = null)\r
330     {\r
331         static $is_built = false;\r
332         //build only once for multiple calls\r
333         if (!$is_built) {\r
334             $files = &$this->_buildFiles();\r
335             if (PEAR::isError($files)) {\r
336                 // there was an error with the form.\r
337                 // Create a faked upload embedding the error\r
338                 $this->files['_error'] =  new HTTP_Upload_File(\r
339                                                        '_error', null,\r
340                                                        null, null,\r
341                                                        null, $files->getCode(),\r
342                                                        $this->lang, $this->_chmod);\r
343             } else {\r
344                 $this->files = $files;\r
345             }\r
346             $is_built = true;\r
347         }\r
348         if ($file !== null) {\r
349             if (is_int($file)) {\r
350                 $pos = 0;\r
351                 foreach ($this->files as $obj) {\r
352                     if ($pos == $file) {\r
353                         return $obj;\r
354                     }\r
355                     $pos++;\r
356                 }\r
357             } elseif (is_string($file) && isset($this->files[$file])) {\r
358                 return $this->files[$file];\r
359             }\r
360             if (isset($this->files['_error'])) {\r
361                 return $this->files['_error'];\r
362             } else {\r
363                 // developer didn't specify this name in the form\r
364                 // warn him about it with a faked upload\r
365                 return new HTTP_Upload_File(\r
366                                            '_error', null,\r
367                                            null, null,\r
368                                            null, 'DEV_NO_DEF_FILE',\r
369                                            $this->lang);\r
370             }\r
371         }\r
372         return $this->files;\r
373     }\r
374 \r
375     /**\r
376      * Creates the list of the uploaded file\r
377      *\r
378      * @return array of HTTP_Upload_File objects for every file\r
379      */\r
380     function &_buildFiles()\r
381     {\r
382         // Form method check\r
383         if (!isset($this->content_type) ||\r
384             strpos($this->content_type, 'multipart/form-data') !== 0)\r
385         {\r
386             return $this->raiseError('BAD_FORM');\r
387         }\r
388         // In 4.1 $_FILES isn't initialized when no uploads\r
389         // XXX (cox) afaik, in >= 4.1 and <= 4.3 only\r
390         if (function_exists('version_compare') &&\r
391             version_compare(phpversion(), '4.1', 'ge'))\r
392         {\r
393             $error = $this->isMissing();\r
394             if (PEAR::isError($error)) {\r
395                 return $error;\r
396             }\r
397         }\r
398 \r
399         // map error codes from 4.2.0 $_FILES['userfile']['error']\r
400         if (function_exists('version_compare') &&\r
401             version_compare(phpversion(), '4.2.0', 'ge')) {\r
402             $uploadError = array(\r
403                 1 => 'TOO_LARGE',\r
404                 2 => 'TOO_LARGE',\r
405                 3 => 'PARTIAL',\r
406                 4 => 'NO_USER_FILE'\r
407                 );\r
408         }\r
409 \r
410 \r
411         // Parse $_FILES (or $HTTP_POST_FILES)\r
412         $files = array();\r
413         foreach ($this->post_files as $userfile => $value) {\r
414             if (is_array($value['name'])) {\r
415                 foreach ($value['name'] as $key => $val) {\r
416                     $err = $value['error'][$key];\r
417                     if (isset($err) && $err !== 0 && isset($uploadError[$err])) {\r
418                         $error = $uploadError[$err];\r
419                     } else {\r
420                         $error = null;\r
421                     }\r
422                     $name = basename($value['name'][$key]);\r
423                     $tmp_name = $value['tmp_name'][$key];\r
424                     $size = $value['size'][$key];\r
425                     $type = $value['type'][$key];\r
426                     $formname = $userfile . "[$key]";\r
427                     $files[$formname] = new HTTP_Upload_File($name, $tmp_name,\r
428                                                              $formname, $type, $size, $error, $this->lang, $this->_chmod);\r
429                 }\r
430                 // One file\r
431             } else {\r
432                 $err = $value['error'];\r
433                 if (isset($err) && $err !== 0 && isset($uploadError[$err])) {\r
434                     $error = $uploadError[$err];\r
435                 } else {\r
436                     $error = null;\r
437                 }\r
438                 $name = basename($value['name']);\r
439                 $tmp_name = $value['tmp_name'];\r
440                 $size = $value['size'];\r
441                 $type = $value['type'];\r
442                 $formname = $userfile;\r
443                 $files[$formname] = new HTTP_Upload_File($name, $tmp_name,\r
444                                                          $formname, $type, $size, $error, $this->lang, $this->_chmod);\r
445             }\r
446         }\r
447         return $files;\r
448     }\r
449 \r
450     /**\r
451      * Checks if the user submited or not some file\r
452      *\r
453      * @return mixed False when are files or PEAR_Error when no files\r
454      * @access public\r
455      * @see Read the note in the source code about this function\r
456      */\r
457     function isMissing()\r
458     {\r
459         if (count($this->post_files) < 1) {\r
460             return $this->raiseError('NO_USER_FILE');\r
461         }\r
462         //we also check if at least one file has more than 0 bytes :)\r
463         $files = array();\r
464         $size = 0;\r
465         foreach ($this->post_files as $userfile => $value) {\r
466             if (is_array($value['name'])) {\r
467                 foreach ($value['name'] as $key => $val) {\r
468                     $size += $value['size'][$key];\r
469                 }\r
470             } else {  //one file\r
471                 $size = $value['size'];\r
472             }\r
473         }\r
474         if ($size == 0) {\r
475             $this->raiseError('NO_USER_FILE');\r
476         }\r
477         return false;\r
478     }\r
479 \r
480     /**\r
481      * Sets the chmod to be used for uploaded files\r
482      *\r
483      * @param int Desired mode \r
484      */\r
485     function setChmod($mode)\r
486     {\r
487         $this->_chmod = $mode;\r
488     }\r
489 }\r
490 \r
491 /**\r
492  * This class provides functions to work with the uploaded file\r
493  *\r
494  * @author  Tomas V.V.Cox <cox@idecnet.com>\r
495  * @see http://vulcanonet.com/soft/index.php?pack=uploader\r
496  * @package  HTTP_Upload\r
497  * @category HTTP\r
498  * @access   public\r
499  */\r
500 class HTTP_Upload_File extends HTTP_Upload_Error\r
501 {\r
502     /**\r
503      * If the random seed was initialized before or not\r
504      * @var  boolean;\r
505      */\r
506     var $_seeded = 0;\r
507 \r
508     /**\r
509      * Assoc array with file properties\r
510      * @var array\r
511      */\r
512     var $upload = array();\r
513 \r
514     /**\r
515      * If user haven't selected a mode, by default 'safe' will be used\r
516      * @var boolean\r
517      */\r
518     var $mode_name_selected = false;\r
519 \r
520     /**\r
521      * It's a common security risk in pages who has the upload dir\r
522      * under the document root (remember the hack of the Apache web?)\r
523      *\r
524      * @var array\r
525      * @access private\r
526      * @see HTTP_Upload_File::setValidExtensions()\r
527      */\r
528     var $_extensions_check = array('php', 'phtm', 'phtml', 'php3', 'inc');\r
529 \r
530     /**\r
531      * @see HTTP_Upload_File::setValidExtensions()\r
532      * @var string\r
533      * @access private\r
534      */\r
535     var $_extensions_mode  = 'deny';\r
536 \r
537     /**\r
538      * Contains the desired chmod for uploaded files\r
539      * @var int\r
540      * @access private\r
541      */\r
542     var $_chmod = HTTP_UPLOAD_DEFAULT_CHMOD;\r
543 \r
544     /**\r
545      * Constructor\r
546      *\r
547      * @param   string  $name       destination file name\r
548      * @param   string  $tmp        temp file name\r
549      * @param   string  $formname   name of the form\r
550      * @param   string  $type       Mime type of the file\r
551      * @param   string  $size       size of the file\r
552      * @param   string  $error      error on upload\r
553      * @param   string  $lang       used language for errormessages\r
554      * @access  public\r
555      */\r
556     function HTTP_Upload_File($name = null, $tmp = null,  $formname = null,\r
557                               $type = null, $size = null, $error = null, \r
558                               $lang = null, $chmod = HTTP_UPLOAD_DEFAULT_CHMOD)\r
559     {\r
560         $this->HTTP_Upload_Error($lang);\r
561         $ext = null;\r
562 \r
563         if (empty($name) || $size == 0) {\r
564             $error = 'NO_USER_FILE';\r
565         } elseif ($tmp == 'none') {\r
566             $error = 'TOO_LARGE';\r
567         } else {\r
568             // strpos needed to detect files without extension\r
569             if (($pos = strrpos($name, '.')) !== false) {\r
570                 $ext = substr($name, $pos + 1);\r
571             }\r
572         }\r
573 \r
574         if (function_exists('version_compare') &&\r
575             version_compare(phpversion(), '4.1', 'ge')) {\r
576             if (isset($_POST['MAX_FILE_SIZE']) &&\r
577                 $size > $_POST['MAX_FILE_SIZE']) {\r
578                 $error = 'TOO_LARGE';\r
579             }\r
580         } else {\r
581             global $HTTP_POST_VARS;\r
582             if (isset($HTTP_POST_VARS['MAX_FILE_SIZE']) &&\r
583                 $size > $HTTP_POST_VARS['MAX_FILE_SIZE']) {\r
584                 $error = 'TOO_LARGE';\r
585             }\r
586         }\r
587 \r
588         $this->upload = array(\r
589             'real'      => $name,\r
590             'name'      => $name,\r
591             'form_name' => $formname,\r
592             'ext'       => $ext,\r
593             'tmp_name'  => $tmp,\r
594             'size'      => $size,\r
595             'type'      => $type,\r
596             'error'     => $error\r
597         );\r
598 \r
599         $this->_chmod = $chmod;\r
600     }\r
601 \r
602     /**\r
603      * Sets the name of the destination file\r
604      *\r
605      * @param string $mode     A valid mode: 'uniq', 'safe' or 'real' or a file name\r
606      * @param string $prepend  A string to prepend to the name\r
607      * @param string $append   A string to append to the name\r
608      *\r
609      * @return string The modified name of the destination file\r
610      * @access public\r
611      */\r
612     function setName($mode, $prepend = null, $append = null)\r
613     {\r
614         switch ($mode) {\r
615             case 'uniq':\r
616                 $name = $this->nameToUniq();\r
617                 $this->upload['ext'] = $this->nameToSafe($this->upload['ext'], 10);\r
618                 $name .= '.' . $this->upload['ext'];\r
619                 break;\r
620             case 'safe':\r
621                 $name = $this->nameToSafe($this->upload['real']);\r
622                 if (($pos = strrpos($name, '.')) !== false) {\r
623                     $this->upload['ext'] = substr($name, $pos + 1);\r
624                 } else {\r
625                     $this->upload['ext'] = '';\r
626                 }\r
627                 break;\r
628             case 'real':\r
629                 $name = $this->upload['real'];\r
630                 break;\r
631             default:\r
632                 $name = $mode;\r
633         }\r
634         $this->upload['name'] = $prepend . $name . $append;\r
635         $this->mode_name_selected = true;\r
636         return $this->upload['name'];\r
637     }\r
638 \r
639     /**\r
640      * Unique file names in the form: 9022210413b75410c28bef.html\r
641      * @see HTTP_Upload_File::setName()\r
642      */\r
643     function nameToUniq()\r
644     {\r
645         if (! $this->_seeded) {\r
646             srand((double) microtime() * 1000000);\r
647             $this->_seeded = 1;\r
648         }\r
649         $uniq = uniqid(rand());\r
650         return $uniq;\r
651     }\r
652 \r
653     /**\r
654      * Format a file name to be safe\r
655      *\r
656      * @param    string $file   The string file name\r
657      * @param    int    $maxlen Maximun permited string lenght\r
658      * @return   string Formatted file name\r
659      * @see HTTP_Upload_File::setName()\r
660      */\r
661     function nameToSafe($name, $maxlen=250)\r
662     {\r
663         $noalpha = 'ÁÉÍÓÚÝáéíóúýÂÊÎÔÛâêîôûÀÈÌÒÙàèìòùÄËÏÖÜäëïöüÿÃãÕõÅåÑñÇç@°ºª';\r
664         $alpha   = 'AEIOUYaeiouyAEIOUaeiouAEIOUaeiouAEIOUaeiouyAaOoAaNnCcaooa';\r
665 \r
666         $name = substr($name, 0, $maxlen);\r
667         $name = strtr($name, $noalpha, $alpha);\r
668         // not permitted chars are replaced with "_"\r
669         return preg_replace('/[^a-zA-Z0-9,._\+\()\-]/', '_', $name);\r
670     }\r
671 \r
672     /**\r
673      * The upload was valid\r
674      *\r
675      * @return bool If the file was submitted correctly\r
676      * @access public\r
677      */\r
678     function isValid()\r
679     {\r
680         if ($this->upload['error'] === null) {\r
681             return true;\r
682         }\r
683         return false;\r
684     }\r
685 \r
686     /**\r
687      * User haven't submit a file\r
688      *\r
689      * @return bool If the user submitted a file or not\r
690      * @access public\r
691      */\r
692     function isMissing()\r
693     {\r
694         if ($this->upload['error'] == 'NO_USER_FILE') {\r
695             return true;\r
696         }\r
697         return false;\r
698     }\r
699 \r
700     /**\r
701      * Some error occured during upload (most common due a file size problem,\r
702      * like max size exceeded or 0 bytes long).\r
703      * @return bool If there were errors submitting the file (probably\r
704      *              because the file excess the max permitted file size)\r
705      * @access public\r
706      */\r
707     function isError()\r
708     {\r
709         if (in_array($this->upload['error'], array('TOO_LARGE', 'BAD_FORM','DEV_NO_DEF_FILE'))) {\r
710             return true;\r
711         }\r
712         return false;\r
713     }\r
714 \r
715     /**\r
716      * Moves the uploaded file to its destination directory.\r
717      *\r
718      * @param    string  $dir_dest  Destination directory\r
719      * @param    bool    $overwrite Overwrite if destination file exists?\r
720      * @return   mixed   True on success or Pear_Error object on error\r
721      * @access public\r
722      */\r
723     function moveTo($dir_dest, $overwrite = true)\r
724     {\r
725         if (!$this->isValid()) {\r
726             return $this->raiseError($this->upload['error']);\r
727         }\r
728 \r
729         //Valid extensions check\r
730         if (!$this->_evalValidExtensions()) {\r
731             return $this->raiseError('NOT_ALLOWED_EXTENSION');\r
732         }\r
733 \r
734         $err_code = $this->_chk_dir_dest($dir_dest);\r
735         if ($err_code !== false) {\r
736             return $this->raiseError($err_code);\r
737         }\r
738         // Use 'safe' mode by default if no other was selected\r
739         if (!$this->mode_name_selected) {\r
740             $this->setName('safe');\r
741         }\r
742 \r
743         $name_dest = $dir_dest . DIRECTORY_SEPARATOR . $this->upload['name'];\r
744 \r
745         if (@is_file($name_dest)) {\r
746             if ($overwrite !== true) {\r
747                 return $this->raiseError('FILE_EXISTS');\r
748             } elseif (!is_writable($name_dest)) {\r
749                 return $this->raiseError('CANNOT_OVERWRITE');\r
750             }\r
751         }\r
752 \r
753         // copy the file and let php clean the tmp\r
754         if (!@move_uploaded_file($this->upload['tmp_name'], $name_dest)) {\r
755             return $this->raiseError('E_FAIL_MOVE');\r
756         }\r
757         @chmod($name_dest, $this->_chmod);\r
758         return $this->getProp('name');\r
759     }\r
760 \r
761     /**\r
762      * Check for a valid destination dir\r
763      *\r
764      * @param    string  $dir_dest Destination dir\r
765      * @return   mixed   False on no errors or error code on error\r
766      */\r
767     function _chk_dir_dest($dir_dest)\r
768     {\r
769         if (!$dir_dest) {\r
770             return 'MISSING_DIR';\r
771         }\r
772         if (!@is_dir ($dir_dest)) {\r
773             return 'IS_NOT_DIR';\r
774         }\r
775         if (!is_writeable ($dir_dest)) {\r
776             return 'NO_WRITE_PERMS';\r
777         }\r
778         return false;\r
779     }\r
780     /**\r
781      * Retrive properties of the uploaded file\r
782      * @param string $name   The property name. When null an assoc array with\r
783      *                       all the properties will be returned\r
784      * @return mixed         A string or array\r
785      * @see HTTP_Upload_File::HTTP_Upload_File()\r
786      * @access public\r
787      */\r
788     function getProp($name = null)\r
789     {\r
790         if ($name === null) {\r
791             return $this->upload;\r
792         }\r
793         return $this->upload[$name];\r
794     }\r
795 \r
796     /**\r
797      * Returns a error message, if a error occured\r
798      * (deprecated) Use getMessage() instead\r
799      * @return string    a Error message\r
800      * @access public\r
801      */\r
802     function errorMsg()\r
803     {\r
804         return $this->errorCode($this->upload['error']);\r
805     }\r
806 \r
807     /**\r
808      * Returns a error message, if a error occured\r
809      * @return string    a Error message\r
810      * @access public\r
811      */\r
812     function getMessage()\r
813     {\r
814         return $this->errorCode($this->upload['error']);\r
815     }\r
816 \r
817     /**\r
818      * Function to restrict the valid extensions on file uploads\r
819      *\r
820      * @param array $exts File extensions to validate\r
821      * @param string $mode The type of validation:\r
822      *                       1) 'deny'   Will deny only the supplied extensions\r
823      *                       2) 'accept' Will accept only the supplied extensions\r
824      *                                   as valid\r
825      * @access public\r
826      */\r
827     function setValidExtensions($exts, $mode = 'deny')\r
828     {\r
829         $this->_extensions_check = $exts;\r
830         $this->_extensions_mode  = $mode;\r
831     }\r
832 \r
833     /**\r
834      * Evaluates the validity of the extensions set by setValidExtensions\r
835      *\r
836      * @return bool False on non valid extension, true if they are valid\r
837      * @access private\r
838      */\r
839     function _evalValidExtensions()\r
840     {\r
841         $exts = $this->_extensions_check;\r
842         settype($exts, 'array');\r
843         if ($this->_extensions_mode == 'deny') {\r
844             if (in_array($this->getProp('ext'), $exts)) {\r
845                 return false;\r
846             }\r
847         // mode == 'accept'\r
848         } else {\r
849             if (!in_array($this->getProp('ext'), $exts)) {\r
850                 return false;\r
851             }\r
852         }\r
853         return true;\r
854     }\r
855 }\r
856 ?>