<?php // -*-php-*-
rcs_id('$Id: UpLoad.php,v 1.25 2007/04/18 20:40:48 rurban Exp $');
/*
 Copyright 2003,2004,2007 $ThePhpWikiProgrammingTeam

 This file is part of PhpWiki.

 PhpWiki is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or
 (at your option) any later version.

 PhpWiki is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with PhpWiki; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

 */

/**
 * UpLoad:  Allow Administrator to upload files to a special directory,
 *          which should preferably be added to the InterWikiMap
 * Usage:   <?plugin UpLoad ?>
 * Author:  NathanGass <gass@iogram.ch>
 * Changes: ReiniUrban <rurban@x-ray.at>,
 *          qubit <rtryon@dartmouth.edu>
 * Note:    See also Jochen Kalmbach's plugin/UserFileManagement.php
 */

class WikiPlugin_UpLoad
extends WikiPlugin
{
    var $disallowed_extensions;
    // TODO: use PagePerms instead
    var $only_authenticated = true; // allow only authenticated users may upload.

    function getName () {
        return "UpLoad";
    }

    function getDescription () {
        return _("Upload files to the local InterWiki Upload:<filename>");
    }

    function getDefaultArguments() {
        return array('logfile'  => 'phpwiki-upload.log',
        	     // add a link of the fresh file automatically to the 
        	     // end of the page (or current page)
        	     'autolink' => true, 
        	     'page'     => '[pagename]',
        	     'size'     => 50,
        	     'mode'     => 'actionpage', // or edit
        	     );
    }

    function run($dbi, $argstr, &$request, $basepage) {
        $this->allowed_extensions = explode("\n",
"7z
avi
bmp
bz2
c
cfg
diff
doc
gif
h
ini
jpeg
jpg
kmz
mp3
patch
pdf
png
ppt
rar
tar
tar.gz
txt
xls
zip");
        $this->disallowed_extensions = explode("\n",
"ad[ep]
asd
ba[st]
chm
cmd
com
cgi
cpl
crt
dll
eml
exe
hlp
hta
in[fs]
isp
jse?
lnk
md[betw]
ms[cipt]
nws
ocx
ops
pcd
p[ir]f
php\d?
phtml
pl
py
reg
sc[frt]
sh[bsm]?
swf
url
vb[esx]?
vxd
ws[cfh]");
        //removed "\{[[:xdigit:]]{8}(?:-[[:xdigit:]]{4}){3}-[[:xdigit:]]{12}\}"

        $args = $this->getArgs($argstr, $request);
        extract($args);

        $file_dir = getUploadFilePath();
        $form = HTML::form(array('action'  => $request->getPostURL(),
                                 'enctype' => 'multipart/form-data',
                                 'method'  => 'post'));
        $contents = HTML::div(array('class' => 'wikiaction'));
        $contents->pushContent(HTML::input(array('type' => 'hidden',
                                                 'name' => 'MAX_FILE_SIZE',
                                                 'value'=> MAX_UPLOAD_SIZE)));
        $contents->pushContent(HTML::input(array('name' => 'userfile',
                                                 'type' => 'file',
                                                 'size' => $size)));
        if ($mode == 'edit') {
            $contents->pushContent(HTML::input(array('name' => 'action',
                                                     'type' => 'hidden',
                                                     'value'=> 'edit')));
            $contents->pushContent(HTML::raw(" "));
            $contents->pushContent(HTML::input(array('value' => _("Upload"),
                                                     'name'  => 'edit[upload]',
                                                     'type'  => 'submit')));
        } else {
            $contents->pushContent(HTML::raw(" "));
            $contents->pushContent(HTML::input(array('value' => _("Upload"),
                                                     'type'  => 'submit')));
        }
        $form->pushContent($contents);

        $message = HTML();
        if ($request->isPost() and $this->only_authenticated) {
            // Make sure that the user is logged in.
            $user = $request->getUser();
            if (!$user->isAuthenticated()) {
                $message->pushContent(HTML::h2(_("ACCESS DENIED: You must log in to upload files.")),
                                          HTML::br(),HTML::br());
                $result = HTML();
                $result->pushContent($form);
                $result->pushContent($message);
                return $result;
            }
        }
        
        $userfile = $request->getUploadedFile('userfile');
        if ($userfile) {
            $userfile_name = $userfile->getName();
            $userfile_name = trim(basename($userfile_name));
	    if (UPLOAD_USERDIR) {
		$file_dir .= $request->_user->_userid;
		if (!file_exists($file_dir))
		    mkdir($file_dir, 0775);
		$file_dir .= "/";
		$u_userfile = $request->_user->_userid . "/" . $userfile_name;
	    } else {
		$u_userfile = $userfile_name;
	    }
	    $u_userfile = preg_replace("/ /", "%20", $u_userfile);
            $userfile_tmpname = $userfile->getTmpName();
	    $err_header = HTML::h2(fmt("ERROR uploading '%s': ", $userfile_name));
            if (preg_match("/(\." . join("|\.", $this->disallowed_extensions) . ")(\.|\$)/i",
                           $userfile_name))
            {
            	$message->pushContent($err_header);
                $message->pushContent(fmt("Files with extension %s are not allowed.",
                                          join(", ", $this->disallowed_extensions)),HTML::br(),HTML::br());
            }
            elseif (! DISABLE_UPLOAD_ONLY_ALLOWED_EXTENSIONS and 
                    ! preg_match("/(\." . join("|\.", $this->allowed_extensions) . ")\$/i", 
                               $userfile_name))
            {
            	$message->pushContent($err_header);
                $message->pushContent(fmt("Only files with the extension %s are allowed.",
                                          join(", ", $this->allowed_extensions)),HTML::br(),HTML::br());
            }
            elseif (preg_match("/[^._a-zA-Z0-9- ]/", $userfile_name))
            {
            	$message->pushContent($err_header);
                $message->pushContent(_("Invalid filename. File names may only contain alphanumeric characters and dot, underscore, space or dash."),
                                      HTML::br(),HTML::br());
            }
            elseif (file_exists($file_dir . $userfile_name)) {
            	$message->pushContent($err_header);
                $message->pushContent(fmt("There is already a file with name %s uploaded.",
                                          $u_userfile),HTML::br(),HTML::br());
            }
            elseif ($userfile->getSize() > (MAX_UPLOAD_SIZE)) {
            	$message->pushContent($err_header);
                $message->pushContent(_("Sorry but this file is too big."),HTML::br(),HTML::br());
            }
            elseif (move_uploaded_file($userfile_tmpname, $file_dir . $userfile_name) or
                    (IsWindows() and rename($userfile_tmpname, $file_dir . $userfile_name))
                    )
            {
            	$interwiki = new PageType_interwikimap();
		$link = $interwiki->link("Upload:$u_userfile");
                $message->pushContent(HTML::h2(_("File successfully uploaded.")));
                $message->pushContent(HTML::ul(HTML::li($link)));

                // the upload was a success and we need to mark this event in the "upload log"
                if ($logfile) { 
                    $upload_log = $file_dir . basename($logfile);
                    $this->log($userfile, $upload_log, $message);
                }
                if ($autolink) {
                    require_once("lib/loadsave.php");
                    $pagehandle = $dbi->getPage($page);
                    if ($pagehandle->exists()) {// don't replace default contents
                        $current = $pagehandle->getCurrentRevision();
                        $version = $current->getVersion();
                        $text = $current->getPackedContent();
                        $newtext = $text . "\n* [Upload:$u_userfile]";
                        $meta = $current->_data;
                        $meta['summary'] = sprintf(_("uploaded %s"),$u_userfile);
                        $pagehandle->save($newtext, $version + 1, $meta);
                    }
                }
            }
            else {
            	$message->pushContent($err_header);
                $message->pushContent(HTML::br(),_("Uploading failed."),HTML::br());
            }
        }
        /*else {
            $message->pushContent(HTML::br(),HTML::br());
        }*/

        //$result = HTML::div( array( 'class' => 'wikiaction' ) );
        $result = HTML();
        $result->pushContent($form);
        $result->pushContent($message);
        return $result;
    }

    function log ($userfile, $upload_log, &$message) {
    	global $WikiTheme;
    	$user = $GLOBALS['request']->_user;
        if (!is_writable($upload_log)) {
            trigger_error(_("The upload logfile is not writable."), E_USER_WARNING);
        }
        elseif (!$log_handle = fopen ($upload_log, "a")) {
            trigger_error(_("Can't open the upload logfile."), E_USER_WARNING);
        }
        else {        // file size in KB; precision of 0.1
            $file_size = round(($userfile->getSize())/1024, 1);
            if ($file_size <= 0) {
                $file_size = "&lt; 0.1";
            }
            $userfile_name = $userfile->getName();
            fwrite($log_handle,
                   "\n"
                   . "<tr><td><a href=\"$userfile_name\">$userfile_name</a></td>"
                   . "<td align=\"right\">$file_size kB</td>"
                   . "<td>&nbsp;&nbsp;" . $WikiTheme->formatDate(time()) . "</td>"
                   . "<td>&nbsp;&nbsp;<em>" . $user->getId() . "</em></td></tr>");
            fclose($log_handle);
        }
        return;
    }

}

// $Log: UpLoad.php,v $
// Revision 1.25  2007/04/18 20:40:48  rurban
// added DISABLE_UPLOAD_ONLY_ALLOWED_EXTENSIONS
//
// Revision 1.24  2007/04/11 17:49:01  rurban
// Chgeck against .php\d, i.e. php3
//
// Revision 1.23  2007/04/08 12:43:45  rurban
// Important security fix!
// Disallow files like "deface.php.3" also. Those are actually in the wild!
//
// Revision 1.22  2007/02/17 14:16:56  rurban
// allow spaces in filenames
//
// Revision 1.21  2007/01/04 16:46:50  rurban
// Support UPLOAD_USERDIR
//
// Revision 1.20  2006/08/15 13:40:40  rurban
// help finding the file (should not be needed)
//
// Revision 1.19  2005/04/11 19:40:15  rurban
// Simplify upload. See https://sourceforge.net/forum/message.php?msg_id=3093651
// Improve UpLoad warnings.
// Move auth check before upload.
//
// Revision 1.18  2005/02/12 17:24:24  rurban
// locale update: missing . : fixed. unified strings
// proper linebreaks
//
// Revision 1.17  2004/11/09 08:15:50  rurban
// trim filename
//
// Revision 1.16  2004/10/21 19:03:37  rurban
// Be more stricter with uploads: Filenames may only contain alphanumeric
// characters. Patch #1037825
//
// Revision 1.15  2004/09/22 13:46:26  rurban
// centralize upload paths.
// major WikiPluginCached feature enhancement:
//   support _STATIC pages in uploads/ instead of dynamic getimg.php? subrequests.
//   mainly for debugging, cache problems and action=pdf
//
// Revision 1.14  2004/06/16 10:38:59  rurban
// Disallow refernces in calls if the declaration is a reference
// ("allow_call_time_pass_reference clean").
//   PhpWiki is now allow_call_time_pass_reference = Off clean,
//   but several external libraries may not.
//   In detail these libs look to be affected (not tested):
//   * Pear_DB odbc
//   * adodb oracle
//
// Revision 1.13  2004/06/14 11:31:39  rurban
// renamed global $Theme to $WikiTheme (gforge nameclash)
// inherit PageList default options from PageList
//   default sortby=pagename
// use options in PageList_Selectable (limit, sortby, ...)
// added action revert, with button at action=diff
// added option regex to WikiAdminSearchReplace
//
// Revision 1.12  2004/06/13 11:34:22  rurban
// fixed bug #969532 (space in uploaded filenames)
// improved upload error messages
//
// Revision 1.11  2004/06/11 09:07:30  rurban
// support theme-specific LinkIconAttr: front or after or none
//
// Revision 1.10  2004/04/12 10:19:18  rurban
// fixed copyright year
//
// Revision 1.9  2004/04/12 10:18:22  rurban
// removed the hairy regex line
//
// Revision 1.8  2004/04/12 09:12:22  rurban
// fix syntax errors
//
// Revision 1.7  2004/04/09 17:49:03  rurban
// Added PhpWiki RssFeed to Sidebar
// sidebar formatting
// some browser dependant fixes (old-browser support)
//
// Revision 1.6  2004/02/27 01:36:51  rurban
// autolink enabled
//
// Revision 1.5  2004/02/27 01:24:43  rurban
// use IntwerWiki links for uploaded file.
// autolink to page prepared, but not yet ready
//
// Revision 1.4  2004/02/21 19:12:59  rurban
// patch by Sascha Carlin
//
// Revision 1.3  2004/02/17 12:11:36  rurban
// added missing 4th basepage arg at plugin->run() to almost all plugins. This caused no harm so far, because it was silently dropped on normal usage. However on plugin internal ->run invocations it failed. (InterWikiSearch, IncludeSiteMap, ...)
//
// Revision 1.2  2004/01/26 09:18:00  rurban
// * changed stored pref representation as before.
//   the array of objects is 1) bigger and 2)
//   less portable. If we would import packed pref
//   objects and the object definition was changed, PHP would fail.
//   This doesn't happen with an simple array of non-default values.
// * use $prefs->retrieve and $prefs->store methods, where retrieve
//   understands the interim format of array of objects also.
// * simplified $prefs->get() and fixed $prefs->set()
// * added $user->_userid and class '_WikiUser' portability functions
// * fixed $user object ->_level upgrading, mostly using sessions.
//   this fixes yesterdays problems with loosing authorization level.
// * fixed WikiUserNew::checkPass to return the _level
// * fixed WikiUserNew::isSignedIn
// * added explodePageList to class PageList, support sortby arg
// * fixed UserPreferences for WikiUserNew
// * fixed WikiPlugin for empty defaults array
// * UnfoldSubpages: added pagename arg, renamed pages arg,
//   removed sort arg, support sortby arg
//
// Revision 1.1  2003/11/04 18:41:41  carstenklapp
// New plugin which was submitted to the mailing list some time
// ago. (This is the best UpLoad function I have seen for PhpWiki so
// far. Cleaned up text formatting and typos from the version on the
// mailing list. Still needs a few adjustments.)

// (c-file-style: "gnu")
// Local Variables:
// mode: php
// tab-width: 8
// c-basic-offset: 4
// c-hanging-comment-ender-p: nil
// indent-tabs-mode: nil
// End:
?>
