mediawiki-extensions-Math/MathRestbaseInterface.php
physikerwelt 1ec767791b Make math usable without RESTbase
* Do not contact RESTbase in texvc rendering mode and
  use the well established tex checking that is already build
  into texvc.
* Do not use RESTbase in LaTeXML mode.

Background:
In I1982612e8c6a356e3dbdf447724ac82e5968cc77 RESTbased replaced
texvccheck that was a derivate of texvc designed for people
that wanted to use secure MathML rendering using mathoid.
The integration of mathoid to restbase made this feature obsolete.
However, texvccheck was not only used to check the latex input
that was sent to mathoid, but also the string which was sent to texvc.
Since texvc has already build in tex checking this is not
required and does not improve security.
Finally, users updating from old versions of the math extension
(prior to 2014) that do not have textexvccheck installed,
do not need to compile the texvccheck binary after this change.

PS5: Also treats the case where VisualEditor is not installed.

Bug: T121173
Change-Id: I1bd076b09206869b5ed75280d22e1b36bfb8d8ad
2015-12-11 22:04:12 +01:00

304 lines
7.6 KiB
PHP

<?php
/**
* MediaWiki math extension
*
* (c) 2002-2015 various MediaWiki contributors
* GPLv2 license; info in main package.
*/
use MediaWiki\Logger\LoggerFactory;
class MathRestbaseInterface {
private $hash = false;
private $tex;
private $type;
private $checkedTex;
private $success;
private $identifiers;
private $error;
/**
* MathRestbaseInterface constructor.
* @param string $tex
* @param bool $displayStyle
*/
public function __construct( $tex = '', $displayStyle = true ) {
$this->tex = $tex;
if ( $displayStyle ) {
$this->type = 'tex';
} else {
$this->type = 'inline-tex';
}
}
/**
* @return string MathML code
* @throws MWException
*/
public function getMathML() {
return $this->getContent( 'mml' );
}
private function getContent( $type ) {
$this->calculateHash();
$request = array(
'method' => 'GET',
'url' => $this->getUrl( "media/math/render/$type/{$this->hash}" )
);
$serviceClient = $this->getServiceClient();
$response = $serviceClient->run( $request );
if ( $response['code'] === 200 ) {
return $response['body'];
}
$this->log()->error( 'Restbase math server problem:', array(
'request' => $request,
'response' => $response,
'type' => $type,
'tex' => $this->tex
) );
throw new MWException( "Cannot get $type. Server problem." );
}
private function calculateHash() {
if ( !$this->hash ) {
if ( !$this->checkTeX() ) {
throw new MWException( "TeX input is invalid." );
}
}
}
public function checkTeX() {
$postData = array(
'type' => $this->type,
'q' => $this->tex
);
$requestResult = $this->makeRestbaseCheckRequest( $postData, $res );
$json = json_decode( $res );
if ( $requestResult ) {
$this->success = $json->success;
$this->checkedTex = $json->checked;
$this->identifiers = $json->identifiers;
return true;
} else {
if ( isset( $json->detail ) && isset( $json->detail->success ) ) {
$this->success = $json->detail->success;
$this->error = $json->detail;
} else {
$this->success = false;
$this->setErrorMessage( 'Math extension cannot connect to Restbase.' );
}
return false;
}
}
/**
* Performs a service request
* Generates error messages on failure
* @see Http::post()
*
* @param string $post the encoded post request
* @param mixed $res the result
* @return bool success
*/
private function makeRestbaseCheckRequest( $post, &$res ) {
$res = null;
$request = array(
'method' => 'POST',
'body' => $post
);
$serviceClient = $this->getServiceClient();
$request['url'] = $this->getUrl( "media/math/check/{$this->type}" );
$response = $serviceClient->run( $request );
if ( $response['code'] === 200 ) {
$res = $response['body'];
$headers = $response['headers'];
$this->hash = $headers['x-resource-location'];
return true;
} else {
$res = $response['body'];
$this->log()->debug( 'Tex check failed:', array(
'post' => $post,
'error' => $response['error'],
'url' => $request['url']
) );
return false;
}
}
private function getServiceClient() {
global $wgVirtualRestConfig;
$serviceClient = new VirtualRESTServiceClient( new MultiHttpClient( array() ) );
if ( isset( $wgVirtualRestConfig['modules']['restbase'] ) ) {
$cfg = $wgVirtualRestConfig['modules']['restbase'];
$cfg['parsoidCompat'] = false;
$vrsObject = new RestbaseVirtualRESTService( $cfg );
$serviceClient->mount( '/mathoid/', $vrsObject );
}
return $serviceClient;
}
/**
* The URL is generated accoding to the following logic:
*
* Case A: <code>$internal = false</code>, which means one needs an URL that is accessible from
* outside:
*
* --> If <code>$wgMathFullRestbaseURL</code> is configured use it, otherwise fall back try to
* <code>$wgVisualEditorFullRestbaseURL</code>. (Note, that this is not be worse than failing
* immediately.)
*
* Case B: <code> $internal= true</code>, which means one needs to access content from Restbase
* which does not need to be accessible from outside:
*
* --> Use the mount point whenever possible. If the mount point is not available, use
* <code>$wgMathFullRestbaseURL</code> with fallback to <code>wgVisualEditorFullRestbaseURL</code>
*
* @param string $path
* @param bool|true $internal
* @return string
* @throws MWException
*/
public function getUrl( $path, $internal = true ) {
global $wgVirtualRestConfig, $wgMathFullRestbaseURL, $wgVisualEditorFullRestbaseURL;
if ( $internal && isset( $wgVirtualRestConfig['modules']['restbase'] ) ) {
return "/mathoid/local/v1/$path";
}
if ( $wgMathFullRestbaseURL ) {
return "{$wgMathFullRestbaseURL}v1/$path";
}
if ( $wgVisualEditorFullRestbaseURL ) {
return "{$wgVisualEditorFullRestbaseURL}v1/$path";
}
$msg = 'Math extension can not find Restbase URL. Please specify $wgMathFullRestbaseURL.';
$this->setErrorMessage( $msg );
throw new MWException( $msg );
}
/**
* @return \Psr\Log\LoggerInterface
*/
private function log() {
return LoggerFactory::getInstance( 'Math' );
}
public function getSvg() {
return $this->getContent( 'svg' );
}
/**
* @param bool|false $skipConfigCheck
* @return bool
*/
public function checkBackend( $skipConfigCheck = false ) {
try {
$request = array(
'method' => 'GET',
'url' => $this->getUrl( '?spec' )
);
} catch ( Exception $e ) {
return false;
}
$serviceClient = $this->getServiceClient();
$response = $serviceClient->run( $request );
if ( $response['code'] === 200 ) {
return $skipConfigCheck || $this->checkConfig();
}
$this->log()->error( "Restbase backend is not correctly set up.", array(
'request' => $request,
'response' => $response
) );
return false;
}
/**
* Generates a unique TeX string, renders it and gets it via a public URL.
* The method fails, if the public URL does not point to the same server, who did render
* the unique TeX input in the first place.
* @return bool
*/
private function checkConfig() {
// Generates a TeX string that probably has not been generated before
$uniqueTeX = uniqid( 't=', true );
$testInterface = new MathRestbaseInterface( $uniqueTeX );
if ( ! $testInterface->checkTeX() ){
$this->log()->warning( 'Config check failed, since test expression was considered as invalid.',
array( 'uniqueTeX' => $uniqueTeX ) );
return false;
}
try {
$url = $testInterface->getFullSvgUrl();
$req = MWHttpRequest::factory( $url );
$status = $req->execute();
if ( $status->isOK() ){
return true;
}
$this->log()->warning( 'Config check failed, due to an invalid response code.',
array( 'responseCode' => $status ) );
} catch ( Exception $e ) {
$this->log()->warning( 'Config check failed, due to an exception.', array( $e ) );
return false;
}
}
/**
* Gets a publicly accessible link to the generated SVG image.
* @return string
* @throws MWException
*/
public function getFullSvgUrl() {
$this->calculateHash();
return $this->getUrl( "media/math/render/svg/{$this->hash}", false );
}
/**
* @return string
*/
public function getCheckedTex() {
return $this->checkedTex;
}
/**
* @return boolean
*/
public function getSuccess() {
return $this->success;
}
/**
* @return array
*/
public function getIdentifiers() {
return $this->identifiers;
}
/**
* @return stdClass
*/
public function getError() {
return $this->error;
}
/**
* @return string
*/
public function getTex() {
return $this->tex;
}
/**
* @return string
*/
public function getType() {
return $this->type;
}
private function setErrorMessage( $msg ) {
$this->error = (object)array(
'error' =>
(object)array( 'message' => $msg )
);
}
}