1<?php
2
3/**
4 * Simple HTTP Streamer.
5 *
6 * This class manages the stream of a generic content
7 * taking into account the correct http headers settings
8 *
9 * Currently it supports only 4 content (mime) types:
10 * - PDF: application/pdf
11 * - HTML5: text/html
12 * - XML: text/xml
13 * - CSV: text/csv
14 * - Generic file: application/octet-stream
15 *
16 * This Source Code Form is subject to the terms of the Mozilla Public License,
17 *  v. 2.0. If a copy of the MPL was not distributed with this file, You can
18 * obtain one at http://mozilla.org/MPL/2.0/.
19 *
20 * @package   phpMyFAQ
21 * @author    Matteo Scaramuccia <matteo@scaramuccia.com>
22 * @copyright 2005-2020 phpMyFAQ Team
23 * @license   http://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
24 * @link      https://www.phpmyfaq.de
25 * @since     2005-11-02
26 */
27
28namespace phpMyFAQ;
29
30/**
31 * Class HttpStreamer
32 *
33 * @package phpMyFAQ
34 */
35class HttpStreamer
36{
37    /**
38     * HTTP content disposition attachment constant.
39     *
40     * @var string
41     */
42    public const HTTP_CONTENT_DISPOSITION_ATTACHMENT = 'attachment';
43
44    /**
45     * HTTP content disposition inline constant.
46     *
47     * @var string
48     */
49    public const HTTP_CONTENT_DISPOSITION_INLINE = 'inline';
50
51    /**
52     * Disposition attachment constant.
53     *
54     * @var string
55     */
56    public const EXPORT_DISPOSITION_ATTACHMENT = 'attachment';
57
58    /**
59     * Disposition inline constant.
60     *
61     * @var string
62     */
63    public const EXPORT_DISPOSITION_INLINE = 'inline';
64
65    /**
66     * Enable buffer.
67     *
68     * @var bool
69     */
70    private const EXPORT_BUFFER_ENABLE = true;
71
72    /**
73     * PMF export data type.
74     *
75     * @var string
76     */
77    private $type;
78
79    /**
80     * HTTP Content Disposition.
81     *
82     * @var string
83     */
84    private $disposition;
85
86    /**
87     * HTTP streaming data.
88     *
89     * @var string
90     */
91    private $content;
92
93    /**
94     * HTTP streaming data length.
95     *
96     * @var int
97     */
98    private $size;
99
100    /**
101     * Constructor.
102     *
103     * @param string $type    Type
104     * @param string $content Content
105     */
106    public function __construct($type, $content)
107    {
108        $this->type = $type;
109        $this->disposition = self::HTTP_CONTENT_DISPOSITION_INLINE;
110        $this->content = $content;
111        $this->size = strlen($this->content);
112    }
113
114    /**
115     * Sends data.
116     *
117     * @param string $disposition Disposition
118     */
119    public function send($disposition)
120    {
121        if (isset($disposition)) {
122            $this->disposition = $disposition;
123        }
124
125        // Sanity checks
126        if (headers_sent()) {
127            die('<b>PMF_HttpStreamer Class</b> error: unable to send my headers: someone already sent other headers!');
128        }
129        if (self::EXPORT_BUFFER_ENABLE) {
130            if (ob_get_contents()) {
131                die('<b>PMF_HttpStreamer Class</b> error: unable to send my data: someone already sent other data!');
132            }
133        }
134
135        // Manage output buffering
136        if (self::EXPORT_BUFFER_ENABLE) {
137            ob_start();
138        }
139        // Send the right HTTP headers
140        $this->setHttpHeaders();
141        // Send the raw content
142        $this->streamContent();
143        // Manage output buffer flushing
144        if (self::EXPORT_BUFFER_ENABLE) {
145            ob_end_flush();
146        }
147    }
148
149    /**
150     * Sends HTTP Headers.
151     */
152    private function setHttpHeaders()
153    {
154        // Evaluate data upon export type request
155        switch ($this->type) {
156            case 'pdf':
157                $filename = 'phpmyfaq.pdf';
158                $description = 'phpMyFaq PDF export file';
159                $mimeType = 'application/pdf';
160                break;
161            case 'html5':
162                $filename = 'phpmyfaq.html';
163                $description = 'phpMyFaq HTML5 export file';
164                $mimeType = 'text/html';
165                break;
166            case 'xml':
167                $filename = 'phpmyfaq.xml';
168                $description = 'phpMyFaq XML export file';
169                $mimeType = 'text/xml';
170                break;
171            case 'csv':
172                $filename = 'phpmyfaq.csv';
173                $description = 'phpMyFaq CSV export file';
174                $mimeType = 'text/csv';
175                break;
176            case 'json':
177                $filename = 'phpmyfaq.json';
178                $description = 'phpMyFaq JSON export file';
179                $mimeType = 'application/json';
180                break;
181            // In this case no default statement is required:
182            // the one above is just for clean coding style
183            default:
184                $filename = 'phpmyfaq.pmf';
185                $description = 'Generic file';
186                $mimeType = 'application/octet-stream';
187                break;
188        }
189
190        $filename = Export::getExportTimestamp() . '_' . $filename;
191
192        // Set the correct HTTP headers:
193        // 1. Prevent proxies&browsers caching
194        header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
195        header('Expires: 0');
196        header('Cache-Control: private, no-store, no-cache, must-revalidate, post-check=0, pre-check=0');
197        header('Pragma: no-cache');
198
199        // 2. Set the correct values for file streaming
200        header('Content-Type: ' . $mimeType);
201        if (
202            ($this->disposition == self::HTTP_CONTENT_DISPOSITION_ATTACHMENT)
203            && isset($_SERVER['HTTP_USER_AGENT']) && !(strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE') === false)
204        ) {
205            header('Content-Type: application/force-download');
206        }
207        // RFC2616, �19.5.1: $filename must be a quoted-string
208        header('Content-Disposition: ' . $this->disposition . '; filename="phpMyFAQ_' . $filename . '"');
209        if (!empty($description)) {
210            header('Content-Description: ' . $description);
211        }
212        header('Content-Transfer-Encoding: binary');
213        header('Accept-Ranges: none');
214        header('Content-Length: ' . $this->size);
215    }
216
217    /**
218     * Streams the content.
219     */
220    private function streamContent()
221    {
222        echo $this->content;
223    }
224}
225