0) { define("UH_DEBUG", true); } else { define("UH_DEBUG", false); } if (UH_DEBUG) { e107::getLog()->addEvent(10, debug_backtrace(), "DEBUG", "Upload Handler test", "Process uploads to {$uploaddir}, fileinfo ".$fileinfo, FALSE, LOG_TO_ROLLING); } // $admin_log->addEvent(10,__FILE__."|".__FUNCTION__."@".__LINE__,"DEBUG","Upload Handler test","Intermediate directory: {$ul_temp_dir} ",FALSE,LOG_TO_ROLLING); $overwrite = varset($options['overwrite'], FALSE); $uploaddir = realpath($uploaddir); // Mostly to get rid of the grot that might be passed in from legacy code. Also strips any trailing '/' if (!is_dir($uploaddir)) { if (UH_DEBUG) { // e107::getLog()->addEvent(10, __FILE__."|".__FUNCTION__."@".__LINE__, "DEBUG", "Upload Handler test", "Invalid directory: ".$uploaddir, FALSE, FALSE); } return FALSE; // Need a valid directory } if (UH_DEBUG) { // e107::getLog()->addEvent(10, __FILE__."|".__FUNCTION__."@".__LINE__, "DEBUG", "Upload Handler test", "Destination directory: ".$uploaddir, FALSE, FALSE); } $final_chmod = varset($options['final_chmod'], 0644); if (isset($options['file_array_name'])) { $files = $_FILES[$options['file_array_name']]; } else { $files = $_FILES['file_userfile']; } $max_file_count = varset($options['max_file_count'], 0); if (empty($files)) { if (deftrue('UH_DEBUG')) { e107::getLog()->addEvent(10, __FILE__."|".__FUNCTION__."@".__LINE__, "DEBUG", "Upload Handler test", "No files uploaded", FALSE, FALSE); } return FALSE; } $uploaded = array(); $max_upload_size = calc_max_upload_size(varset($options['max_upload_size'], -1)); // Find overriding maximum upload size $allowed_filetypes = get_filetypes(varset($options['file_mask'], ''), varset($options['filetypes'], '')); $max_upload_size = set_max_size($allowed_filetypes, $max_upload_size); // That's the basics set up - we can start processing files now if (deftrue('UH_DEBUG')) { // e107::getLog()->addEvent(10, __FILE__."|".__FUNCTION__."@".__LINE__, "DEBUG", "Upload Handler test", "Start individual files: ".count($files['name'])." Max upload: ".$max_upload_size, FALSE, FALSE); } $c = 0; $tp = e107::getParser(); $uploadfile = null; foreach ($files['name'] as $key=>$name) { $name = $tp->filter($name, 'str'); $first_error = FALSE; // Clear error flag if (($name != '') || $files['size'][$key]) // Need this check for things like file manager which allow multiple possible uploads { $origname = $name; //$name = preg_replace("/[^a-z0-9._-]/", '', str_replace(' ', '_', str_replace('%20', '_', strtolower($name)))); // FIX handle non-latin file names $name = preg_replace("/[^\w\pL.-]/u", '', str_replace(' ', '_', str_replace('%20', '_', $tp->ustrtolower($name)))); $raw_name = $name; // Save 'proper' file name - useful for display $file_ext = trim(strtolower(substr(strrchr($name, "."), 1))); // File extension - forced to lower case internally if (!trim($files['type'][$key])) $files['type'][$key] = 'Unknowm mime-type'; if (UH_DEBUG) { e107::getLog()->addEvent(10, __FILE__."|".__FUNCTION__."@".__LINE__, "DEBUG", "Upload Handler test", "Process file {$name}, size ".$files['size'][$key], FALSE, FALSE); } if ($max_file_count && ($c >= $max_file_count)) { $first_error = 249; // 'Too many files uploaded' error } else { $first_error = $files['error'][$key]; // Start with whatever error PHP gives us for the file } if (!$first_error) { // Check file size early on if ($files['size'][$key] == 0) { $first_error = 4; // Standard error code for zero size file } elseif ($files['size'][$key] > $max_upload_size) { $first_error = 254; } elseif (isset($allowed_filetypes[$file_ext]) && ($allowed_filetypes[$file_ext] > 0) && ($files['size'][$key] > $allowed_filetypes[$file_ext])) { // XML file set limits per extension $first_error = 254; } } if (!$first_error) { $uploadfile = $files['tmp_name'][$key]; // Name in temporary directory if (!$uploadfile) $first_error = 253; } if (!$first_error) { // Need to support multiple files with the same 'real' name in some cases if (strpos($fileinfo, "attachment") === 0) { // For attachments, add in a prefix plus time and date to give a unique file name $addbit = explode('+', $fileinfo, 2); $name = time()."_".USERID."_".trim($addbit[1]).$name; } elseif (strpos($fileinfo, "prefix") === 0) { // For attachments, avatars, photos etc alternatively just add a prefix we've been passed $addbit = explode('+', $fileinfo, 2); $name = trim($addbit[1]).$name; } $destination_file = $uploaddir."/".$name; if ($fileinfo == "unique" && file_exists($destination_file)) { // Modify destination name to make it unique - but only if target file name exists $name = time()."_".$name; $destination_file = $uploaddir."/".$name; } if (file_exists($destination_file) && !$overwrite) $first_error = 250; // Invent our own error number - duplicate file } if (!$first_error) { $tpos = FALSE; if ($file_ext != '') // Require any uploaded file to have an extension { if ($ul_temp_dir) { // Need to move file to our own temporary directory $tempfilename = $uploadfile; $uploadfile = $ul_temp_dir.basename($uploadfile); if (UH_DEBUG) { e107::getLog()->addEvent(10, __FILE__."|".__FUNCTION__."@".__LINE__, "DEBUG", "Upload Handler test", "Move {$tempfilename} to {$uploadfile} ", FALSE, LOG_TO_ROLLING); } @move_uploaded_file($tempfilename, $uploadfile); // This should work on all hosts } $tpos = (($file_status = vet_file($uploadfile, $name, $allowed_filetypes, varset($options['extra_file_types'], FALSE))) === TRUE); } if ($tpos === FALSE) { // File type upload not permitted - error message and abort $first_error = 251; // Invent our own error number - file type not permitted } } if (!$first_error) // All tests passed - can store it somewhere { // File upload broken - temp file renamed. // FIXME - method starting with 'get' shouldn't do system file changes. $uploaded[$c] = e107::getFile()->getFileInfo($uploadfile, true, false); $uploaded[$c]['name'] = $name; $uploaded[$c]['rawname'] = $raw_name; $uploaded[$c]['origname'] = $origname; $uploaded[$c]['type'] = $files['type'][$key]; $uploaded[$c]['size'] = 0; $uploaded[$c]['index'] = $key; // Store the actual index from the file_userfile array // e107::getMessage()->addDebug(print_a($uploaded[$c],true)); // Store as flat file if ((!$ul_temp_dir && @move_uploaded_file($uploadfile, $destination_file)) || ($ul_temp_dir && @rename($uploadfile, $destination_file))) // This should work on all hosts { @chmod($destination_file, $final_chmod); if (UH_DEBUG) { e107::getLog()->addEvent(10, __FILE__."|".__FUNCTION__."@".__LINE__, "DEBUG", "Upload Handler test", "Final chmod() file {$destination_file} to {$final_chmod} ", FALSE, FALSE); } $uploaded[$c]['size'] = $files['size'][$key]; $uploaded[$c]['fullpath'] = $uploaddir.DIRECTORY_SEPARATOR.$name; if (UH_DEBUG) { e107::getLog()->addEvent(10, __FILE__."|".__FUNCTION__."@".__LINE__, "DEBUG", "Upload Handler test", "Saved file {$c} OK: ".$uploaded[$c]['name'], FALSE, FALSE); } } else { $first_error = 252; // Error - "couldn't save destination" } } if (!$first_error) // This file succeeded { $uploaded[$c]['message'] = LANUPLOAD_3." '".$raw_name."'"; $uploaded[$c]['error'] = 0; } else { $uploaded[$c]['error'] = $first_error; $uploaded[$c]['size'] = 0; switch ($first_error) { case 1: // Exceeds upload_max_filesize in php.ini $error = LANUPLOAD_5; break; case 2: // Exceeds MAX_FILE_SIZE in form $error = LANUPLOAD_6; break; case 3: // Partial upload $error = LANUPLOAD_7; break; case 4: // No file uploaded $error = LANUPLOAD_8; break; case 5: // Undocumented code (zero file size) $error = LANUPLOAD_9; break; case 6: // Missing temporary folder $error = LANUPLOAD_13; break; case 7: // File write failed $error = LANUPLOAD_14; break; case 8: // Upload stopped by extension $error = LANUPLOAD_15; break; case 249: // Too many files (our error code) $error = LANUPLOAD_19; break; case 250: // Duplicate File (our error code) $error = LANUPLOAD_10; break; case 251: // File type not allowed (our error code) $error = LANUPLOAD_1." ".$files['type'][$key]." ".LANUPLOAD_2." ({$file_status})"; break; case 252: // File uploaded OK, but couldn't save it $error = LANUPLOAD_4." [".str_replace("../", "", $uploaddir)."]"; break; case 253: // Bad name for uploaded file (our error code) $error = LANUPLOAD_17; break; case 254: // file size exceeds allowable limits (our error code) $error = LANUPLOAD_18; break; default: // Shouldn't happen - but at least try and make it obvious if it does! $error = LANUPLOAD_16; } $uploaded[$c]['message'] = LANUPLOAD_11." '".$name."'
".LAN_ERROR.": ".$error; $uploaded[$c]['line'] = __LINE__; $uploaded[$c]['file'] = __FILE__; if (UH_DEBUG) // If we need to abort on first error, do so here - could check for specific error codes { e107::getLog()->addEvent(10, __FILE__."|".__FUNCTION__."@".__LINE__, "DEBUG", "Upload Handler test", "Main routine error {$first_error} file {$c}: ".$uploaded[$c]['message'], FALSE, FALSE); } } if (is_file($uploadfile)) { @unlink($uploadfile); // Don't leave the file on the server if error (although should be auto-deleted) } $c++; } } return $uploaded; } /** * Utility routine to handle the messages returned by process_uploaded_files(). * @param array $upload_array is the list of uploaded files (as returned by process_uploaded_files()) * @param boolean $errors_only - if TRUE, no message is shown for a successful upload. * @param boolean $use_handler - if TRUE, message_handler is used to display the message. * @return string - a list of all accumulated messages. (Non-destructive call, so can be called several times with different options). */ function handle_upload_messages(&$upload_array, $errors_only = TRUE, $use_handler = FALSE) { // Display error messages, accumulate FMESSAGE // Write as a separate routine - returns all messages displayed. Option to only display failures. $f_message = []; foreach ($upload_array as $k=>$r) { if (!$errors_only || $r['error']) { if ($use_handler) { require_once (e_HANDLER."message_handler.php"); message_handler("MESSAGE", $r['message'], $r['line'], $r['file']); } $f_message[] = $r['message']; } } return implode("
", $f_message); } /* //==================================================================== // LEGACY FILE UPLOAD HANDLER //==================================================================== /** * This is the 'legacy' interface, which handles various special cases etc. * It was the only option in E107 0.7.8 and earlier, and is still used in some places in core. * It also attempts to return in the same way as the original, especially when any errors occur * @deprecated * @param string $uploaddir - target directory for file. Defaults to e_FILE/public * @param boolean|string $avatar - sets the 'type' or destination of the file: * FALSE - its a 'general' file * 'attachment' - indicates an attachment (related to forum post or PM) * 'unique' - indicates that file name must be unique - new name given (prefixed with time()_ ) * 'avatar' - indicates an avatar is being uploaded * @param string $fileinfo - included within the name of the saved file with attachments - can be an identifier of some sort * (Forum adds 'FT{$tid}_' - where $tid is the thread ID. * @param boolean $overwrite - if true, an uploaded file can overwrite an existing file of the same name (not used in 0.7 core) * * Preference used: * $pref['upload_storagetype'] - now ignored - used to be 1 for files, 2 for database storage * * @return boolean|array - For backward compatibility, returns FALSE if only one file uploaded and an error; * otherwise returns an array with per-file error codes as appropriate. * On exit, F_MESSAGE is defined with the success/failure message(s) that have been displayed - one file per line */ /** * @Deprecated use e107::getFile()->getUploaded(); * @param $uploaddir * @param bool|false $avatar * @param string $fileinfo * @param string $overwrite * @return array|bool */ function file_upload($uploaddir, $avatar = FALSE, $fileinfo = "", $overwrite = "") { $admin_log = e107::getLog(); $options = array( 'extra_file_types'=>TRUE ); // As default, allow any filetype enabled in filetypes.php if (!$uploaddir) { $uploaddir = e_UPLOAD; } if (strpos($avatar, '=') !== FALSE) { list($avatar, $param) = explode('=', $avatar, 2); } else { $param = USERID; } switch ($avatar) { case 'attachment': $avatar = "attachment+".$fileinfo; break; case 'avatar': $avatar = 'prefix+ap_'.$param.'_'; // Prefix unique to user $options['overwrite'] = TRUE; // Allow update of avatar with same file name break; } if (UH_DEBUG) $admin_log-> addEvent(10, __FILE__."|".__FUNCTION__."@".__LINE__, "DEBUG", "Upload Handler test", "Legacy call, directory ".$uploaddir, FALSE, FALSE); $ret = process_uploaded_files(getcwd()."/".$uploaddir, $avatar, $options); // Well, that's the way it was done before if ($ret === FALSE) { if (UH_DEBUG) $admin_log-> addEvent(10, __FILE__."|".__FUNCTION__."@".__LINE__, "DEBUG", "Upload Handler test", "Legacy return FALSE", FALSE, FALSE); return FALSE; } if (UH_DEBUG) $admin_log-> addEvent(10, __FILE__."|".__FUNCTION__."@".__LINE__, "DEBUG", "Upload Handler test", "Legacy return with ".count($ret)." files", FALSE, FALSE); $messages = handle_upload_messages($ret, FALSE, TRUE); // Show all the error and acknowledgment messages define('F_MESSAGE', $messages); if (count($ret) == 1) { if ($ret[0]['error'] != 0) return FALSE; // Special case if errors } return $ret; } //==================================================================== // VETTING AND UTILITY ROUTINES //==================================================================== /** * @param $filename * @param bool|false $extended * @return array|string *@see e_file::getImageMime(); * or when extended - array [(string) mime-type, (array) associated extensions)]. * A much faster way to retrieve mimes than getimagesize() * * @deprecated Get image (string) mime type */ function get_image_mime($filename, $extended = false) { trigger_error(__METHOD__.' is deprecated. Use e107::getFile()->getImageMime($filename, $extended); instead.', E_USER_DEPRECATED); return e107::getFile()->getImageMime($filename, $extended); } /** * Check uploaded file to try and identify dodgy content. * @param string $filename is the full path+name to the uploaded file on the server * @param string $target_name is the intended name of the file once transferred * @param array $allowed_filetypes is an array of permitted file extensions, in lower case, no leading '.' * (usually generated from filetypes.xml/filetypes.php) * @param boolean|string $unknown - handling of file types unknown to us/define additional types * if FALSE, rejects totally unknown file extensions (even if in $allowed_filetypes). * if $unknown is TRUE, accepts totally unknown file extensions. * otherwise $unknown is a comma-separated list of additional permitted file extensions * @return boolean|int - TRUE if file acceptable, a numeric 'fail' code if unacceptable: * 1 - file type not allowed * 2 - can't read file contents * 3 - illegal file contents (usually '$f) { $file_array[$k] = trim($f); } } if ($def_file && is_readable(e_ADMIN.$def_file)) { $a_filetypes = trim(file_get_contents(e_ADMIN.$def_file)); $a_filetypes = explode(',', $a_filetypes); } foreach ($a_filetypes as $ftype) { $ftype = strtolower(trim(str_replace('.', '', $ftype))); if ($ftype && (!$file_mask || in_array($ftype, $file_array))) { $ret[$ftype] = -1; } } return $ret; } /** * Parse a file size string (e.g. 16M) and compute the simple numeric value. * Proxy to e_file::file_size_decode(). * * @see e_file::file_size_decode() * @param string $source - input string which may include 'multiplier' characters such as 'M' or 'G'. Converted to 'decoded value' * @param int $compare - a 'compare' value * @param string $action - values (gt|lt) * * @return int file size value. * If the decoded value evaluates to zero, returns the value of $compare * If $action == 'gt', return the larger of the decoded value and $compare * If $action == 'lt', return the smaller of the decoded value and $compare */ function file_size_decode($source, $compare = 0, $action = '') { return e107::getFile(true)->file_size_decode($source, $compare, $action); } /** * @deprecated @see e_file::getAllowedFileTypes(); * Get array of file types (file extensions) which are permitted - reads an XML-formatted definition file. * (Similar to @See{get_allowed_filetypes()}, but expects an XML file) * * @param string $def_file - name of an XML-formatted file, which is sought in the E_ADMIN directory * @param string $file_mask - comma-separated list of allowed file types - only those specified in both $file_mask and $def_file are returned * * @return array - where key is the file type (extension); value is max upload size */ function get_XML_filetypes($def_file = FALSE, $file_mask = '') { trigger_error(__METHOD__.' is deprecated. Use e107::getFile()->getAllowedFileTypes(); instead.', E_USER_DEPRECATED); return e107::getFile()->getAllowedFileTypes(); } /** * Calculate 'global' maximum upload size - the maximum before extension-specific restrictions taken into account * * @param int $max_up - if > 0, its a global maximum permitted. If < 0, $pref['upload_maxfilesize'] is used (if set) * * @return int maximum allowed upload size for file */ function calc_max_upload_size($max_up = -1) { global $pref; $admin_log = e107::getLog(); // Work out maximum allowable file size if (deftrue('UH_DEBUG')) { $admin_log-> addEvent(10, __FILE__."|".__FUNCTION__."@".__LINE__, "DEBUG", "Upload Handler test", "File size limits - user set: ".$pref['upload_maxfilesize']." Post_max_size: ".ini_get('post_max_size')." upload_max_size: ".ini_get('upload_max_size'), FALSE, FALSE); } $max_upload_size = file_size_decode(ini_get('post_max_size')); $max_upload_size = file_size_decode(ini_get('upload_max_filesize'), $max_upload_size, 'lt'); if ($max_up > 0) { $max_upload_size = file_size_decode($max_up, $max_upload_size, 'lt'); } else { if (varset($pref['upload_maxfilesize'], 0) > 0) $max_upload_size = file_size_decode($pref['upload_maxfilesize'], $max_upload_size, 'lt'); } if (deftrue('UH_DEBUG')) $admin_log-> addEvent(10, __FILE__."|".__FUNCTION__."@".__LINE__, "DEBUG", "Upload Handler test", "Final max upload size: {$max_upload_size}", FALSE, FALSE); return $max_upload_size; } /** * Get an array of permitted filetypes according to a set hierarchy. * If a specific file name given, that's used. Otherwise the default hierarchy is used * * @param string $file_mask - comma-separated list of allowed file types * @param string $filename - optional override file name - defaults ignored * * @return array of filetypes */ function get_filetypes($file_mask = FALSE, $filename = '') { if ($filename != '') { if (strtolower(substr($filename, -4) == '.xml')) { return get_XML_filetypes($filename, $file_mask); } return get_allowed_filetypes($filename, $file_mask); } if (is_readable(e_SYSTEM.e_READ_FILETYPES)) { return get_XML_filetypes(e_READ_FILETYPES, $file_mask); } if (ADMIN && is_readable(e_ADMIN.'admin_filetypes.php')) { return get_allowed_filetypes('admin_filetypes.php', $file_mask); } if (is_readable(e_ADMIN.'filetypes.php')) { return get_allowed_filetypes('filetypes.php', $file_mask); } return array(); // Just an empty array } /** * Scans the array of allowed file types, updates allowed max size as appropriate. * If the value is larger than the site-wide maximum, reduces it. * * @param array $allowed_filetypes - key is file type (extension), value is maximum size allowed * @param int $max_upload_size - site-wide maximum file upload size * * @return int largest allowed file size across all file types */ function set_max_size(&$allowed_filetypes, $max_upload_size) { $new_max = 0; foreach ($allowed_filetypes as $t=>$s) { if ($s < 0) { // Unspecified max - use the global value $allowed_filetypes[$t] = $max_upload_size; } elseif ($s > $max_upload_size) $allowed_filetypes[$t] = $max_upload_size; if ($allowed_filetypes[$t] > $new_max) $new_max = $allowed_filetypes[$t]; } return $new_max; } /* * Quick routine if all we want is the size of the largest file the current user can upload * * @return int largest allowed file size across all file types */ /** * @return int */ function get_user_max_upload() { $a_filetypes = get_filetypes(); if (count($a_filetypes) == 0) return 0; // Return if no upload allowed $max_upload_size = calc_max_upload_size(-1); // Find overriding maximum upload size $max_upload_size = set_max_size($a_filetypes, $max_upload_size); return $max_upload_size; }