Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 49
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 / 48
0.00% covered (danger)
0.00%
0 / 3
380
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 / 34
0.00% covered (danger)
0.00%
0 / 1
272
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 InvalidArgumentException;
27use MediaWiki\Context\RequestContext;
28use MediaWiki\MainConfigNames;
29use MediaWiki\MediaWikiServices;
30use UploadBase;
31use Wikimedia\FileBackend\FileBackend;
32use Wikimedia\FileBackend\HTTPFileStreamer;
33
34/**
35 * Functions related to the output of file content
36 */
37class StreamFile {
38
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                case 'webp':
114                    return 'image/webp';
115            }
116
117            return self::UNKNOWN_CONTENT_TYPE;
118        }
119
120        $magic = MediaWikiServices::getInstance()->getMimeAnalyzer();
121        // Use the extension only, rather than magic numbers, to avoid opening
122        // up vulnerabilities due to uploads of files with allowed extensions
123        // but disallowed types.
124        $type = $magic->getMimeTypeFromExtensionOrNull( $ext );
125
126        /**
127         * Double-check some security settings that were done on upload but might
128         * have changed since.
129         */
130        if ( $safe ) {
131            $mainConfig = MediaWikiServices::getInstance()->getMainConfig();
132            $prohibitedFileExtensions = $mainConfig->get( MainConfigNames::ProhibitedFileExtensions );
133            $checkFileExtensions = $mainConfig->get( MainConfigNames::CheckFileExtensions );
134            $strictFileExtensions = $mainConfig->get( MainConfigNames::StrictFileExtensions );
135            $fileExtensions = $mainConfig->get( MainConfigNames::FileExtensions );
136            $verifyMimeType = $mainConfig->get( MainConfigNames::VerifyMimeType );
137            $mimeTypeExclusions = $mainConfig->get( MainConfigNames::MimeTypeExclusions );
138            [ , $extList ] = UploadBase::splitExtensions( $filename );
139            if ( UploadBase::checkFileExtensionList( $extList, $prohibitedFileExtensions ) ) {
140                return self::UNKNOWN_CONTENT_TYPE;
141            }
142            if (
143                $checkFileExtensions &&
144                $strictFileExtensions &&
145                !UploadBase::checkFileExtensionList( $extList, $fileExtensions )
146            ) {
147                return self::UNKNOWN_CONTENT_TYPE;
148            }
149            if ( $verifyMimeType && $type !== null && in_array( strtolower( $type ), $mimeTypeExclusions ) ) {
150                return self::UNKNOWN_CONTENT_TYPE;
151            }
152        }
153        return $type;
154    }
155}
156
157/** @deprecated class alias since 1.41 */
158class_alias( StreamFile::class, 'StreamFile' );