Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 47
0.00% covered (danger)
0.00%
0 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
StreamFile
0.00% covered (danger)
0.00%
0 / 46
0.00% covered (danger)
0.00%
0 / 3
342
0.00% covered (danger)
0.00%
0 / 1
 stream
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
6
 setHeader
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 contentTypeFromPath
0.00% covered (danger)
0.00%
0 / 32
0.00% covered (danger)
0.00%
0 / 1
240
1<?php
2
3/**
4 * Functions related to the output of file content.
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 * http://www.gnu.org/copyleft/gpl.html
20 *
21 * @file
22 */
23
24namespace MediaWiki\Output;
25
26use HTTPFileStreamer;
27use InvalidArgumentException;
28use MediaWiki\Context\RequestContext;
29use MediaWiki\MainConfigNames;
30use MediaWiki\MediaWikiServices;
31use UploadBase;
32use Wikimedia\FileBackend\FileBackend;
33
34/**
35 * Functions related to the output of file content
36 */
37class StreamFile {
38    /** @var string */
39    private const UNKNOWN_CONTENT_TYPE = 'unknown/unknown';
40
41    /**
42     * Stream a file to the browser, adding all the headings and fun stuff.
43     * Headers sent include: Content-type, Content-Length, Last-Modified,
44     * and Content-Disposition.
45     *
46     * @param string $fname Full name and path of the file to stream
47     * @param array $headers Any additional headers to send if the file exists
48     * @param bool $sendErrors Send error messages if errors occur (like 404)
49     * @param array $optHeaders HTTP request header map (e.g. "range") (use lowercase keys)
50     * @param int $flags Bitfield of STREAM_* constants
51     * @return bool Success
52     */
53    public static function stream(
54        $fname,
55        $headers = [],
56        $sendErrors = true,
57        $optHeaders = [],
58        $flags = 0
59    ) {
60        if ( FileBackend::isStoragePath( $fname ) ) {
61            throw new InvalidArgumentException( __FUNCTION__ . " given storage path '$fname'." );
62        }
63
64        $streamer = new HTTPFileStreamer(
65            $fname,
66            [
67                'obResetFunc' => 'wfResetOutputBuffers',
68                'streamMimeFunc' => [ __CLASS__, 'contentTypeFromPath' ],
69                'headerFunc' => [ __CLASS__, 'setHeader' ],
70            ]
71        );
72
73        return $streamer->stream( $headers, $sendErrors, $optHeaders, $flags );
74    }
75
76    /**
77     * @param string $header
78     *
79     * @internal
80     */
81    public static function setHeader( $header ) {
82        RequestContext::getMain()->getRequest()->response()->header( $header );
83    }
84
85    /**
86     * Determine the file type of a file based on the path
87     *
88     * @param string $filename Storage path or file system path
89     * @param bool $safe Whether to do retroactive upload prevention checks
90     * @return null|string
91     */
92    public static function contentTypeFromPath( $filename, $safe = true ) {
93        // NOTE: TrivialMimeDetection is forced by ThumbnailEntryPoint. When this
94        // code is moved to a non-static method in a service object, we can no
95        // longer rely on that.
96        $trivialMimeDetection = MediaWikiServices::getInstance()->getMainConfig()
97            ->get( MainConfigNames::TrivialMimeDetection );
98
99        $ext = strrchr( $filename, '.' );
100        $ext = $ext ? strtolower( substr( $ext, 1 ) ) : '';
101
102        # trivial detection by file extension,
103        # used for thumbnails (thumb.php)
104        if ( $trivialMimeDetection ) {
105            switch ( $ext ) {
106                case 'gif':
107                    return 'image/gif';
108                case 'png':
109                    return 'image/png';
110                case 'jpg':
111                case 'jpeg':
112                    return 'image/jpeg';
113            }
114
115            return self::UNKNOWN_CONTENT_TYPE;
116        }
117
118        $magic = MediaWikiServices::getInstance()->getMimeAnalyzer();
119        // Use the extension only, rather than magic numbers, to avoid opening
120        // up vulnerabilities due to uploads of files with allowed extensions
121        // but disallowed types.
122        $type = $magic->getMimeTypeFromExtensionOrNull( $ext );
123
124        /**
125         * Double-check some security settings that were done on upload but might
126         * have changed since.
127         */
128        if ( $safe ) {
129            $mainConfig = MediaWikiServices::getInstance()->getMainConfig();
130            $prohibitedFileExtensions = $mainConfig->get( MainConfigNames::ProhibitedFileExtensions );
131            $checkFileExtensions = $mainConfig->get( MainConfigNames::CheckFileExtensions );
132            $strictFileExtensions = $mainConfig->get( MainConfigNames::StrictFileExtensions );
133            $fileExtensions = $mainConfig->get( MainConfigNames::FileExtensions );
134            $verifyMimeType = $mainConfig->get( MainConfigNames::VerifyMimeType );
135            $mimeTypeExclusions = $mainConfig->get( MainConfigNames::MimeTypeExclusions );
136            [ , $extList ] = UploadBase::splitExtensions( $filename );
137            if ( UploadBase::checkFileExtensionList( $extList, $prohibitedFileExtensions ) ) {
138                return self::UNKNOWN_CONTENT_TYPE;
139            }
140            if (
141                $checkFileExtensions &&
142                $strictFileExtensions &&
143                !UploadBase::checkFileExtensionList( $extList, $fileExtensions )
144            ) {
145                return self::UNKNOWN_CONTENT_TYPE;
146            }
147            if ( $verifyMimeType && $type !== null && in_array( strtolower( $type ), $mimeTypeExclusions ) ) {
148                return self::UNKNOWN_CONTENT_TYPE;
149            }
150        }
151        return $type;
152    }
153}
154
155/** @deprecated class alias since 1.41 */
156class_alias( StreamFile::class, 'StreamFile' );