'upload', 'download' => 'read')); SDVA($HandleActions, array('upload_upsmrkp' => 'HandleUpload_upsmrkp', 'postupload_upsmrkp' => 'HandlePostUpload_upsmrkp', 'download_upsmrkp' => 'HandleDownload_upsmrkp')); SDVA($HandleAuth, array('upload_upsmrkp' => $HandleAuth['upload'], // inherit as much as possible from the std upload options 'download_upsmrkp' => $HandleAuth['download'])); SDV($HandleAuth['postupload_upsmrkp'], $HandleAuth['upload_upsmrkp']); function UPSMRKPbendGlobalVars() { global $UploadNameChars, $UPSMRKPuploadNameChars_save, $UploadFileFmt, $UPSMRKPuploadFileFmt_save, $PageUploadFmt, $UPSMRKPpageUploadFmt_save, $UploadDir; SDV($UploadNameChars, "-\\w. "); $UPSMRKPuploadNameChars_save = $UploadNameChars; $UploadNameChars .= "\\/"; // add slash as allow char $UPSMRKPuploadFileFmt_save = $UploadFileFmt; $UploadFileFmt = $UploadDir; $UPSMRKPpageUploadFmt_save = $PageUploadFmt; $PageUploadFmt = str_replace("value='postupload'", "value='postupload_upsmrkp'", $PageUploadFmt); } function UPSMRKPrestoreGlobalVars() { $UploadNameChars = $UPSMRKPuploadNameChars_save; $UploadFileFmt = $UPSMRKPuploadFileFmt_save; $PageUploadFmt = $UPSMRKPpageUploadFmt_save; } // The following three functions expect in _REQUEST['upname'] a filename // with absolute path interpreted as starting in the upload root dir. // (Like /MyGroup/MyPage//myfile.txt) // The additional authorization check in the wrappers is needed to prevent // attacks via playing nice in the page argument while targeting something else // in the upname argument. function HandleDownload_upsmrkp($pagename, $auth = 'read') { UPSMRKPbendGlobalVars(); // clone behaviour to get exactly the same upload path: $upname = MakeUploadName("DummyGroup.DummyPage", @$_REQUEST['upname']); $filepath = FmtPageName("$UploadFileFmt/$upname", $pagename); // with bent vars already! // infer the related page from it and get authorization for the target: $referencedPagename = UPSMRKPgetReferencedPagename($filepath); $page = RetrieveAuthPage($referencedPagename, $auth, true, READPAGE_CURRENT); if (!$page) Abort("?cannot read $pagename"); // if everything ok, proceed along the usual path: $res = HandleDownload($pagename, $auth); UPSMRKPrestoreGlobalVars(); return $res; } function HandleUpload_upsmrkp($pagename, $auth = 'upload') { UPSMRKPbendGlobalVars(); $upname = MakeUploadName("DummyGroup.DummyPage", @$_REQUEST['upname']); $filepath = FmtPageName("$UploadFileFmt/$upname", $pagename); // with bent vars already! $referencedPagename = UPSMRKPgetReferencedPagename($filepath); $page = RetrieveAuthPage($referencedPagename, $auth, true, READPAGE_CURRENT); if (!$page) Abort("?cannot upload to $pagename"); $res = HandleUpload($pagename, $auth); UPSMRKPrestoreGlobalVars(); return $res; } function HandlePostUpload_upsmrkp($pagename, $auth = 'upload') { UPSMRKPbendGlobalVars(); $upname = $_REQUEST['upname']; if ($upname=='') $upname=$uploadfile['name']; $upname = MakeUploadName("DummyGroup.DummyPage",$upname); $filepath = FmtPageName("$UploadFileFmt/$upname",$pagename); $referencedPagename = UPSMRKPgetReferencedPagename($filepath); $page = RetrieveAuthPage($referencedPagename, $auth, true, READPAGE_CURRENT); if (!$page) Abort("?cannot upload to $pagename"); $res = HandlePostUpload($pagename, $auth); UPSMRKPrestoreGlobalVars(); return $res; } //=============================================================== function getConsolidatedPath($initialPath, $fileReference) { return getConsolidatedPathFromParts($initialPath, dirname($fileReference), basename($fileReference)); } function getConsolidatedPathFromParts($initialPath, $pathPart, $basePart) { // Expects non-empty initialPath without trailing slashes. // Returns final path of the file reference "$pathPart/$basePart" considering // cases with relative and absolute path referencing. // Ex.: // bla/blub . myfile.txt -> bla/blub/myfile.txt // bla/blub .. myfile.txt -> bla/myfile.txt // bla/blub ../foo myfile.txt -> bla/foo/myfile.txt // bla/blub / myfile.txt -> /myfile.txt // a.s.o. // Known restriction: path components may not include directories whose names // contain dots. if ($pathPart[0] == '/') { $x = "$pathPart/$basePart"; } else { $x = "$initialPath/$pathPart/$basePart"; $x = str_replace("/./", "/", $x); while (($y = preg_replace('#[^\/\.]+/\.\./#', '', $x)) != $x) $x = $y; } $x = preg_replace('#^\./#', '', $x); $x = str_replace("//", "/", $x); return $x; } function UPSMRKPgetReferencedPagename($uploadPath) { global $DefaultGroup, $DefaultName; if (preg_match('#^/([^/.]+)/([^/.]+)/.*#', $uploadPath, $matches)) { // (it could make sense to drop the dots from the disallowed characters in the regexp here and below) $referencedPagename = $matches[1] . '.' . $matches[2]; } else if (preg_match('#^/([^/.]+)/.*#', $uploadPath, $matches)) { $referencedPagename = $matches[1] . '.' . $DefaultName; } else { $referencedPagename = "$DefaultGroup.$DefaultName"; } return $referencedPagename; } /* Storage oriented file referencing: if the given Uploads argument is a sole filename (no slashes), interpret its location being in the respective upload dir of the page where the markup is located. If a path is given and is relative, then dto.. If path is given and absolute, interpret it with respect to the upload root dir as root directory: myfile.txt -> uploads/MyGroup/MyPage/myfile.txt ./myfile.txt -> uploads/MyGroup/MyPage/myfile.txt mysubdir/myfile.txt -> uploads/MyGroup/MyPage/mysubdir/myfile.txt ../myfile.txt -> uploads/MyGroup/myfile.txt ../MyOtherPage/myfile.txt -> uploads/MyGroup/MyOtherPage/myfile.txt /MyGroup/MyPage/myfile.txt -> uploads/MyGroup/MyPage/myfile.txt /myfile.txt -> uploads/myfile.txt a.s.o. This was for upload prefix format set to /$Group/$Page. When however the default setting is used (/$Group), things behave accordingly. In particular, the default file location is in the respective group upload dir, so myfile.txt -> uploads/MyGroup/myfile.txt ../MyOtherGroup/myfile.txt -> uploads/MyOtherGroup/myfile.txt For evaluating the authorization to upload/download a file, the file must be associated to some page. If the file is located in an upload dir uploads/MyGroup/MyPage, the page MyGroup.MyPage is interpreted as "mother" page. If in uploads/MyGroup, then Mygroup.$DefaultName is used. In all other cases $DefaultGroup.$DefaultName. */ function LinkUpload_upsmrkp($pagename, $imap, $path, $title, $txt, $fmt=NULL) { global $UploadDir, $UploadPrefixPathFmt, $FmtV, $LinkUploadCreateFmt, $UploadUrlFmt, $EnableDirectDownload; // Note: at this point pagename refers to the page where the markup "Uploads:" is // _located_, not where the file reference points to. Therefore infer this from the // path info in $path. $upname = MakeUploadName("DummyGroup.DummyPage", basename($path)); //pagename argument never used // some character sanitization on the whole path: (note that this needs to preserve the slashes (all slashes!)) $path = preg_replace('/[^-\w. \/]/', '', $path); // get referenced dir: $initialpath = FmtPageName("$UploadPrefixPathFmt", $pagename); // where we start from when interpreting the path $filepath_pre = getConsolidatedPathFromParts($initialpath, dirname($path), $upname); // stop if it tries to go "over the top": if (strpos($filepath_pre, "../") !== false) return ""Uploads:" failed: Referenced file target lies beyond the allowed directory tree."; // infer referenced page to base the authorization on it: $referencedPagename = UPSMRKPgetReferencedPagename($filepath_pre); // up to here the filepath did not contain the path part to the upload root dir yet, so amend: $filepath = "$UploadDir$filepath_pre"; $FmtV['$LinkUpload'] = FmtPageName("\$PageUrl?action=upload_upsmrkp&upname=$filepath_pre", $referencedPagename); $FmtV['$LinkText'] = $txt; if (!file_exists($filepath)) return FmtPageName($LinkUploadCreateFmt, $referencedPagename); $path = PUE(FmtPageName(IsEnabled($EnableDirectDownload, 1) ? "$UploadUrlFmt/$filepath_pre" : "{\$PageUrl}?action=download_upsmrkp&upname=$filepath_pre", $referencedPagename)); return LinkIMap($pagename, $imap, $path, $title, $txt, $fmt); }