<?php if (!defined('PmWiki')) exit();

/*
  UploadsMarkup.php ver 1.1-beta2   --   A pmwiki cookbook recipe.

  Adapted from scripts/upload.php.

  Provides a markup similar to "Attach:", however with a storage-oriented way of
  referencing the file "target". Allowed are relative and absolute path components
  with respect to the markup location page and the upload root dir, respectively.

  See www.pmwiki.org/wiki/Cookbook/UploadsMarkup for details.

  Dec 2007, ThomasP
*/

$RecipeInfo['UploadsMarkup']['Version'] = '2007-12-19';
$RecipeInfo['UploadsMarkup']['Author'] = 'ThomasP';

SDV($UploadPrefixPathFmt, $UploadPrefixFmt);

SDV($LinkFunctions['Uploads:'], 'LinkUpload_upsmrkp');
SDV($IMap['Uploads:'], '$1');

SDVA($HandleAuth, array('upload' => '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 "&quot;Uploads:&quot; 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&amp;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&amp;upname=$filepath_pre",
              $referencedPagename));

  return LinkIMap($pagename, $imap, $path, $title, $txt, $fmt);
}