/** * Creates the virtual REST service object to be used in VE's API calls. The * method determines whether to instantiate a ParsoidVirtualRESTService or a * RestbaseVirtualRESTService object based on configuration directives: if * $wgVirtualRestConfig['modules']['restbase'] is defined, RESTBase is chosen, * otherwise Parsoid is used (either by using the MW Core config, or the * VE-local one). * * @return VirtualRESTService the VirtualRESTService object to use */ private function getVRSObject() { // the params array to create the service object with $params = array(); // the VRS class to use, defaults to Parsoid $class = 'ParsoidVirtualRESTService'; $config = $this->veConfig; // the global virtual rest service config object, if any $vrs = $this->getConfig()->get('VirtualRestConfig'); if (isset($vrs['modules']) && isset($vrs['modules']['restbase'])) { // if restbase is available, use it $params = $vrs['modules']['restbase']; $class = 'RestbaseVirtualRESTService'; // remove once VE generates restbase paths $params['parsoidCompat'] = true; } elseif (isset($vrs['modules']) && isset($vrs['modules']['parsoid'])) { // there's a global parsoid config, use it next $params = $vrs['modules']['parsoid']; } else { // no global modules defined, fall back to old defaults $params = array('URL' => $config->get('VisualEditorParsoidURL'), 'prefix' => $config->get('VisualEditorParsoidPrefix'), 'timeout' => $config->get('VisualEditorParsoidTimeout'), 'HTTPProxy' => $config->get('VisualEditorParsoidHTTPProxy'), 'forwardCookies' => $config->get('VisualEditorParsoidForwardCookies')); } // merge the global and service-specific params if (isset($vrs['global'])) { $params = array_merge($vrs['global'], $params); } // set up cookie forwarding if ($params['forwardCookies'] && !User::isEveryoneAllowed('read')) { $params['forwardCookies'] = RequestContext::getMain()->getRequest()->getHeader('Cookie'); } else { $params['forwardCookies'] = false; } // create the VRS object and return it return new $class($params); }
function onView() { $this->getOutput()->disable(); $request = $this->getRequest(); $response = $request->response(); $config = $this->context->getConfig(); if (!$request->checkUrlExtension()) { return; } if ($this->getOutput()->checkLastModified($this->page->getTouched())) { return; // Client cache fresh and headers sent, nothing more to do. } $gen = $request->getVal('gen'); if ($gen == 'css' || $gen == 'js') { $this->gen = true; } $contentType = $this->getContentType(); $maxage = $request->getInt('maxage', $config->get('SquidMaxage')); $smaxage = $request->getIntOrNull('smaxage'); if ($smaxage === null) { if ($contentType == 'text/css' || $contentType == 'text/javascript') { // CSS/JS raw content has its own CDN max age configuration. // Note: Title::getCdnUrls() includes action=raw for css/js pages, // so if using the canonical url, this will get HTCP purges. $smaxage = intval($config->get('ForcedRawSMaxage')); } else { // No CDN cache for anything else $smaxage = 0; } } $response->header('Content-type: ' . $contentType . '; charset=UTF-8'); // Output may contain user-specific data; // vary generated content for open sessions on private wikis $privateCache = !User::isEveryoneAllowed('read') && ($smaxage == 0 || session_id() != ''); // Don't accidentally cache cookies if user is logged in (T55032) $privateCache = $privateCache || $this->getUser()->isLoggedIn(); $mode = $privateCache ? 'private' : 'public'; $response->header('Cache-Control: ' . $mode . ', s-maxage=' . $smaxage . ', max-age=' . $maxage); $text = $this->getRawText(); // Don't return a 404 response for CSS or JavaScript; // 404s aren't generally cached and it would create // extra hits when user CSS/JS are on and the user doesn't // have the pages. if ($text === false && $contentType == 'text/x-wiki') { $response->statusHeader(404); } if (!Hooks::run('RawPageViewBeforeOutput', array(&$this, &$text))) { wfDebug(__METHOD__ . ": RawPageViewBeforeOutput hook broke raw page output.\n"); } echo $text; }
protected function requestParsoid($method, $title, $params) { global $wgVisualEditorParsoidURL, $wgVisualEditorParsoidTimeout, $wgVisualEditorParsoidForwardCookies; $url = $wgVisualEditorParsoidURL . '/' . urlencode($this->getApiSource()) . '/' . urlencode($title->getPrefixedDBkey()); $data = array_merge($this->getProxyConf(), array('method' => $method, 'timeout' => $wgVisualEditorParsoidTimeout)); if ($method === 'POST') { $data['postData'] = $params; } else { $url = wfAppendQuery($url, $params); } $req = MWHttpRequest::factory($url, $data); // Forward cookies, but only if configured to do so and if there are read restrictions if ($wgVisualEditorParsoidForwardCookies && !User::isEveryoneAllowed('read')) { $req->setHeader('Cookie', $this->getRequest()->getHeader('Cookie')); } $status = $req->execute(); if ($status->isOK()) { // Pass thru performance data from Parsoid to the client, unless the response was // served directly from Varnish, in which case discard the value of the XPP header // and use it to declare the cache hit instead. $xCache = $req->getResponseHeader('X-Cache'); if (is_string($xCache) && strpos(strtolower($xCache), 'hit') !== false) { $xpp = 'cached-response=true'; $hit = true; } else { $xpp = $req->getResponseHeader('X-Parsoid-Performance'); $hit = false; } WikiaLogger::instance()->debug('ApiVisualEditor', array('hit' => $hit, 'method' => $method, 'url' => $url)); if ($xpp !== null) { $resp = $this->getRequest()->response(); $resp->header('X-Parsoid-Performance: ' . $xpp); } } elseif ($status->isGood()) { $this->dieUsage($req->getContent(), 'parsoidserver-http-' . $req->getStatus()); } elseif ($errors = $status->getErrorsByType('error')) { $error = $errors[0]; $code = $error['message']; if (count($error['params'])) { $message = $error['params'][0]; } else { $message = 'MWHttpRequest error'; } $this->dieUsage($message, 'parsoidserver-' . $code); } // TODO pass through X-Parsoid-Performance header, merge with getHTML above return $req->getContent(); }
/** * Check that the user is allowed to read this page. * * @param string $action The action to check * @param User $user User to check * @param array $errors List of current errors * @param string $rigor Same format as Title::getUserPermissionsErrors() * @param bool $short Short circuit on first error * * @return array List of errors */ private function checkReadPermissions($action, $user, $errors, $rigor, $short) { global $wgWhitelistRead, $wgWhitelistReadRegexp; $whitelisted = false; if (User::isEveryoneAllowed('read')) { # Shortcut for public wikis, allows skipping quite a bit of code $whitelisted = true; } elseif ($user->isAllowed('read')) { # If the user is allowed to read pages, he is allowed to read all pages $whitelisted = true; } elseif ($this->isSpecial('Userlogin') || $this->isSpecial('ChangePassword') || $this->isSpecial('PasswordReset')) { # Always grant access to the login page. # Even anons need to be able to log in. $whitelisted = true; } elseif (is_array($wgWhitelistRead) && count($wgWhitelistRead)) { # Time to check the whitelist # Only do these checks is there's something to check against $name = $this->getPrefixedText(); $dbName = $this->getPrefixedDBkey(); // Check for explicit whitelisting with and without underscores if (in_array($name, $wgWhitelistRead, true) || in_array($dbName, $wgWhitelistRead, true)) { $whitelisted = true; } elseif ($this->getNamespace() == NS_MAIN) { # Old settings might have the title prefixed with # a colon for main-namespace pages if (in_array(':' . $name, $wgWhitelistRead)) { $whitelisted = true; } } elseif ($this->isSpecialPage()) { # If it's a special page, ditch the subpage bit and check again $name = $this->getDBkey(); list($name, ) = SpecialPageFactory::resolveAlias($name); if ($name) { $pure = SpecialPage::getTitleFor($name)->getPrefixedText(); if (in_array($pure, $wgWhitelistRead, true)) { $whitelisted = true; } } } } if (!$whitelisted && is_array($wgWhitelistReadRegexp) && !empty($wgWhitelistReadRegexp)) { $name = $this->getPrefixedText(); // Check for regex whitelisting foreach ($wgWhitelistReadRegexp as $listItem) { if (preg_match($listItem, $name)) { $whitelisted = true; break; } } } if (!$whitelisted) { # If the title is not whitelisted, give extensions a chance to do so... Hooks::run('TitleReadWhitelist', array($this, $user, &$whitelisted)); if (!$whitelisted) { $errors[] = $this->missingPermissionError($action, $short); } } return $errors; }
/** * Check for sufficient permissions to execute * @param ApiBase $module An Api module */ protected function checkExecutePermissions($module) { $user = $this->getUser(); if ($module->isReadMode() && !User::isEveryoneAllowed('read') && !$user->isAllowed('read')) { $this->dieUsageMsg('readrequired'); } if ($module->isWriteMode()) { if (!$this->mEnableWrite) { $this->dieUsageMsg('writedisabled'); } if (!$user->isAllowed('writeapi')) { $this->dieUsageMsg('writerequired'); } if (wfReadOnly()) { $this->dieReadOnly(); } } // Allow extensions to stop execution for arbitrary reasons. $message = false; if (!Hooks::run('ApiCheckCanExecute', array($module, $user, &$message))) { $this->dieUsageMsg($message); } }
/** * Check for sufficient permissions to execute * @param ApiBase $module An Api module */ protected function checkExecutePermissions($module) { $user = $this->getUser(); if ($module->isReadMode() && !User::isEveryoneAllowed('read') && !$user->isAllowed('read')) { $this->dieUsageMsg('readrequired'); } if ($module->isWriteMode()) { if (!$this->mEnableWrite) { $this->dieUsageMsg('writedisabled'); } elseif (!$user->isAllowed('writeapi')) { $this->dieUsageMsg('writerequired'); } elseif ($this->getRequest()->getHeader('Promise-Non-Write-API-Action')) { $this->dieUsage("Promise-Non-Write-API-Action HTTP header cannot be sent to write API modules", 'promised-nonwrite-api'); } $this->checkReadOnly($module); } // Allow extensions to stop execution for arbitrary reasons. $message = false; if (!Hooks::run('ApiCheckCanExecute', array($module, $user, &$message))) { $this->dieUsageMsg($message); } }
/** * Pass the request to our internal function. * BEWARE! Data are passed as they have been supplied by the user, * they should be carefully handled in the function processing the * request. * * @param User $user */ function performAction(User $user) { if (empty($this->mode)) { return; } if (!in_array($this->func_name, $this->config->get('AjaxExportList'))) { wfDebug(__METHOD__ . ' Bad Request for unknown function ' . $this->func_name . "\n"); wfHttpError(400, 'Bad Request', "unknown function " . $this->func_name); } elseif (!User::isEveryoneAllowed('read') && !$user->isAllowed('read')) { wfHttpError(403, 'Forbidden', 'You are not allowed to view pages.'); } else { wfDebug(__METHOD__ . ' dispatching ' . $this->func_name . "\n"); try { $result = call_user_func_array($this->func_name, $this->args); if ($result === false || $result === null) { wfDebug(__METHOD__ . ' ERROR while dispatching ' . $this->func_name . "(" . var_export($this->args, true) . "): " . "no data returned\n"); wfHttpError(500, 'Internal Error', "{$this->func_name} returned no data"); } else { if (is_string($result)) { $result = new AjaxResponse($result); } // Make sure DB commit succeeds before sending a response wfGetLBFactory()->commitMasterChanges(__METHOD__); $result->sendHeaders(); $result->printText(); wfDebug(__METHOD__ . ' dispatch complete for ' . $this->func_name . "\n"); } } catch (Exception $e) { wfDebug(__METHOD__ . ' ERROR while dispatching ' . $this->func_name . "(" . var_export($this->args, true) . "): " . get_class($e) . ": " . $e->getMessage() . "\n"); if (!headers_sent()) { wfHttpError(500, 'Internal Error', $e->getMessage()); } else { print $e->getMessage(); } } } }
function onView() { global $wgSquidMaxage, $wgForcedRawSMaxage; $this->getOutput()->disable(); $request = $this->getRequest(); if (!$request->checkUrlExtension()) { return; } if ($this->getOutput()->checkLastModified($this->page->getTouched())) { return; // Client cache fresh and headers sent, nothing more to do. } # special case for 'generated' raw things: user css/js # This is deprecated and will only return empty content $gen = $request->getVal('gen'); $smaxage = $request->getIntOrNull('smaxage'); if ($gen == 'css' || $gen == 'js') { $this->mGen = $gen; if ($smaxage === null) { $smaxage = $wgSquidMaxage; } } else { $this->mGen = false; } $contentType = $this->getContentType(); # Force caching for CSS and JS raw content, default: 5 minutes. # Note: If using a canonical url for userpage css/js, we send an HTCP purge. if ($smaxage === null) { if ($contentType == 'text/css' || $contentType == 'text/javascript') { $smaxage = intval($wgForcedRawSMaxage); } else { $smaxage = 0; } } $maxage = $request->getInt('maxage', $wgSquidMaxage); $response = $request->response(); $response->header('Content-type: ' . $contentType . '; charset=UTF-8'); # Output may contain user-specific data; # vary generated content for open sessions on private wikis $privateCache = !User::isEveryoneAllowed('read') && ($smaxage == 0 || session_id() != ''); // Bug 53032 - make this private if user is logged in, // so we don't accidentally cache cookies $privateCache = $privateCache ?: $this->getUser()->isLoggedIn(); # allow the client to cache this for 24 hours $mode = $privateCache ? 'private' : 'public'; $response->header('Cache-Control: ' . $mode . ', s-maxage=' . $smaxage . ', max-age=' . $maxage); $text = $this->getRawText(); if ($text === false && $contentType == 'text/x-wiki') { # Don't return a 404 response for CSS or JavaScript; # 404s aren't generally cached and it would create # extra hits when user CSS/JS are on and the user doesn't # have the pages. $response->header('HTTP/1.x 404 Not Found'); } if (!wfRunHooks('RawPageViewBeforeOutput', array(&$this, &$text))) { wfDebug(__METHOD__ . ": RawPageViewBeforeOutput hook broke raw page output.\n"); } echo $text; }
/** * Pass the request to our internal function. * BEWARE! Data are passed as they have been supplied by the user, * they should be carefully handled in the function processing the * request. */ function performAction() { global $wgAjaxExportList, $wgUser; if (empty($this->mode)) { return; } wfProfileIn(__METHOD__); if (!in_array($this->func_name, $wgAjaxExportList)) { wfDebug(__METHOD__ . ' Bad Request for unknown function ' . $this->func_name . "\n"); wfHttpError(400, 'Bad Request', "unknown function " . $this->func_name); } elseif (!User::isEveryoneAllowed('read') && !$wgUser->isAllowed('read')) { wfHttpError(403, 'Forbidden', 'You are not allowed to view pages.'); } else { wfDebug(__METHOD__ . ' dispatching ' . $this->func_name . "\n"); try { $result = call_user_func_array($this->func_name, $this->args); if ($result === false || $result === null) { wfDebug(__METHOD__ . ' ERROR while dispatching ' . $this->func_name . "(" . var_export($this->args, true) . "): " . "no data returned\n"); wfHttpError(500, 'Internal Error', "{$this->func_name} returned no data"); } else { if (is_string($result)) { $result = new AjaxResponse($result); } $result->sendHeaders(); $result->printText(); wfDebug(__METHOD__ . ' dispatch complete for ' . $this->func_name . "\n"); } } catch (Exception $e) { wfDebug(__METHOD__ . ' ERROR while dispatching ' . $this->func_name . "(" . var_export($this->args, true) . "): " . get_class($e) . ": " . $e->getMessage() . "\n"); if (!headers_sent()) { wfHttpError(500, 'Internal Error', $e->getMessage()); } else { print $e->getMessage(); } } } wfProfileOut(__METHOD__); }