ContentDispositionHeader::name()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 1
c 1
b 0
f 0
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
cc 1
nc 1
nop 0
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Http;
6
7
use InvalidArgumentException;
8
use Yiisoft\Strings\Inflector;
9
10
use function in_array;
11
12
/**
13
 * Helps to build "Content-Disposition" header that complies to RFC-6266 and works in the majority of modern browsers.
14
 *
15
 * @see https://tools.ietf.org/html/rfc6266
16
 */
17
final class ContentDispositionHeader
18
{
19
    /**
20
     * Content sent with "attachment" disposition usually triggers download dialog.
21
     */
22
    public const ATTACHMENT = 'attachment';
23
24
    /**
25
     * Content sent with "inline" disposition is usually displayed within the browser window.
26
     */
27
    public const INLINE = 'inline';
28
29
    /**
30
     * @return string Content-Disposition header name.
31
     */
32 1
    public static function name(): string
33
    {
34 1
        return 'Content-Disposition';
35
    }
36
37
    /**
38
     * Returns Content-Disposition header value that is safe to use with both old and new browsers.
39
     *
40
     * Fallback name:
41
     *
42
     * - Causes issues if contains non-ASCII characters with codes less than 32 or more than 126.
43
     * - Causes issues if contains urlencoded characters (starting with `%`) or `%` character. Some browsers interpret
44
     *   `filename="X"` as urlencoded name, some don't.
45
     * - Causes issues if contains path separator characters such as `\` or `/`.
46
     * - Since value is wrapped with `"`, it should be escaped as `\"`.
47
     * - Since input could contain non-ASCII characters, fallback is obtained by transliteration.
48
     *
49
     * UTF name:
50
     *
51
     * - Causes issues if contains path separator characters such as `\` or `/`.
52
     * - Should be urlencoded since headers are ASCII-only.
53
     * - Could be omitted if it exactly matches fallback name.
54
     *
55
     * @param string $type The disposition type.
56
     * @param string|null $fileName The file name.
57
     *
58
     * @throws InvalidArgumentException if `$type` is incorrect.
59
     *
60
     * @return string
61
     */
62 16
    public static function value(string $type, ?string $fileName = null): string
63
    {
64 16
        if (!in_array($type, [self::INLINE, self::ATTACHMENT])) {
65 1
            throw new InvalidArgumentException(
66 1
                'Disposition type must be either "' . self::ATTACHMENT . '" or "' . self::INLINE . '".'
67 1
            );
68
        }
69
70 15
        $header = $type;
71
72 15
        if ($fileName === null) {
73 2
            return $header;
74
        }
75
76 13
        $fileName = str_replace(['%', '/', '\\'], '_', $fileName);
77
78 13
        $fallbackName = (new Inflector())->toTransliterated($fileName, Inflector::TRANSLITERATE_LOOSE);
79 13
        $fallbackName = str_replace("\r\n", '_', $fallbackName);
80 13
        $fallbackName = preg_replace('/[^\x20-\x7e]/u', '_', $fallbackName);
81 13
        $fallbackName = str_replace('"', '\\"', $fallbackName);
82
83 13
        $utfName = rawurlencode($fileName);
84
85 13
        $header .= "; filename=\"{$fallbackName}\"";
86 13
        if ($utfName !== $fallbackName) {
87 7
            $header .= "; filename*=utf-8''{$utfName}";
88
        }
89
90 13
        return $header;
91
    }
92
93
    /**
94
     * Returns "Content-Disposition" header with "inline" disposition.
95
     *
96
     * @see value()
97
     *
98
     * @param string|null $fileName The file name.
99
     *
100
     * @return string
101
     */
102 1
    public static function inline(?string $fileName = null): string
103
    {
104 1
        return self::value(self::INLINE, $fileName);
105
    }
106
107
    /**
108
     * Returns "Content-Disposition" header with "attachment" disposition.
109
     *
110
     * @see value()
111
     *
112
     * @param string|null $fileName The file name.
113
     *
114
     * @return string
115
     */
116 1
    public static function attachment(?string $fileName = null): string
117
    {
118 1
        return self::value(self::ATTACHMENT, $fileName);
119
    }
120
}
121