Commit | Line | Data |
---|---|---|
158bd3ca | 1 | <?php |
a9229942 | 2 | |
158bd3ca | 3 | namespace wcf\system\request; |
a9229942 | 4 | |
c3299383 | 5 | use wcf\system\application\ApplicationHandler; |
4448afe3 | 6 | use wcf\system\box\BoxHandler; |
af28fae6 | 7 | use wcf\system\exception\AJAXException; |
ec1b1daf | 8 | use wcf\system\exception\IllegalLinkException; |
57bbef1a | 9 | use wcf\system\exception\NamedUserException; |
ec1b1daf | 10 | use wcf\system\exception\SystemException; |
15843d52 | 11 | use wcf\system\notice\NoticeHandler; |
2bc9f31d | 12 | use wcf\system\SingletonFactory; |
dce072f6 | 13 | use wcf\system\WCF; |
148b0b83 | 14 | use wcf\util\FileUtil; |
5bded211 | 15 | use wcf\util\HeaderUtil; |
158bd3ca TD |
16 | |
17 | /** | |
18 | * Handles http requests. | |
a9229942 TD |
19 | * |
20 | * @author Marcel Werk | |
21 | * @copyright 2001-2020 WoltLab GmbH | |
22 | * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> | |
23 | * @package WoltLabSuite\Core\System\Request | |
158bd3ca | 24 | */ |
a9229942 TD |
25 | class RequestHandler extends SingletonFactory |
26 | { | |
27 | /** | |
28 | * active request object | |
29 | * @var Request | |
30 | */ | |
31 | protected $activeRequest; | |
32 | ||
33 | /** | |
34 | * true, if current domain mismatch any known domain | |
35 | * @var bool | |
36 | */ | |
37 | protected $inRescueMode = false; | |
38 | ||
39 | /** | |
40 | * indicates if the request is an acp request | |
41 | * @var bool | |
42 | */ | |
43 | protected $isACPRequest = false; | |
44 | ||
45 | /** | |
46 | * @inheritDoc | |
47 | */ | |
48 | protected function init() | |
49 | { | |
50 | $this->isACPRequest = \class_exists('wcf\system\WCFACP', false); | |
51 | } | |
52 | ||
53 | /** | |
54 | * Handles a http request. | |
55 | * | |
56 | * @param string $application | |
57 | * @param bool $isACPRequest | |
58 | * @throws AJAXException | |
59 | * @throws IllegalLinkException | |
60 | * @throws SystemException | |
61 | */ | |
62 | public function handle($application = 'wcf', $isACPRequest = false) | |
63 | { | |
64 | try { | |
65 | $this->isACPRequest = $isACPRequest; | |
66 | ||
67 | if (!RouteHandler::getInstance()->matches()) { | |
68 | if (ENABLE_DEBUG_MODE) { | |
69 | throw new SystemException("Cannot handle request, no valid route provided."); | |
70 | } else { | |
71 | throw new IllegalLinkException(); | |
72 | } | |
73 | } | |
74 | ||
75 | // build request | |
76 | $this->buildRequest($application); | |
77 | ||
78 | // enforce that certain ACP pages are not available for non-owners in enterprise mode | |
79 | if ( | |
2f187221 | 80 | $this->isACPRequest() |
a9229942 | 81 | && ENABLE_ENTERPRISE_MODE |
9d47b1e0 TD |
82 | && \defined($this->getActiveRequest()->getClassName() . '::BLACKLISTED_IN_ENTERPRISE_MODE') |
83 | && \constant($this->getActiveRequest()->getClassName() . '::BLACKLISTED_IN_ENTERPRISE_MODE') | |
52e95d8e | 84 | && !WCF::getUser()->hasOwnerAccess() |
a9229942 TD |
85 | ) { |
86 | throw new IllegalLinkException(); | |
87 | } | |
88 | ||
60fff547 | 89 | $this->checkOfflineMode(); |
a9229942 TD |
90 | |
91 | // start request | |
9d47b1e0 | 92 | $this->getActiveRequest()->execute(); |
a9229942 TD |
93 | } catch (NamedUserException $e) { |
94 | $e->show(); | |
95 | ||
96 | exit; | |
97 | } | |
98 | } | |
99 | ||
100 | /** | |
101 | * Builds a new request. | |
102 | * | |
103 | * @param string $application | |
104 | * @throws IllegalLinkException | |
105 | * @throws NamedUserException | |
106 | * @throws SystemException | |
107 | */ | |
108 | protected function buildRequest($application) | |
109 | { | |
110 | try { | |
111 | $routeData = RouteHandler::getInstance()->getRouteData(); | |
112 | ||
113 | // handle landing page for frontend requests | |
114 | if (!$this->isACPRequest()) { | |
115 | $this->handleDefaultController($application, $routeData); | |
116 | ||
117 | // check if accessing from the wrong domain (e.g. "www." omitted but domain was configured with) | |
118 | if (!\defined('WCF_RUN_MODE') || WCF_RUN_MODE !== 'embedded') { | |
119 | $applicationObject = ApplicationHandler::getInstance()->getApplication($application); | |
120 | if ($applicationObject->domainName != $_SERVER['HTTP_HOST']) { | |
121 | // build URL, e.g. http://example.net/forum/ | |
122 | $url = FileUtil::addTrailingSlash( | |
123 | RouteHandler::getProtocol() . $applicationObject->domainName . RouteHandler::getPath() | |
124 | ); | |
125 | ||
126 | // query string, e.g. ?foo=bar | |
127 | if (!empty($_SERVER['QUERY_STRING'])) { | |
128 | $url .= '?' . $_SERVER['QUERY_STRING']; | |
129 | } | |
130 | ||
131 | HeaderUtil::redirect($url, true, false); | |
132 | ||
133 | exit; | |
134 | } | |
135 | } | |
136 | } elseif (empty($routeData['controller'])) { | |
137 | $routeData['controller'] = 'index'; | |
138 | } | |
139 | ||
140 | $controller = $routeData['controller']; | |
141 | ||
142 | if (isset($routeData['className'])) { | |
143 | $classData = [ | |
144 | 'className' => $routeData['className'], | |
145 | 'controller' => $routeData['controller'], | |
146 | 'pageType' => $routeData['pageType'], | |
147 | ]; | |
148 | ||
149 | unset($routeData['className']); | |
150 | unset($routeData['controller']); | |
151 | unset($routeData['pageType']); | |
152 | } else { | |
153 | if ( | |
154 | $this->isACPRequest() | |
155 | && ($controller === 'login' || $controller === 'index') | |
156 | && $application !== 'wcf' | |
157 | ) { | |
158 | HeaderUtil::redirect( | |
159 | LinkHandler::getInstance()->getLink(\ucfirst($controller)), | |
160 | true, | |
161 | false | |
162 | ); | |
163 | ||
164 | exit; | |
165 | } | |
166 | ||
167 | $classApplication = $application; | |
168 | if ( | |
169 | !empty($routeData['isDefaultController']) | |
170 | && !empty($routeData['application']) | |
171 | && $routeData['application'] !== $application | |
172 | ) { | |
173 | $classApplication = $routeData['application']; | |
174 | } | |
175 | ||
176 | $classData = ControllerMap::getInstance()->resolve( | |
177 | $classApplication, | |
178 | $controller, | |
179 | $this->isACPRequest(), | |
180 | RouteHandler::getInstance()->isRenamedController() | |
181 | ); | |
182 | if (\is_string($classData)) { | |
183 | $this->redirect($routeData, $application, $classData); | |
184 | } | |
185 | } | |
186 | ||
187 | // handle CMS page meta data | |
188 | $metaData = ['isDefaultController' => (!empty($routeData['isDefaultController']))]; | |
189 | if (isset($routeData['cmsPageID'])) { | |
190 | $metaData['cms'] = [ | |
191 | 'pageID' => $routeData['cmsPageID'], | |
192 | 'languageID' => $routeData['cmsPageLanguageID'], | |
193 | ]; | |
194 | ||
195 | if ( | |
196 | $routeData['cmsPageLanguageID'] | |
197 | && $routeData['cmsPageLanguageID'] != WCF::getLanguage()->languageID | |
198 | ) { | |
199 | WCF::setLanguage($routeData['cmsPageLanguageID']); | |
200 | } | |
201 | ||
202 | unset($routeData['cmsPageID']); | |
203 | unset($routeData['cmsPageLanguageID']); | |
204 | } | |
205 | ||
206 | $this->activeRequest = new Request( | |
207 | $classData['className'], | |
208 | $classData['controller'], | |
209 | $classData['pageType'], | |
210 | $metaData | |
211 | ); | |
212 | ||
213 | // check if the controller matches an app that has an expired evaluation date | |
214 | $abbreviation = \mb_substr($classData['className'], 0, \mb_strpos($classData['className'], '\\')); | |
215 | if ($abbreviation !== 'wcf') { | |
216 | $applicationObject = ApplicationHandler::getInstance()->getApplication($abbreviation); | |
217 | $endDate = WCF::getApplicationObject($applicationObject)->getEvaluationEndDate(); | |
218 | if ($endDate && $endDate < TIME_NOW) { | |
219 | $package = $applicationObject->getPackage(); | |
220 | ||
221 | $pluginStoreFileID = WCF::getApplicationObject($applicationObject)->getEvaluationPluginStoreID(); | |
222 | $isWoltLab = false; | |
223 | if ($pluginStoreFileID === 0 && \strpos($package->package, 'com.woltlab.') === 0) { | |
224 | $isWoltLab = true; | |
225 | } | |
226 | ||
227 | throw new NamedUserException(WCF::getLanguage()->getDynamicVariable( | |
228 | 'wcf.acp.package.evaluation.expired', | |
229 | [ | |
230 | 'packageName' => $package->getName(), | |
231 | 'pluginStoreFileID' => $pluginStoreFileID, | |
232 | 'isWoltLab' => $isWoltLab, | |
233 | ] | |
234 | )); | |
235 | } | |
236 | } | |
237 | ||
238 | if (!$this->isACPRequest()) { | |
239 | // determine if current request matches the landing page | |
240 | if (ControllerMap::getInstance()->isLandingPage($classData, $metaData)) { | |
241 | $this->activeRequest->setIsLandingPage(); | |
242 | } | |
243 | } | |
244 | ||
245 | ApplicationHandler::getInstance()->rebuildActiveApplication(); | |
246 | } catch (SystemException $e) { | |
247 | if ( | |
248 | \defined('ENABLE_DEBUG_MODE') | |
249 | && ENABLE_DEBUG_MODE | |
250 | && \defined('ENABLE_DEVELOPER_TOOLS') | |
251 | && ENABLE_DEVELOPER_TOOLS | |
252 | ) { | |
253 | throw $e; | |
254 | } | |
255 | ||
256 | throw new IllegalLinkException(); | |
257 | } | |
258 | } | |
259 | ||
60fff547 TD |
260 | protected function checkOfflineMode() |
261 | { | |
262 | if (!$this->isACPRequest() && \defined('OFFLINE') && OFFLINE) { | |
263 | if ( | |
264 | !WCF::getSession()->getPermission('admin.general.canViewPageDuringOfflineMode') | |
265 | && !$this->getActiveRequest()->isAvailableDuringOfflineMode() | |
266 | ) { | |
267 | if ( | |
268 | isset($_SERVER['HTTP_X_REQUESTED_WITH']) | |
269 | && ($_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') | |
270 | ) { | |
271 | throw new AJAXException( | |
272 | WCF::getLanguage()->getDynamicVariable('wcf.ajax.error.permissionDenied'), | |
273 | AJAXException::INSUFFICIENT_PERMISSIONS | |
274 | ); | |
275 | } else { | |
276 | @\header('HTTP/1.1 503 Service Unavailable'); | |
277 | BoxHandler::disablePageLayout(); | |
278 | NoticeHandler::disableNotices(); | |
279 | WCF::getTPL()->assign([ | |
280 | 'templateName' => 'offline', | |
281 | 'templateNameApplication' => 'wcf', | |
282 | ]); | |
283 | WCF::getTPL()->display('offline'); | |
284 | } | |
285 | ||
286 | exit; | |
287 | } | |
288 | } | |
289 | } | |
290 | ||
a9229942 TD |
291 | /** |
292 | * Redirects to the actual URL, e.g. controller has been aliased or mistyped (boardlist instead of board-list). | |
293 | * | |
294 | * @param string[] $routeData | |
295 | * @param string $application | |
296 | * @param string $controller | |
297 | */ | |
298 | protected function redirect(array $routeData, $application, $controller = null) | |
299 | { | |
300 | $routeData['application'] = $application; | |
301 | if ($controller !== null) { | |
302 | $routeData['controller'] = $controller; | |
303 | } | |
304 | ||
305 | // append the remaining query parameters | |
306 | foreach ($_GET as $key => $value) { | |
307 | if (!empty($value) && $key != 'controller') { | |
308 | $routeData[$key] = $value; | |
309 | } | |
310 | } | |
311 | ||
312 | $redirectURL = LinkHandler::getInstance()->getLink($routeData['controller'], $routeData); | |
313 | HeaderUtil::redirect($redirectURL, true, false); | |
314 | ||
315 | exit; | |
316 | } | |
317 | ||
318 | /** | |
319 | * Checks page access for possible mandatory redirects. | |
320 | * | |
321 | * @param string $application | |
322 | * @param string[] $routeData | |
323 | * @throws IllegalLinkException | |
324 | */ | |
325 | protected function handleDefaultController($application, array &$routeData) | |
326 | { | |
327 | if (!RouteHandler::getInstance()->isDefaultController()) { | |
328 | return; | |
329 | } | |
330 | ||
331 | $data = ControllerMap::getInstance()->lookupDefaultController($application); | |
332 | if ($data === null) { | |
333 | // handle WCF which does not have a default controller | |
334 | throw new IllegalLinkException(); | |
335 | } elseif (!empty($data['redirect'])) { | |
336 | // force a redirect | |
337 | HeaderUtil::redirect($data['redirect'], true, false); | |
338 | ||
339 | exit; | |
340 | } elseif (!empty($data['application']) && $data['application'] !== $application) { | |
341 | $override = ControllerMap::getInstance()->getApplicationOverride($application, $data['controller']); | |
342 | if ($application !== $override) { | |
343 | HeaderUtil::redirect( | |
344 | LinkHandler::getInstance()->getLink( | |
345 | ControllerMap::getInstance()->resolve( | |
346 | $data['application'], | |
347 | $data['controller'], | |
348 | false | |
349 | )['controller'], | |
350 | ['application' => $data['application']] | |
351 | ), | |
352 | true, | |
353 | true | |
354 | ); | |
355 | ||
356 | exit; | |
357 | } | |
358 | } | |
359 | ||
360 | // copy route data | |
361 | foreach ($data as $key => $value) { | |
362 | $routeData[$key] = $value; | |
363 | } | |
364 | ||
365 | $routeData['isDefaultController'] = true; | |
366 | } | |
367 | ||
368 | /** | |
369 | * Returns the active request object. | |
370 | * | |
371 | * @return Request | |
372 | */ | |
373 | public function getActiveRequest() | |
374 | { | |
375 | return $this->activeRequest; | |
376 | } | |
377 | ||
378 | /** | |
379 | * Returns true if the request is an acp request. | |
380 | * | |
381 | * @return bool | |
382 | */ | |
383 | public function isACPRequest() | |
384 | { | |
385 | return $this->isACPRequest; | |
386 | } | |
387 | ||
388 | /** | |
389 | * Returns true, if current host mismatches any known domain. | |
390 | * | |
391 | * @return bool | |
392 | */ | |
393 | public function inRescueMode() | |
394 | { | |
395 | return $this->inRescueMode; | |
396 | } | |
dcb3a44c | 397 | } |