<?php
// A dynamically generated ZIP served as an attachment, standing in for the
// Ivalua/Docebo course file.
//
// Every run of this test so far has downloaded text/csv, which is the one
// thing the field case never does. The captured metadata says
//
//    original_filename: "Create your PR (v182) - 20250523.zip"
//    file_type:         "archive"
//
// and the originally reported file was "...RFx.pptx", which is also a ZIP
// container. A CSV and an archive do not take the same path through file
// scanning, so an archive is what has to be on the wire to exercise it.
//
// The archive is built by hand rather than with ZipArchive, which is not
// installed everywhere, and uses a single stored (uncompressed) entry so the
// total byte count is exact and the body can be paced out over time.
//
//   ?size=<bytes>    total size of the .zip, default 25000000
//   ?delay_ms=<ms>   spread the body over this many ms, as attachment_csv does
//   ?type=pptx       serve as .pptx with the PowerPoint content type. The
//                    bytes are still a ZIP container, which is what a .pptx
//                    is. Default is a plain .zip.
//   ?name=<text>     override the attachment filename (no extension)

set_time_limit(0);

$target_size = isset($_GET['size']) ? intval($_GET['size']) : 25000000;
$delay_ms = isset($_GET['delay_ms']) ? intval($_GET['delay_ms']) : 0;
$is_pptx = (isset($_GET['type']) && $_GET['type'] === 'pptx');

$base_name = isset($_GET['name']) && $_GET['name'] !== ''
   ? $_GET['name'] : 'Create your PR (v182) - 20250523';
$ext = $is_pptx ? '.pptx' : '.zip';
$download_name = $base_name . $ext;

// Name of the single entry inside the archive.
$entry_name = 'course/content.txt';
$name_len = strlen($entry_name);

// A single stored entry lays out as:
//   local header (30 + name) + data + central directory (46 + name) + EOCD (22)
// so the payload size follows from the total the caller asked for.
$overhead = 30 + $name_len + 46 + $name_len + 22;
$data_size = $target_size - $overhead;
if ($data_size < 1) {
   $data_size = 1;
   $target_size = $overhead + 1;
}

// Deterministic, incompressible-enough filler. Built in one pass; 25 MB costs
// a fraction of a second and well under the default memory limit.
$row = "course-material-line,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-,-\n";
$data = str_repeat($row, intdiv($data_size, strlen($row)) + 1);
$data = substr($data, 0, $data_size);

$crc = crc32($data);

// DOS timestamp for the entry.
$now = getdate();
$dos_time = ($now['hours'] << 11) | ($now['minutes'] << 5) | ($now['seconds'] >> 1);
$dos_date = (($now['year'] - 1980) << 9) | ($now['mon'] << 5) | $now['mday'];

// Local file header: signature, version, flags, method=0 (stored), time, date,
// crc, compressed size, uncompressed size, name length, extra length.
$local = pack('VvvvvvVVVvv', 0x04034b50, 20, 0, 0, $dos_time, $dos_date,
              $crc, $data_size, $data_size, $name_len, 0) . $entry_name;

// Central directory header, then end-of-central-directory.
$central = pack('VvvvvvvVVVvvvvvVV', 0x02014b50, 20, 20, 0, 0, $dos_time,
                $dos_date, $crc, $data_size, $data_size, $name_len,
                0, 0, 0, 0, 0, 0) . $entry_name;

$eocd = pack('VvvvvVVv', 0x06054b50, 0, 0, 1, 1, strlen($central),
             strlen($local) + $data_size, 0);

$total = strlen($local) + $data_size + strlen($central) + strlen($eocd);

header('HTTP/1.1 200 OK');
header('Content-Type: ' . ($is_pptx
   ? 'application/vnd.openxmlformats-officedocument.presentationml.presentation'
   : 'application/zip'));
// ?nocd=1 omits Content-Disposition entirely, so the response is a download
// only because application/zip is not renderable and the name has to be
// derived from the URL path. The field log's surrogate-download-requested line
// has no filename= field, and that field is printed with if_truthy() -- so
// GetSuggestedFilename() was empty there, which is what this reproduces.
if (!isset($_GET['nocd']) || !$_GET['nocd']) {
   header('Content-Disposition: attachment; filename="' . $download_name . '"');
}
header('Content-Length: ' . $total);
header('Cache-Control: no-cache');

echo $local;

if ($delay_ms <= 0) {
   echo $data;
} else {
   // Same pacing as attachment_csv.php: twenty chunks, evenly spread, so the
   // transfer occupies the requested wall-clock time.
   $chunks = 20;
   $chunk_size = (int)ceil($data_size / $chunks);
   $chunk_delay_us = intval($delay_ms * 1000 / $chunks);
   for ($offset = 0; $offset < $data_size; $offset += $chunk_size) {
      echo substr($data, $offset, $chunk_size);
      flush();
      usleep($chunk_delay_us);
   }
}

echo $central;
echo $eocd;
?>
