<?php
// Stands in for Ivalua/Docebo's
//    /learn/v1/filerepo/downloadCourseFile?course_id=..&file_id=..&access_token=..
//
// In the field capture that URL does not serve the file itself. It answers
// with a redirect chain that ends on a presigned S3 object:
//
//    downloadCourseFile?...            302
//      -> (intermediate)               302
//        -> lmsfiles.s3.eu-west-1.amazonaws.com/files/i/v/..../<sha>.zip
//           Content-Disposition: attachment
//
// The surrogate logged "redirect_count=2" on the resulting download item, and
// the last hop is cross-site: ivalua.docebosaas.com -> *.amazonaws.com. Both
// properties are reproduced here.
//
// No host or directory is baked in. Every hop is built from this script's own
// location, so the whole set can be dropped into any folder on any server and
// works as-is. The cross-origin hop is the one thing that cannot be derived --
// it needs a second host - so it is opt-in via ?xo_host= and the chain stays
// same-origin unless you supply one.
//
// Query parameters
//   ?hops=<n>       number of 302s before the attachment. Default 2, matching
//                   redirect_count=2 in the capture.
//   ?xo_host=<url>  scheme + host (+ optional port) of a second server that
//                   also has these files in the same directory path, e.g.
//                   https://other.example.com. When set, the final hop goes
//                   there, so the tab's last navigation is cross-site exactly
//                   as the S3 hop is. Omit it and everything stays
//                   same-origin, which is what separates "new tab" from
//                   "cross-site" as a cause.
//   ?hop=<n>        internal, tracks position in the chain. Do not pass.
//   ?delay_ms=<ms>  forwarded to the final attachment, to slow the transfer.
//   ?size=<bytes>   forwarded to the final attachment.
//
// Test-tree endpoint only: ?xo_host= is an unvalidated redirect target beyond
// a scheme check. Do not deploy this outside a test server.

// This script's own directory, as an absolute path, derived rather than
// assumed: "/anything/you/like" for .../anything/you/like/<this>.php, or ""
// when it sits at the document root.
$dir = rtrim(str_replace('\\', '/', dirname($_SERVER['SCRIPT_NAME'])), '/');
if ($dir === '.' || $dir === '/') {
   $dir = '';
}

// |hops| is the number of 302s the browser sees, so that it lines up directly
// with the surrogate's redirect_count. Redirect number i is emitted by
// ?hop=i-1, and the last one lands on the attachment itself. One redirect is
// the minimum this script can produce.
$hops = isset($_GET['hops']) ? max(1, intval($_GET['hops'])) : 2;
$hop = isset($_GET['hop']) ? intval($_GET['hop']) : 0;

// Only http(s) origins are accepted, and only the origin part is kept, so a
// stray path in the parameter cannot bend the chain somewhere unexpected.
$xo_host = '';
if (isset($_GET['xo_host']) && $_GET['xo_host'] !== '') {
   $parts = parse_url($_GET['xo_host']);
   if ($parts && isset($parts['scheme'], $parts['host']) &&
       ($parts['scheme'] === 'http' || $parts['scheme'] === 'https')) {
      $xo_host = $parts['scheme'] . '://' . $parts['host'];
      if (isset($parts['port'])) {
         $xo_host .= ':' . $parts['port'];
      }
   }
}

$target_url = '';
if (isset($_GET['target_url']) && $_GET['target_url'] !== '') {
   $parts = parse_url($_GET['target_url']);
   if ($parts && isset($parts['scheme'], $parts['host']) &&
       ($parts['scheme'] === 'http' || $parts['scheme'] === 'https')) {
      $target_url = $_GET['target_url'];
   }
}

// Forward the parameters that belong to the final attachment.
$passthrough = array();
foreach (array('size', 'delay_ms', 'nocd') as $key) {
   if (isset($_GET[$key])) {
      $passthrough[$key] = $_GET[$key];
   }
}

// Which attachment the chain ends on. The field file is an archive, so zip is
// the default; csv is kept for comparison against the earlier runs.
$ft = isset($_GET['ft']) ? $_GET['ft'] : 'zip';
if ($ft === 'csv') {
   $final_script = 'attachment_csv.php';
} else {
   $final_script = 'attachment_zip.php';
   if ($ft === 'pptx') {
      $passthrough['type'] = 'pptx';
   }
}

if ($hop >= $hops - 1) {
   // Last redirect in the chain: point it at the attachment. With a valid
   // ?target_url or ?xo_host this is the hop that crosses origins (e.g. to S3).
   if ($target_url !== '') {
      $target = $target_url;
   } else {
      $target = $xo_host . $dir . '/' . $final_script;
      if ($passthrough) {
         $target .= '?' . http_build_query($passthrough);
      }
   }
   header('Location: ' . $target, true, 302);
   exit();
}

// An intermediate 302, still on this origin. 'type' belongs to the final
// script, so re-derive it from ft rather than forwarding it around the chain.
unset($passthrough['type']);
$next = array_merge($passthrough, array(
   'hops' => $hops,
   'hop' => $hop + 1,
   'ft' => $ft,
));
if ($xo_host !== '') {
   $next['xo_host'] = $xo_host;
}
if ($target_url !== '') {
   $next['target_url'] = $target_url;
}
header('Location: ' . $dir . '/course_file_redirect.php?' .
       http_build_query($next), true, 302);
exit();
?>
