Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
48.54% covered (danger)
48.54%
50 / 103
42.86% covered (danger)
42.86%
3 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
EntryPoint
48.54% covered (danger)
48.54%
50 / 103
42.86% covered (danger)
42.86%
3 / 7
45.65
0.00% covered (danger)
0.00%
0 / 1
 createRouter
0.00% covered (danger)
0.00%
0 / 33
0.00% covered (danger)
0.00%
0 / 1
2
 getMainRequest
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 doSetup
68.18% covered (warning)
68.18%
15 / 22
0.00% covered (danger)
0.00%
0 / 1
2.13
 getTextFormatters
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setRouter
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 execute
80.00% covered (warning)
80.00%
24 / 30
0.00% covered (danger)
0.00%
0 / 1
6.29
1<?php
2
3namespace MediaWiki\Rest;
4
5use MediaWiki\Config\ServiceOptions;
6use MediaWiki\Context\IContextSource;
7use MediaWiki\Context\RequestContext;
8use MediaWiki\EntryPointEnvironment;
9use MediaWiki\Exception\MWExceptionRenderer;
10use MediaWiki\MainConfigNames;
11use MediaWiki\MediaWikiEntryPoint;
12use MediaWiki\MediaWikiServices;
13use MediaWiki\Registration\ExtensionRegistry;
14use MediaWiki\Rest\BasicAccess\CompoundAuthorizer;
15use MediaWiki\Rest\BasicAccess\MWBasicAuthorizer;
16use MediaWiki\Rest\Module\ModuleManager;
17use MediaWiki\Rest\Reporter\MWErrorReporter;
18use MediaWiki\Rest\Validator\Validator;
19use Wikimedia\Message\ITextFormatter;
20
21/**
22 * @internal
23 */
24class EntryPoint extends MediaWikiEntryPoint {
25
26    private RequestInterface $request;
27    private ?Router $router = null;
28    private ?CorsUtils $cors  = null;
29
30    /**
31     * @internal Public for use in core tests
32     *
33     * @param MediaWikiServices $services
34     * @param IContextSource $context
35     * @param RequestInterface $request
36     * @param ResponseFactory $responseFactory
37     * @param CorsUtils $cors
38     *
39     * @return Router
40     */
41    public static function createRouter(
42        MediaWikiServices $services,
43        IContextSource $context,
44        RequestInterface $request,
45        ResponseFactory $responseFactory,
46        CorsUtils $cors
47    ): Router {
48        $conf = $services->getMainConfig();
49
50        $authority = $context->getAuthority();
51        $authorizer = new CompoundAuthorizer();
52        $authorizer
53            ->addAuthorizer( new MWBasicAuthorizer( $authority ) )
54            ->addAuthorizer( $cors );
55
56        $objectFactory = $services->getObjectFactory();
57        $restValidator = new Validator( $objectFactory,
58            $request,
59            $authority
60        );
61
62        $stats = $services->getStatsFactory();
63
64        $moduleManager = new ModuleManager(
65            new ServiceOptions( ModuleManager::CONSTRUCTOR_OPTIONS, $conf ),
66            $services->getLocalServerObjectCache(),
67            $responseFactory
68        );
69
70        return ( new Router(
71            $moduleManager->getRouteFiles(),
72            ExtensionRegistry::getInstance()->getAttribute( 'RestRoutes' ),
73            new ServiceOptions( Router::CONSTRUCTOR_OPTIONS, $conf ),
74            $services->getLocalServerObjectCache(),
75            $responseFactory,
76            $authorizer,
77            $authority,
78            $objectFactory,
79            $restValidator,
80            new MWErrorReporter(),
81            $services->getHookContainer(),
82            $context->getRequest()->getSession()
83        ) )
84            ->setCors( $cors )
85            ->setStats( $stats );
86    }
87
88    /**
89     * @internal
90     * @return RequestInterface The RequestInterface object used by this entry point.
91     */
92    public static function getMainRequest(): RequestInterface {
93        static $mainRequest = null;
94
95        if ( $mainRequest === null ) {
96            $conf = MediaWikiServices::getInstance()->getMainConfig();
97            $mainRequest = new RequestFromGlobals( [
98                'cookiePrefix' => $conf->get( MainConfigNames::CookiePrefix )
99            ] );
100        }
101
102        return $mainRequest;
103    }
104
105    protected function doSetup() {
106        parent::doSetup();
107
108        $context = RequestContext::getMain();
109
110        $responseFactory = new ResponseFactory( $this->getTextFormatters() );
111        $responseFactory->setShowExceptionDetails(
112            MWExceptionRenderer::shouldShowExceptionDetails()
113        );
114
115        $this->cors = new CorsUtils(
116            new ServiceOptions(
117                CorsUtils::CONSTRUCTOR_OPTIONS,
118                $this->getServiceContainer()->getMainConfig()
119            ),
120            $responseFactory,
121            $context->getUser()
122        );
123
124        if ( !$this->router ) {
125            $this->router = $this->createRouter(
126                $this->getServiceContainer(),
127                $context,
128                $this->request,
129                $responseFactory,
130                $this->cors
131            );
132        }
133    }
134
135    /**
136     * Get a TextFormatter array from MediaWikiServices
137     *
138     * @return ITextFormatter[]
139     */
140    private function getTextFormatters() {
141        $services = $this->getServiceContainer();
142
143        $code = $services->getContentLanguageCode()->toString();
144        $langs = array_unique( [ $code, 'en' ] );
145        $textFormatters = [];
146        $factory = $services->getMessageFormatterFactory();
147
148        foreach ( $langs as $lang ) {
149            $textFormatters[] = $factory->getTextFormatter( $lang );
150        }
151
152        return $textFormatters;
153    }
154
155    public function __construct(
156        RequestInterface $request,
157        RequestContext $context,
158        EntryPointEnvironment $environment,
159        MediaWikiServices $mediaWikiServices
160    ) {
161        parent::__construct( $context, $environment, $mediaWikiServices );
162
163        $this->request = $request;
164    }
165
166    /**
167     * Sets the router to use.
168     * Intended for testing.
169     */
170    public function setRouter( Router $router ): void {
171        $this->router = $router;
172    }
173
174    public function execute() {
175        $this->startOutputBuffer();
176
177        // IDEA: Move the call to cors->modifyResponse() into Module,
178        //       so it's in the same class as cors->createPreflightResponse().
179        $response = $this->cors->modifyResponse(
180            $this->request,
181            $this->router->execute( $this->request )
182        );
183
184        $webResponse = $this->getResponse();
185
186        $webResponse->header(
187            'HTTP/' . $response->getProtocolVersion() . ' ' . $response->getStatusCode() . ' ' .
188            $response->getReasonPhrase()
189        );
190
191        foreach ( $response->getRawHeaderLines() as $line ) {
192            $webResponse->header( $line );
193        }
194
195        foreach ( $response->getCookies() as $cookie ) {
196            $webResponse->setCookie(
197                $cookie['name'],
198                $cookie['value'],
199                $cookie['expiry'],
200                $cookie['options']
201            );
202        }
203
204        // Clear all errors that might have been displayed if display_errors=On
205        $this->discardOutputBuffer();
206
207        $stream = $response->getBody();
208        $stream->rewind();
209
210        $this->prepareForOutput();
211
212        if ( $stream instanceof CopyableStreamInterface ) {
213            $stream->copyToStream( fopen( 'php://output', 'w' ) );
214        } else {
215            while ( true ) {
216                $buffer = $stream->read( 65536 );
217                if ( $buffer === '' ) {
218                    break;
219                }
220                $this->print( $buffer );
221            }
222        }
223    }
224
225}