1<?php if (!defined('PmWiki')) exit(); 2/* Copyright 2004-2021 Patrick R. Michaud (pmichaud@pobox.com) 3 This file is part of PmWiki; you can redistribute it and/or modify 4 it under the terms of the GNU General Public License as published 5 by the Free Software Foundation; either version 2 of the License, or 6 (at your option) any later version. See pmwiki.php for full details. 7 8 This script adds upload capabilities to PmWiki. Uploads can be 9 enabled by setting 10 $EnableUpload = 1; 11 in config.php. In addition, an upload password must be set, as 12 the default is to lock uploads. In some configurations it may also 13 be necessary to set values for $UploadDir and $UploadUrlFmt, 14 especially if any form of URL rewriting is being performed. 15 See the PmWiki.UploadsAdmin page for more information. 16 17 Script maintained by Petko YOTOV www.pmwiki.org/petko 18*/ 19 20## $EnableUploadOverwrite determines if we allow previously uploaded 21## files to be overwritten. 22SDV($EnableUploadOverwrite,1); 23 24## $UploadExts contains the list of file extensions we're willing to 25## accept, along with the Content-Type: value appropriate for each. 26SDVA($UploadExts,array( 27 'gif' => 'image/gif', 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 28 'png' => 'image/png', 'apng' => 'image/apng', 'bmp' => 'image/bmp', 'ico' => 'image/x-icon', 29 'wbmp'=> 'image/vnd.wap.wbmp', 'xcf' => 'image/x-xcf', 'webp' => 'image/webp', 30 'svg' => 'image/svg+xml', 'svgz' => 'image/svg+xml', 31 'mp3' => 'audio/mpeg', 'au' => 'audio/basic', 'wav' => 'audio/x-wav', 32 'ogg' => 'audio/ogg', 'flac' => 'audio/x-flac', 'opus' => 'audio/opus', 33 'ogv' => 'video/ogg', 'mp4' => 'video/mp4', 'webm' => 'video/webm', 34 'mpg' => 'video/mpeg', 'mpeg' => 'video/mpeg', 'mkv' => 'video/x-matroska', 35 'm4v' => 'video/x-m4v', '3gp' => 'video/3gpp', 36 'mov' => 'video/quicktime', 'qt' => 'video/quicktime', 37 'wmf' => 'text/plain', 'avi' => 'video/x-msvideo', 38 'zip' => 'application/zip', '7z' => 'application/x-7z-compressed', 39 'gz' => 'application/x-gzip', 'tgz' => 'application/x-gzip', 40 'rpm' => 'application/x-rpm', 41 'hqx' => 'application/mac-binhex40', 'sit' => 'application/x-stuffit', 42 'doc' => 'application/msword', 'ppt' => 'application/vnd.ms-powerpoint', 43 'xls' => 'application/vnd.ms-excel', 'mdb' => 'text/plain', 44 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 45 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 46 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 47 'exe' => 'application/octet-stream', 48 'pdf' => 'application/pdf', 'psd' => 'text/plain', 49 'ps' => 'application/postscript', 'ai' => 'application/postscript', 50 'eps' => 'application/postscript', 51 'htm' => 'text/html', 'html' => 'text/html', 'css' => 'text/css', 52 'fla' => 'application/x-shockwave-flash', 53 'swf' => 'application/x-shockwave-flash', 54 'txt' => 'text/plain', 'rtf' => 'application/rtf', 55 'tex' => 'application/x-tex', 'dvi' => 'application/x-dvi', 56 'odt' => 'application/vnd.oasis.opendocument.text', 57 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', 58 'odp' => 'application/vnd.oasis.opendocument.presentation', 59 'odg' => 'application/vnd.oasis.opendocument.graphics', 60 'epub'=> 'application/epub+zip', 61 'kml' => 'application/vnd.google-earth.kml+xml', 62 'kmz' => 'application/vnd.google-earth.kmz', 63 'vtt' => 'text/vtt', 64 '' => 'text/plain')); 65 66# Array containing forbidden strings in a filename, array('.php', '.cgi') 67SDV($UploadBlacklist, array()); 68 69SDV($UploadMaxSize,50000); 70SDV($UploadPrefixQuota,0); 71SDV($UploadDirQuota,0); 72foreach($UploadExts as $k=>$v) 73 if (!isset($UploadExtSize[$k])) $UploadExtSize[$k]=$UploadMaxSize; 74 75SDV($UploadDir,'uploads'); 76SDV($UploadPermAdd,0444); 77SDV($UploadPermSet,0); 78SDV($UploadPrefixFmt,'/$Group'); 79SDV($UploadFileFmt,"$UploadDir$UploadPrefixFmt"); 80$v = preg_replace('#^/(.*/)#', '', $UploadDir); 81SDV($UploadUrlFmt,preg_replace('#/[^/]*$#', "/$v", $PubDirUrl, 1)); 82SDV($LinkUploadCreateFmt, "<a rel='nofollow' class='createlinktext' href='\$LinkUpload'>\$LinkText</a><a rel='nofollow' class='createlink' href='\$LinkUpload'> Δ</a>"); 83SDVA($ActionTitleFmt, array('upload' => '| $[Attach]')); 84 85 86if ($EnablePostAuthorRequired) 87 SDV($EnableUploadAuthorRequired, $EnablePostAuthorRequired); 88 89SDV($PageUploadFmt,array(" 90 <div id='wikiupload'> 91 <h2 class='wikiaction'>$[Attachments for] {\$FullName}</h2> 92 <h3>\$UploadResult</h3> 93 <form enctype='multipart/form-data' action='{\$PageUrl}?action=postupload' method='post'> 94 <input type='hidden' name='n' value='{\$FullName}' /> 95 <input type='hidden' name='action' value='postupload' /> 96 <input type='hidden' name='\$TokenName' value='\$TokenValue' /> 97 <table border='0'> 98 <tr><td align='right'>$[File to upload:]</td><td><input 99 name='uploadfile' type='file' required='required' /></td></tr> 100 <tr><td align='right'>$[Name attachment as:]</td> 101 <td><input type='text' name='upname' value='\$UploadName' /> 102 </td></tr> 103 <tr><td align='right'>$[Uploader]:</td> 104 <td><input type='text' name='author' value='\$UploadAuthor' \$UploadAuthorRequired /> 105 <input type='submit' value=' $[Upload] ' /> 106 </td></tr></table></form></div>", 107 'wiki:$[{$SiteGroup}/UploadQuickReference]')); 108XLSDV('en',array( 109 'ULsuccess' => 'successfully uploaded', 110 'ULinvalidtoken' => 'Token invalid or missing.', 111 'ULauthorrequired' => 'An author name is required.', 112 'ULbadname' => 'invalid attachment name', 113 'ULbadtype' => '\'$upext\' is not an allowed file extension', 114 'ULtoobig' => 'file is larger than maximum allowed by webserver', 115 'ULtoobigext' => 'file is larger than allowed maximum of $upmax 116 bytes for \'$upext\' files', 117 'ULpartial' => 'incomplete file received', 118 'ULnofile' => 'no file uploaded', 119 'ULexists' => 'file with that name already exists', 120 'ULpquota' => 'group quota exceeded', 121 'ULtquota' => 'upload quota exceeded')); 122SDV($PageAttributes['passwdupload'],'$[Set new upload password:]'); 123SDV($DefaultPasswords['upload'],'@lock'); 124SDV($AuthCascade['upload'], 'read'); 125SDV($FmtPV['$PasswdUpload'], 'PasswdVar($pn, "upload")'); 126 127Markup('attachlist', 'directives', 128 '/\\(:attachlist\\s*(.*?):\\)/i', 129 "MarkupFmtUploadList"); 130function MarkupFmtUploadList($m) { 131 extract($GLOBALS["MarkupToHTML"]); # get $pagename 132 return Keep('<ul>'.FmtUploadList($pagename,$m[1]).'</ul>'); 133} 134SDV($GUIButtons['attach'], array(220, 'Attach:', '', '$[file.ext]', 135 '$GUIButtonDirUrlFmt/attach.gif"$[Attach file]"')); 136SDV($LinkFunctions['Attach:'], 'LinkUpload'); 137SDV($IMap['Attach:'], '$1'); 138SDVA($HandleActions, array('upload' => 'HandleUpload', 139 'postupload' => 'HandlePostUpload', 140 'download' => 'HandleDownload')); 141SDVA($HandleAuth, array('upload' => 'upload', 142 'download' => 'read')); 143SDV($HandleAuth['postupload'], $HandleAuth['upload']); 144SDV($UploadVerifyFunction, 'UploadVerifyBasic'); 145 146function MakeUploadName($pagename,$x) { 147 global $UploadNameChars, $MakeUploadNamePatterns; 148 SDV($UploadNameChars, "-\\w. "); 149 SDV($MakeUploadNamePatterns, array( 150 "/[^$UploadNameChars]/" => '', 151 '/(\\.[^.]*)$/' => 'cb_tolower', 152 '/^[^[:alnum:]_]+/' => '', 153 '/[^[:alnum:]_]+$/' => '')); 154 return PPRA($MakeUploadNamePatterns, $x); 155} 156 157function LinkUpload($pagename, $imap, $path, $alt, $txt, $fmt=NULL) { 158 global $FmtV, $UploadFileFmt, $LinkUploadCreateFmt, 159 $UploadUrlFmt, $UploadPrefixFmt, $EnableDirectDownload; 160 if (preg_match('!^(.*)/([^/]+)$!', $path, $match)) { 161 $pagename = MakePageName($pagename, $match[1]); 162 $path = $match[2]; 163 } 164 $upname = MakeUploadName($pagename, $path); 165 $encname = rawurlencode($upname); 166 $filepath = FmtPageName("$UploadFileFmt/$upname", $pagename); 167 $FmtV['$LinkUpload'] = 168 FmtPageName("\$PageUrl?action=upload&upname=$encname", $pagename); 169 $FmtV['$LinkText'] = $txt; 170 if (!file_exists($filepath)) 171 return FmtPageName($LinkUploadCreateFmt, $pagename); 172 $path = PUE(FmtPageName(IsEnabled($EnableDirectDownload, 1) 173 ? "$UploadUrlFmt$UploadPrefixFmt/$encname" 174 : "{\$PageUrl}?action=download&upname=$encname", 175 $pagename)); 176 return LinkIMap($pagename, $imap, $path, $alt, $txt, $fmt); 177} 178 179# Authenticate group downloads with the group password 180function UploadAuth($pagename, $auth, $cache=0){ 181 global $GroupAttributesFmt, $EnableUploadGroupAuth; 182 if (IsEnabled($EnableUploadGroupAuth,0)){ 183 SDV($GroupAttributesFmt,'$Group/GroupAttributes'); 184 $pn_upload = FmtPageName($GroupAttributesFmt, $pagename); 185 } else $pn_upload = $pagename; 186 $page = RetrieveAuthPage($pn_upload, $auth, true, READPAGE_CURRENT); 187 if (!$page) Abort("?No '$auth' permissions for $pagename"); 188 if ($cache) PCache($pn_upload,$page); 189 return true; 190} 191 192function UploadSetVars($pagename) { 193 global $Author, $FmtV, $UploadExtMax, $EnableReadOnly, 194 $EnablePostAuthorRequired, $EnableUploadAuthorRequired; 195 $FmtV['$UploadName'] = MakeUploadName($pagename,@$_REQUEST['upname']); 196 $FmtV['$UploadAuthor'] = PHSC($Author, ENT_QUOTES); 197 $upresult = PHSC(@$_REQUEST['upresult']); 198 $uprname = PHSC(@$_REQUEST['uprname']); 199 $FmtV['$upext'] = PHSC(@$_REQUEST['upext']); 200 $FmtV['$upmax'] = PHSC(@$_REQUEST['upmax']); 201 $FmtV['$TokenValue'] = pmtoken(); 202 $FmtV['$UploadResult'] = ($upresult) ? 203 FmtPageName("<i>$uprname</i>: $[UL$upresult]",$pagename) : 204 (@$EnableReadOnly ? XL('Cannot modify site -- $EnableReadOnly is set'): ''); 205 $FmtV['$UploadAuthorRequired'] = @$EnableUploadAuthorRequired ? 206 'required="required"' : ''; 207} 208 209function HandleUpload($pagename, $auth = 'upload') { 210 global $HandleUploadFmt,$PageStartFmt,$PageEndFmt,$PageUploadFmt; 211 UploadAuth($pagename, $auth, 1); 212 UploadSetVars($pagename); 213 SDV($HandleUploadFmt,array(&$PageStartFmt,&$PageUploadFmt,&$PageEndFmt)); 214 PrintFmt($pagename,$HandleUploadFmt); 215} 216 217function HandleDownload($pagename, $auth = 'read') { 218 global $UploadFileFmt, $UploadExts, $DownloadDisposition, $EnableIMSCaching; 219 SDV($DownloadDisposition, "inline"); 220 UploadAuth($pagename, $auth); 221 $upname = MakeUploadName($pagename, @$_REQUEST['upname']); 222 $filepath = FmtPageName("$UploadFileFmt/$upname", $pagename); 223 if (!$upname || !file_exists($filepath)) { 224 header("HTTP/1.0 404 Not Found"); 225 Abort("?requested file not found"); 226 exit(); 227 } 228 if (IsEnabled($EnableIMSCaching, 0)) { 229 header('Cache-Control: private'); 230 header('Expires: '); 231 $filelastmod = gmdate('D, d M Y H:i:s \G\M\T', filemtime($filepath)); 232 if (@$_SERVER['HTTP_IF_MODIFIED_SINCE'] == $filelastmod) 233 { header("HTTP/1.0 304 Not Modified"); exit(); } 234 header("Last-Modified: $filelastmod"); 235 } 236 preg_match('/\\.([^.]+)$/',$filepath,$match); 237 if ($UploadExts[@$match[1]]) 238 header("Content-Type: {$UploadExts[@$match[1]]}"); 239 $fsize = $length = filesize($filepath); 240 $end = $fsize-1; 241 header("Accept-Ranges: bytes"); 242 if (@$_SERVER['HTTP_RANGE']) { 243 if(! preg_match('/^\\s*bytes\\s*=\\s*(\\d*)\\s*-\\s*(\\d*)\\s*$/i', $_SERVER['HTTP_RANGE'], $r) 244 || intval($r[1])>$end 245 || intval($r[2])>$end 246 || ($r[2] && intval($r[1])>intval($r[2])) 247 ) { 248 header('HTTP/1.1 416 Requested Range Not Satisfiable'); 249 header("Content-Range: bytes 0-$end/$fsize"); 250 exit; 251 } 252 if ($r[2]=='') $r[2] = $end; 253 if ($r[1]=='') $r[1] = $end - $r[2]; 254 $length = $r[2] - $r[1] + 1; 255 header('HTTP/1.1 206 Partial Content'); 256 header("Content-Range: bytes $r[1]-$r[2]/$fsize"); 257 } 258 else { 259 $r = array( null, 0, $end); 260 } 261 header("Content-Length: $length"); 262 header("Content-Disposition: $DownloadDisposition; filename=\"$upname\""); 263 $fp = fopen($filepath, "rb"); 264 if ($fp) { 265 $bf = 8192; 266 fseek($fp, $r[1]); 267 while (!feof($fp) && ($pos = ftell($fp)) <= $r[2]) { 268 $bf = max($bf, $r[2] - $pos + 1); 269 echo fread($fp, $bf); 270 flush(); 271 } 272 fclose($fp); 273 } 274 exit(); 275} 276 277function HandlePostUpload($pagename, $auth = 'upload') { 278 global $UploadVerifyFunction, $UploadFileFmt, $LastModFile, 279 $EnableUploadVersions, $Now, $RecentUploadsFmt, $FmtV, 280 $NotifyItemUploadFmt, $NotifyItemFmt, $IsUploadPosted, 281 $UploadRedirectFunction, $UploadPermAdd, $UploadPermSet, 282 $EnableReadOnly; 283 284 if (IsEnabled($EnableReadOnly, 0)) 285 Abort('Cannot modify site -- $EnableReadOnly is set', 'readonly'); 286 287 UploadAuth($pagename, $auth); 288 $uploadfile = $_FILES['uploadfile']; 289 $upname = @$_REQUEST['upname']; 290 if ($upname=='') $upname=$uploadfile['name']; 291 $upname = MakeUploadName($pagename,$upname); 292 if (!function_exists($UploadVerifyFunction)) 293 Abort('?no UploadVerifyFunction available'); 294 $filepath = FmtPageName("$UploadFileFmt/$upname",$pagename); 295 $result = $UploadVerifyFunction($pagename,$uploadfile,$filepath); 296 if ($result=='') { 297 $filedir = preg_replace('#/[^/]*$#','',$filepath); 298 mkdirp($filedir); 299 if (IsEnabled($EnableUploadVersions, 0)) 300 @rename($filepath, "$filepath,$Now"); 301 if (!move_uploaded_file($uploadfile['tmp_name'],$filepath)) 302 { Abort("?cannot move uploaded file to $filepath"); return; } 303 fixperms($filepath, $UploadPermAdd, $UploadPermSet); 304 if ($LastModFile) { touch($LastModFile); fixperms($LastModFile); } 305 $result = "upresult=success"; 306 $FmtV['$upname'] = $upname; 307 $FmtV['$upsize'] = $uploadfile['size']; 308 if (IsEnabled($RecentUploadsFmt, 0)) { 309 PostRecentChanges($pagename, '', '', $RecentUploadsFmt); 310 } 311 if (IsEnabled($NotifyItemUploadFmt, 0) && function_exists('NotifyUpdate')) { 312 $NotifyItemFmt = $NotifyItemUploadFmt; 313 $IsUploadPosted = 1; 314 register_shutdown_function('NotifyUpdate', $pagename, getcwd()); 315 } 316 } 317 $FmtV['$upresult'] = $result; 318 SDV($UploadRedirectFunction, 'Redirect'); 319 $UploadRedirectFunction($pagename,"{\$PageUrl}?action=upload&uprname=$upname&$result"); 320} 321 322function UploadVerifyBasic($pagename,$uploadfile,$filepath) { 323 global $EnableUploadOverwrite,$UploadExtSize,$UploadPrefixQuota, 324 $UploadDirQuota,$UploadDir, $UploadBlacklist, 325 $Author, $EnablePostAuthorRequired, $EnableUploadAuthorRequired; 326 327 if(! AutoCheckToken()) { 328 return 'upresult=invalidtoken'; 329 } 330 331 if (IsEnabled($EnableUploadAuthorRequired,0) && !$Author) 332 return 'upresult=authorrequired'; 333 334 if (count($UploadBlacklist)) { 335 $tmp = explode("/", $filepath); 336 $upname = strtolower(end($tmp)); 337 foreach($UploadBlacklist as $needle) { 338 if (strpos($upname, $needle)!==false) return 'upresult=badname'; 339 } 340 } 341 if (!$EnableUploadOverwrite && file_exists($filepath)) 342 return 'upresult=exists'; 343 preg_match('/\\.([^.\\/]+)$/',$filepath,$match); $ext=@$match[1]; 344 $maxsize = $UploadExtSize[$ext]; 345 if ($maxsize<=0) return "upresult=badtype&upext=$ext"; 346 if ($uploadfile['size']>$maxsize) 347 return "upresult=toobigext&upext=$ext&upmax=$maxsize"; 348 switch (@$uploadfile['error']) { 349 case 1: return 'upresult=toobig'; 350 case 2: return 'upresult=toobig'; 351 case 3: return 'upresult=partial'; 352 case 4: return 'upresult=nofile'; 353 } 354 if (!is_uploaded_file($uploadfile['tmp_name'])) return 'upresult=nofile'; 355 $filedir = preg_replace('#/[^/]*$#','',$filepath); 356 if ($UploadPrefixQuota && 357 (dirsize($filedir)-@filesize($filepath)+$uploadfile['size']) > 358 $UploadPrefixQuota) return 'upresult=pquota'; 359 if ($UploadDirQuota && 360 (dirsize($UploadDir)-@filesize($filepath)+$uploadfile['size']) > 361 $UploadDirQuota) return 'upresult=tquota'; 362 return ''; 363} 364 365function dirsize($dir) { 366 $size = 0; 367 $dirp = @opendir($dir); 368 if (!$dirp) return 0; 369 while (($file=readdir($dirp)) !== false) { 370 if ($file[0]=='.') continue; 371 if (is_dir("$dir/$file")) $size+=dirsize("$dir/$file"); 372 else $size+=filesize("$dir/$file"); 373 } 374 closedir($dirp); 375 return $size; 376} 377 378function FmtUploadList($pagename, $args) { 379 global $UploadDir, $UploadPrefixFmt, $UploadUrlFmt, $EnableUploadOverwrite, 380 $TimeFmt, $EnableDirectDownload, $IMapLinkFmt, $UrlLinkFmt, $FmtV; 381 382 $opt = ParseArgs($args); 383 if (@$opt[''][0]) $pagename = MakePageName($pagename, $opt[''][0]); 384 385 $matchfnames = ''; 386 if (@$opt['names'] ) $matchfnames = $opt['names']; 387 if (@$opt['ext']) 388 $matchfnames .= FixGlob($opt['ext'], '$1*.$2'); 389 390 $uploaddir = FmtPageName("$UploadDir$UploadPrefixFmt", $pagename); 391 $uploadurl = FmtPageName(IsEnabled($EnableDirectDownload, 1) 392 ? "$UploadUrlFmt$UploadPrefixFmt/" 393 : "\$PageUrl?action=download&upname=", 394 $pagename); 395 396 $dirp = @opendir($uploaddir); 397 if (!$dirp) return ''; 398 $filelist = array(); 399 while (($file=readdir($dirp)) !== false) { 400 if ($file[0] == '.') continue; 401 if ($matchfnames && ! MatchNames($file, $matchfnames)) continue; 402 $filelist[$file] = rawurlencode($file); 403 } 404 closedir($dirp); 405 $out = array(); 406 natcasesort($filelist); 407 $overwrite = ''; 408 $fmt = IsEnabled($IMapLinkFmt['Attach:'], $UrlLinkFmt); 409 foreach($filelist as $file=>$encfile) { 410 $FmtV['$LinkUrl'] = PUE("$uploadurl$encfile"); 411 $FmtV['$LinkText'] = $file; 412 $FmtV['$LinkUpload'] = 413 FmtPageName("\$PageUrl?action=upload&upname=$encfile", $pagename); 414 $stat = stat("$uploaddir/$file"); 415 if ($EnableUploadOverwrite) 416 $overwrite = FmtPageName("<a rel='nofollow' class='createlink' 417 href='\$LinkUpload'> Δ</a>", 418 $pagename); 419 $lnk = FmtPageName($fmt, $pagename); 420 $out[] = "<li> $lnk$overwrite ... ". 421 number_format($stat['size']) . " bytes ... " . 422 strftime($TimeFmt, $stat['mtime']) . "</li>"; 423 } 424 return implode("\n",$out); 425} 426 427# this adds (:if [!]attachments filepattern pagename:) to the markup 428$Conditions['attachments'] = "AttachExist(\$pagename, \$condparm)"; 429function AttachExist($pagename, $condparm='*') { 430 global $UploadFileFmt; 431 @list($fpat, $pn) = explode(' ', $condparm, 2); 432 $pn = ($pn > '') ? MakePageName($pagename, $pn) : $pagename; 433 434 $uploaddir = FmtPageName($UploadFileFmt, $pn); 435 $flist = array(); 436 $dirp = @opendir($uploaddir); 437 if ($dirp) { 438 while (($file = readdir($dirp)) !== false) 439 if ($file[0] != '.') $flist[] = $file; 440 closedir($dirp); 441 $flist = MatchNames($flist, $fpat); 442 } 443 return count($flist); 444} 445