<?php

/*
 *  W-AGORA 4.2
 *  -----------
 *  $Id: fileupload.php,v 1.19 2005/04/14 14:12:44 mdruilhe Exp $
 *  Usage:      Upload management class (include file)
 *  Author:     Marc Druilhe <mdruilhe@w-agora.net>
 *
 */

if (defined("_FILEUPLOAD")) return;

define('_FILEUPLOAD', 1);

define ('NO_FILE', -1);
define ('FILE_TOO_BIG', -2);
define ('INVALID_FILE_TYPE', -3);
define ('DB_ERROR', -4);
define ('COPY_ERROR', -5);
define ('ERR_FILE', -6);
define ('FILE_EMPTY', -7);
define ('ERR_ARG', -8);

define ('DEFAULT_INLINE', '1');

class FileUpload {

var $errno = 0;

var $site = '';
var $forum = '';
var $upload_dir = '';

var $Halt_On_Error = 'report';

/**
 * Constructor : Initialize some variables
 *
 * @param     string $site  site name
 * @param     string $bn    forum name
 * @param     string $dir   directory into which save the attached files
 * @access    public
 * @return    void
 */
function init ($site, $forum, $dir) {
	$this->site = $site;
	$this->forum = $forum;
	$this->upload_dir = $dir;
} // end init


/**
 * error handling
 *
 * @param
 * @since     1.0
 * @access    private
 * @return    void
 */
function halt ($msg='') {
	if ($this->Halt_On_Error == 'no')
		return;

	switch ($this->errno) {
		case FILE_TOO_BIG:
			$reason = ERROR_FILE_TOO_BIG;
			break;
		case INVALID_FILE_TYPE:
			$reason = ERROR_INVALID_FILE_TYPE;
			break;
		default:
			$reason = sprintf (ERROR_CODE_WAS, $this->errno);
			break;
	}

	print ('</td></tr></table></td></tr></table></td></tr></table>');

	if ($this->Halt_On_Error == 'report') {
		printf (MSG_WARNING, $msg, $reason);
	} else {
		printf (MSG_ERROR, "File management", $msg, $reason);
	}

	if ($this->Halt_On_Error != 'report') {
		die(MSG_SESSION_HALTED);
	}
}

/**
 * Copy one uploaded file to its destination and insert an entry in the database
 *
 * @param	string	$note_key	id of associated note
 * @param	array	$file		Uploaded file information see $_FILES[] in PHP doc
 * @param	char	$state		state of this attachment ("1": active, "P": pending (preview mode),
 * @param	char	$inline		display mode (1 = yes (let w-agora choose) )
 * @access	private
 * @return	boolean	TRUE if OK
 */
function uploadFile ($note_key, $file, $state=1, $inline=DEFAULT_INLINE) {
	global $db, $MAX_FILE_SIZE, $inc_dir, $ext;
	global $mimetypes, $mimetype_default;

	// extract => $name, $type, $size, $tmp_name, $error
	extract($file);

	// Catch file upload error (only available since PHP 4.2)
	switch ($error) {
	  case 1:
		// The uploaded file exceeds the upload_max_filesize directive in php.ini. 
		$this->errno = FILE_TOO_BIG;
		return false;
	  break;

	  case 2:
		// The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the html form
		$this->errno = FILE_TOO_BIG;
		return false;
	  break;

	  case 3:
		// The uploaded file was only partially uploaded
		$this->errno = ERR_FILE;
		return false;
	  break;

	  case 4:
	  case 5:
		// No file was uploaded
		$this->errno = NO_FILE;
		return false;
	  break;

	  case 6:
		// Missing a temporary folder. Introduced in PHP 4.3.10 and PHP 5.0.3. 
		$this->errno = ERR_FILE;
		return false;
	  break;
	}

	settype ($size, 'integer');
	$this->errno = 0;

# Check temporary file
# --------------------
	if (empty($tmp_name) || (strcasecmp($tmp_name, 'none') == 0) ) {
		$this->errno = NO_FILE;
		return false;
	}

# Check size
# ----------
	if ($size == 0) {
		$this->errno = FILE_EMPTY;
		return false;
	}

	if ($size > $MAX_FILE_SIZE) {
		$this->errno = FILE_TOO_BIG;
		return FALSE;
	}

# Check name
# ----------
	if (empty($name) ) {
		$this->errno = NO_FILE;
		return false;
	}
	$name = ereg_replace ("[/\\\\:\*\?\"<>|']", '_', rawurldecode($name));

# Check type and extension
# ------------------------
	load_mimetypes();

	$suffix = strtoLower(substr(strrchr( $name, '.' ), 1 ));
	list ($type, $garbage) = split(';', $type);		// strip "; name=.... " (Opera6)

	if (isset($mimetypes[$suffix]) ) {
		$type = $mimetypes[$suffix];
	} elseif ( empty($type) || ($type=='application/octet-stream') ) {
		$type = $mimetype_default;
	}

	if (! $this->isAllowedFile ($name, $type) ) {
		$this->errno = INVALID_FILE_TYPE;
		return FALSE;
	}

# insert attachment reference in database
# ---------------------------------------
	$id = $db->insertAttachment ($this->forum, $note_key, $name, $this->upload_dir, $inline, $size, $type, $state, 0);
	if ($id <= 0) {
		$this->errno = DB_ERROR;
		return FALSE;
	}

# copy temporary file to the upload directory
# -------------------------------------------
	$dest_file = $this->upload_dir . "/$id.$name";
	$copyfunc = (function_exists('move_uploaded_file') ) ? 'move_uploaded_file' : 'copy';
	if (! $copyfunc ($tmp_name, $dest_file) ) {
		$db->deleteAttachment ($this->forum, $note_key, $this->upload_dir, $id);
		$this->errno = COPY_ERROR;
		return FALSE;
	}

	$fsize = filesize ($dest_file);
	if ($size != $fsize) {
		$this->errno = ERR_FILE;
		$db->deleteAttachment ($this->forum, $note_key, $this->upload_dir, $id);
		return FALSE;
	}
	if (defined('_FILEMODE')) {
		@chmod($dest_file, _FILEMODE);
	}
	return TRUE;
} // end uploadFile


/**
 * Get files uploaded with a note
 *
 * @param	string $note_key	id of the associated note
 * @param	char $state			state of attachments ("1": active, "P": pending (preview mode),
 * @param	char $name			The name of the input field in the form
 * @since	4.1
 * @access	public
 * @return	array
 */
function getUploadedFiles ($note_key, $state=1) {

	$this->errno = 0;
	$att_size = 0;
	$att_count = 0;

	if (empty($this->forum)) {
		$this->errno = ERR_ARG;
		return FALSE;
	}

	if (!is_array($_FILES)) {
		$this->errno = NO_FILE;
		return false;
	}

	foreach ($_FILES as $_FILE) {
		if (is_array($_FILE['name'])) {
			$nfiles = count($_FILE['name']);
			for ($i=0; $i<$nfiles; $i++) {
				$file['name'] = $_FILE['name'][$i];
				$file['size'] = $_FILE['size'][$i];
				$file['type'] = $_FILE['type'][$i];
				$file['tmp_name'] = $_FILE['tmp_name'][$i];
				$file['error'] = (isset($_FILE['error'][$i])) ? ( (int) $_FILE['error'][$i]) : 0;
				if ($this->uploadFile ($note_key, $file, $state, DEFAULT_INLINE) ) {
					$att_size += $file['size'];
					$att_count++;
				} elseif ($this->errno != NO_FILE) {
					$errmsg = sprintf (ERROR_FILE_NOT_SAVED, $file['name'], $file['type']);
					$this->halt ($errmsg);
				}
			}
		} else {
			if ($this->uploadFile ($note_key, $_FILE, $state, DEFAULT_INLINE) ) {
				$att_size = $_FILE['size'];
				$att_count = 1;
			} elseif ($this->errno != NO_FILE) {
				$errmsg = sprintf (ERROR_FILE_NOT_SAVED, $_FILE['name'], $_FILE['type']);
				$this->halt ($errmsg);
			}
		}
	}

	if ($att_size>0) {
		$att['att_size'] = $att_size;
		$att['att_count'] = $att_count;
		return $att;
	} else {
		return false;
	}

} // end getUploadedFiles

/**
 * Copy one file located on the server then insert an entry in the database
 * The function makes all the necessary checks (file siez, type, and so on)
 *
 * @param     string $note_key  id of associated note
 * @param     string $src_file  path to the file to be copied
 * @param     char $state       state of this attachment ("1": active, "P": pending (preview mode),
 * @param     char $inline  display mode
 * @access    private
 * @return    boolean   TRUE if OK
 */
function copyServerFile ($note_key, $src_file, $state=1, $inline=DEFAULT_INLINE) {
	global $db, $MAX_FILE_SIZE, $bn_browse_dir, $inc_dir, $ext;
	global $mimetypes, $mimetype_default;

	$this->errno = 0;

# Check source file
# -----------------
	if (empty($src_file) || (strcasecmp($src_file, 'none')==0) ) {
		$this->errno = NO_FILE;
		return false;
	}

	$ufile = eregi_replace ("^.:", '', $src_file);
	if (!ereg ("$bn_browse_dir", $ufile)) {
		$this->errno = ERR_FILE;
		return FALSE;
	}

# Check size
# ----------
	$size = filesize ($src_file);
	if ($size <= 0) {
		$this->errno = ERR_FILE;
		return FALSE;
	} elseif ($size > $MAX_FILE_SIZE) {
		$this->errno = FILE_TOO_BIG;
		return FALSE;
	}

# Check name
# ----------
	$name = basename($src_file);

# Check type and extension
# ------------------------
	load_mimetypes();
	$suffix = strtoLower(substr(strrchr( $name, "." ), 1 ));
	$type = (empty($mimetypes[$suffix])) ? $mimetype_default : $mimetypes[$suffix];
	if (! $this->isAllowedFile ($name, $type) ) {
		$this->errno = INVALID_FILE_TYPE;
		return FALSE;
	}

# insert attachment reference in database
# ---------------------------------------
	$id = $db->insertAttachment ($this->forum, $note_key, $name, $this->upload_dir, $inline, $size, $type, $state, 0);
	if ($id <= 0) {
		$this->errno = DB_ERROR;
		return FALSE;
	}

# copy temporary file to the upload directory
# -------------------------------------------
	$dest_file = $this->upload_dir . "/$id.$name";
	if (copy ($src_file, $dest_file) ) {
		@chmod($dest_file, _FILEMODE);
	} else {
		$db->deleteAttachment ($this->forum, $note_key, $this->upload_dir, $id);
		$this->errno = COPY_ERROR;
		return FALSE;
	}

	return TRUE;
} // end copyServerFile

/**
 * Attach a file to a note from the server
 *
 * The function takes the path of the file from the global variable $server_file
 * This variable can be an array that contains multiple files to be copied
 *
 * @param     string $note_key  id of the associated note
 * @param     char $state       state of attachments ("1": active, "P": pending (preview mode),
 * @since     4.1
 * @access    public
 * @return    boolean
 */
function getServerFiles ($note_key, $state=1) {
	global $server_file;

	$this->errno = 0;
	if (empty($this->forum)) {
		$this->errno = ERR_ARG;
		return FALSE;
	}

	$att_size = 0;
	$att_count = 0;
	if (is_string($server_file) && !empty($server_file) && ($server_file != 'none') ) {
		$att_name = basename($server_file);
		if ( $this->copyServerFile ($note_key, $server_file, $state, DEFAULT_INLINE) ) {
			$att_size = filesize ($server_file);
			$att_count = 1;
		} else {
			$this->halt ( sprintf(ERROR_FILE_COPY, $att_name) );
		}
	} elseif (is_array($server_file)) {
		$nfiles = count($server_file);
		for ($i=0; $i<$nfiles; $i++) {
			if (!empty($server_file[$i]) && ($server_file[$i] != 'none')) {
				if ($this->copyServerFile ($note_key, $server_file[$i], $state, DEFAULT_INLINE) ) {
					$att_size += filesize ($server_file[$i]);
					$att_count++;
				} else {
					$this->halt (sprintf(ERROR_FILE_COPY, $server_file[$i]) );
				}
			}
		}
	} else {
		$this->errno = NO_FILE;
		return FALSE;
	}

	if ($att_size>0) {
		$att['att_size'] = $att_size;
		$att['att_count'] = $att_count;
		return $att;
	} else {
		return false;
	}

} // end getServerFiles

/**
 * Check if the file is allowed for upload
 *
 * Check if either the extension or the file type (mime-type) is allowed in this
 * configuration
 * @param   string  $filename     the name of the file
 * @param   string  $mimetype     the mime type
 * @since     4.1
 * @access    public
 * @return    void
 * @throws
 */
function isAllowedFile ($filename, $mimetype) {
	global $bn_allowed_extensions, $bn_allowed_mimetypes;
	global $bn_banned_extensions, $bn_banned_mimetypes;

# First check allowed extensions
# ------------------------------
	$ext = strrchr ($filename, '.');
	if (!empty ($bn_allowed_extensions)) {
		$allowed_extensions = split(' ', $bn_allowed_extensions);
		if (is_array($allowed_extensions)) {
			$found = FALSE;
			reset ($allowed_extensions);
			while ( list (,$goodext) = each($allowed_extensions) ) {
				if ($ext == $goodext) {
					$found = TRUE;
					break;
				}
			}
			if (!$found) {
				return FALSE;   // extension not found in the list
			}
		}
	}

# Now deny banned extension
# -------------------------
	if (!empty ($bn_banned_extensions)) {
		$banned_extensions = split(' ', $bn_banned_extensions);
		if (is_array($banned_extensions)) {
			reset ($banned_extensions);
			while ( list (,$badext) = each($banned_extensions) ) {
				if ($ext == $badext) {
					return FALSE;   // extension denied from the list
				}
			}
		}
	}

# Now check mime-type
# -------------------
	list ($type, $subtype) = split ('/', $mimetype);

# check allowed mime-types
# ------------------------
	if (!empty ($bn_allowed_mimetypes)) {
		$allowed_mimetypes = split(' ', $bn_allowed_mimetypes);
		if (is_array($allowed_mimetypes)) {
			$found = FALSE;
			reset ($allowed_mimetypes);
			while ( list (,$mt) = each($allowed_mimetypes) ) {
				list ($good_type, $good_subtype) = split ('/', $mt);
				if ($type == $good_type) {
					if ( ($good_subtype == '*') || ($subtype == $good_subtype) ) {
						$found = TRUE;
						break;
					}
				}
			}
			if (!$found) {
				return FALSE;   // extension not found in the list
			}
		}
	}

# check denied mime-types
# -----------------------
	if (!empty ($bn_banned_mimetypes)) {
		$banned_mimetypes = split(' ', $bn_banned_mimetypes);
		if (is_array($banned_mimetypes)) {
			reset ($banned_mimetypes);
			while ( list (,$mt) = each($banned_mimetypes) ) {
				list ($bad_type, $bad_subtype) = split ('/', $mt);
				if ($type == $bad_type) {
					if (($bad_subtype == '*') || ($subtype == $bad_subtype)) {
						return FALSE;
					}
				}
			}
		}
	}

	return TRUE;
}

} // end class

?>
