Skip to content

Commit

Permalink
Merge pull request #27 from mikehaertl/26-send-custom-headers
Browse files Browse the repository at this point in the history
Issue #26 Implement custom headers
  • Loading branch information
mikehaertl authored Mar 1, 2021
2 parents 9626f9c + d9c9a83 commit 70a5b70
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 21 deletions.
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ A convenience class for temporary files.

* Create temporary file with arbitrary content
* Delete file after use (can be disabled)
* Send file to client, either inline or with save dialog
* Send file to client, either inline or with save dialog, optionally with custom HTTP headers
* Save file locally

## Examples
Expand All @@ -25,6 +25,14 @@ $file = new File('some content', '.html');

// send to client for download
$file->send('home.html');
// ... with custom content type (autodetected otherwhise)
$file->send('home.html', 'application/pdf');
// ... for inline display (download dialog otherwhise)
$file->send('home.html', 'application/pdf', true);
// ... with custom headers
$file->send('home.html', 'application/pdf', true, [
'X-Header' => 'Example',
]);

// save to disk
$file->saveAs('/dir/test.html');
Expand All @@ -43,3 +51,14 @@ use mikehaertl\tmp\File;
$file = new File('some content', '.html');
$file->delete = false;
```

Default HTTP headers can also be added:
```php
<?php
use mikehaertl\tmp\File;

File::$defaultHeader['X-Header'] = 'My Default';

$file = new File('some content', '.html');
$file->send('home.html');
```
82 changes: 62 additions & 20 deletions src/File.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@ class File
*/
public $delete = true;

/**
* @var array the list of static default headers to send when `send()` is
* called as key/value pairs.
*/
public static $defaultHeaders = array(
'Pragma' => 'public',
'Expires' => 0,
'Cache-Control' => 'must-revalidate, post-check=0, pre-check=0',
'Content-Transfer-Encoding' => 'binary',
);

/**
* @var string the name of this file
*/
Expand Down Expand Up @@ -73,40 +84,48 @@ public function __destruct()
* 'application/octet-stream' is used.
* @param bool $inline whether to force inline display of the file, even if
* filename is present.
* @param array $headers a list of additional HTTP headers to send in the
* response as an array. The array keys are the header names like
* 'Cache-Control' and the array values the header value strings to send.
* Each array value can also be another array of strings if the same header
* should be sent multiple times. This can also be used to override
* automatically created headers like 'Expires' or 'Content-Length'. To suppress
* automatically created headers, `false` can also be used as header value.
*/
public function send($filename = null, $contentType = null, $inline = false)
public function send($filename = null, $contentType = null, $inline = false, $headers = array())
{
if ($contentType === null) {
$headers = array_merge(self::$defaultHeaders, $headers);

if ($contentType !== null) {
$headers['Content-Type'] = $contentType;
} elseif (!isset($headers['Content-Type'])) {
$contentType = @mime_content_type($this->_filename);
if ($contentType === false) {
$contentType = self::DEFAULT_CONTENT_TYPE;
}
$headers['Content-Type'] = $contentType;
}
header('Pragma: public');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Content-Type: ' . $contentType);
header('Content-Transfer-Encoding: binary');

//#11 Undefined index: HTTP_USER_AGENT
$userAgent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';

// #84: Content-Length leads to "network connection was lost" on iOS
$isIOS = preg_match('/i(phone|pad|pod)/i', $userAgent);
if (!$isIOS) {
header('Content-Length: ' . filesize($this->_fileName));

if (!isset($headers['Content-Length'])) {
// #11 Undefined index: HTTP_USER_AGENT
$userAgent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';

// #84: Content-Length leads to "network connection was lost" on iOS
$isIOS = preg_match('/i(phone|pad|pod)/i', $userAgent);
if (!$isIOS) {
$headers['Content-Length'] = filesize($this->_fileName);
}
}

if ($filename !== null || $inline) {
if (($filename !== null || $inline) && !isset($headers['Content-Disposition'])) {
$disposition = $inline ? 'inline' : 'attachment';
$encodedFilename = rawurlencode($filename);
header(
"Content-Disposition: $disposition; " .
$headers['Content-Disposition'] = "$disposition; " .
"filename=\"$filename\"; " .
"filename*=UTF-8''$encodedFilename"
);
"filename*=UTF-8''$encodedFilename";
}

$this->sendHeaders($headers);
readfile($this->_fileName);
}

Expand Down Expand Up @@ -152,4 +171,27 @@ public function __toString()
{
return $this->_fileName;
}

/**
* Send the given list of headers
*
* @param array $headers the list of headers to send as key/value pairs.
* Value can either be a string or an array of strings to send the same
* header multiple times.
*/
protected function sendHeaders($headers)
{
foreach ($headers as $name => $value) {
if ($value === false) {
continue;
}
if (is_array($value)) {
foreach ($value as $v) {
header("$name: $v");
}
} else {
header("$name: $value");
}
}
}
}

0 comments on commit 70a5b70

Please sign in to comment.