Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
47.83% |
55 / 115 |
|
37.50% |
3 / 8 |
CRAP | |
0.00% |
0 / 1 |
EntryPoint | |
47.83% |
55 / 115 |
|
37.50% |
3 / 8 |
70.27 | |
0.00% |
0 / 1 |
createRouter | |
0.00% |
0 / 28 |
|
0.00% |
0 / 1 |
2 | |||
getMainRequest | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
6 | |||
doSetup | |
74.07% |
20 / 27 |
|
0.00% |
0 / 1 |
2.07 | |||
getTextFormatters | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
2 | |||
getRouteFiles | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
20 | |||
__construct | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
setRouter | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
execute | |
80.00% |
24 / 30 |
|
0.00% |
0 / 1 |
6.29 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Rest; |
4 | |
5 | use ExtensionRegistry; |
6 | use MediaWiki\Config\Config; |
7 | use MediaWiki\Config\ServiceOptions; |
8 | use MediaWiki\Context\IContextSource; |
9 | use MediaWiki\Context\RequestContext; |
10 | use MediaWiki\EntryPointEnvironment; |
11 | use MediaWiki\MainConfigNames; |
12 | use MediaWiki\MediaWikiEntryPoint; |
13 | use MediaWiki\MediaWikiServices; |
14 | use MediaWiki\Rest\BasicAccess\CompoundAuthorizer; |
15 | use MediaWiki\Rest\BasicAccess\MWBasicAuthorizer; |
16 | use MediaWiki\Rest\Reporter\MWErrorReporter; |
17 | use MediaWiki\Rest\Validator\Validator; |
18 | use MediaWiki\Title\Title; |
19 | use MWExceptionRenderer; |
20 | use Wikimedia\Message\ITextFormatter; |
21 | |
22 | /** |
23 | * @internal |
24 | */ |
25 | class EntryPoint extends MediaWikiEntryPoint { |
26 | |
27 | private RequestInterface $request; |
28 | private ?Router $router = null; |
29 | private ?CorsUtils $cors = null; |
30 | |
31 | /** |
32 | * @internal Public for use in core tests |
33 | * |
34 | * @param MediaWikiServices $services |
35 | * @param IContextSource $context |
36 | * @param RequestInterface $request |
37 | * @param ResponseFactory $responseFactory |
38 | * @param CorsUtils $cors |
39 | * |
40 | * @return Router |
41 | */ |
42 | public static function createRouter( |
43 | MediaWikiServices $services, |
44 | IContextSource $context, |
45 | RequestInterface $request, |
46 | ResponseFactory $responseFactory, |
47 | CorsUtils $cors |
48 | ): Router { |
49 | $conf = $services->getMainConfig(); |
50 | |
51 | $authority = $context->getAuthority(); |
52 | $authorizer = new CompoundAuthorizer(); |
53 | $authorizer |
54 | ->addAuthorizer( new MWBasicAuthorizer( $authority ) ) |
55 | ->addAuthorizer( $cors ); |
56 | |
57 | $objectFactory = $services->getObjectFactory(); |
58 | $restValidator = new Validator( $objectFactory, |
59 | $request, |
60 | $authority |
61 | ); |
62 | |
63 | $stats = $services->getStatsdDataFactory(); |
64 | |
65 | return ( new Router( |
66 | self::getRouteFiles( $conf ), |
67 | ExtensionRegistry::getInstance()->getAttribute( 'RestRoutes' ), |
68 | new ServiceOptions( Router::CONSTRUCTOR_OPTIONS, $conf ), |
69 | $services->getLocalServerObjectCache(), |
70 | $responseFactory, |
71 | $authorizer, |
72 | $authority, |
73 | $objectFactory, |
74 | $restValidator, |
75 | new MWErrorReporter(), |
76 | $services->getHookContainer(), |
77 | $context->getRequest()->getSession() |
78 | ) ) |
79 | ->setCors( $cors ) |
80 | ->setStats( $stats ); |
81 | } |
82 | |
83 | /** |
84 | * @internal |
85 | * @return RequestInterface The RequestInterface object used by this entry point. |
86 | */ |
87 | public static function getMainRequest(): RequestInterface { |
88 | static $mainRequest = null; |
89 | |
90 | if ( $mainRequest === null ) { |
91 | $conf = MediaWikiServices::getInstance()->getMainConfig(); |
92 | $mainRequest = new RequestFromGlobals( [ |
93 | 'cookiePrefix' => $conf->get( MainConfigNames::CookiePrefix ) |
94 | ] ); |
95 | } |
96 | |
97 | return $mainRequest; |
98 | } |
99 | |
100 | protected function doSetup() { |
101 | parent::doSetup(); |
102 | |
103 | $context = RequestContext::getMain(); |
104 | |
105 | // Set $wgTitle and the title in RequestContext, as in api.php |
106 | global $wgTitle; |
107 | $wgTitle = Title::makeTitle( |
108 | NS_SPECIAL, |
109 | 'Badtitle/rest.php' |
110 | ); |
111 | $context->setTitle( $wgTitle ); |
112 | |
113 | $responseFactory = new ResponseFactory( $this->getTextFormatters() ); |
114 | $responseFactory->setShowExceptionDetails( |
115 | MWExceptionRenderer::shouldShowExceptionDetails() |
116 | ); |
117 | |
118 | $this->cors = new CorsUtils( |
119 | new ServiceOptions( |
120 | CorsUtils::CONSTRUCTOR_OPTIONS, |
121 | $this->getServiceContainer()->getMainConfig() |
122 | ), |
123 | $responseFactory, |
124 | $context->getUser() |
125 | ); |
126 | |
127 | if ( !$this->router ) { |
128 | $this->router = $this->createRouter( |
129 | $this->getServiceContainer(), |
130 | $context, |
131 | $this->request, |
132 | $responseFactory, |
133 | $this->cors |
134 | ); |
135 | } |
136 | } |
137 | |
138 | /** |
139 | * Get a TextFormatter array from MediaWikiServices |
140 | * |
141 | * @return ITextFormatter[] |
142 | */ |
143 | private function getTextFormatters() { |
144 | $services = $this->getServiceContainer(); |
145 | |
146 | $code = $services->getContentLanguage()->getCode(); |
147 | $langs = array_unique( [ $code, 'en' ] ); |
148 | $textFormatters = []; |
149 | $factory = $services->getMessageFormatterFactory(); |
150 | |
151 | foreach ( $langs as $lang ) { |
152 | $textFormatters[] = $factory->getTextFormatter( $lang ); |
153 | } |
154 | |
155 | return $textFormatters; |
156 | } |
157 | |
158 | /** |
159 | * @param Config $conf |
160 | * |
161 | * @return string[] |
162 | */ |
163 | private static function getRouteFiles( $conf ) { |
164 | global $IP; |
165 | $extensionsDir = $conf->get( MainConfigNames::ExtensionDirectory ); |
166 | // Always include the "official" routes. Include additional routes if specified. |
167 | $routeFiles = array_merge( |
168 | [ 'includes/Rest/coreRoutes.json' ], |
169 | $conf->get( MainConfigNames::RestAPIAdditionalRouteFiles ) |
170 | ); |
171 | foreach ( $routeFiles as &$file ) { |
172 | if ( |
173 | str_starts_with( $file, '/' ) |
174 | ) { |
175 | // Allow absolute paths on non-Windows |
176 | } elseif ( |
177 | str_starts_with( $file, 'extensions/' ) |
178 | ) { |
179 | // Support hacks like Wikibase.ci.php |
180 | $file = substr_replace( $file, $extensionsDir, |
181 | 0, strlen( 'extensions' ) ); |
182 | } else { |
183 | $file = "$IP/$file"; |
184 | } |
185 | } |
186 | |
187 | return $routeFiles; |
188 | } |
189 | |
190 | public function __construct( |
191 | RequestInterface $request, |
192 | RequestContext $context, |
193 | EntryPointEnvironment $environment, |
194 | MediaWikiServices $mediaWikiServices |
195 | ) { |
196 | parent::__construct( $context, $environment, $mediaWikiServices ); |
197 | |
198 | $this->request = $request; |
199 | } |
200 | |
201 | /** |
202 | * Sets the router to use. |
203 | * Intended for testing. |
204 | * |
205 | * @param Router $router |
206 | */ |
207 | public function setRouter( Router $router ): void { |
208 | $this->router = $router; |
209 | } |
210 | |
211 | public function execute() { |
212 | $this->startOutputBuffer(); |
213 | |
214 | $response = $this->cors->modifyResponse( |
215 | $this->request, |
216 | $this->router->execute( $this->request ) |
217 | ); |
218 | |
219 | $webResponse = $this->getResponse(); |
220 | |
221 | $webResponse->header( |
222 | 'HTTP/' . $response->getProtocolVersion() . ' ' . $response->getStatusCode() . ' ' . |
223 | $response->getReasonPhrase() |
224 | ); |
225 | |
226 | foreach ( $response->getRawHeaderLines() as $line ) { |
227 | $webResponse->header( $line ); |
228 | } |
229 | |
230 | foreach ( $response->getCookies() as $cookie ) { |
231 | $webResponse->setCookie( |
232 | $cookie['name'], |
233 | $cookie['value'], |
234 | $cookie['expiry'], |
235 | $cookie['options'] |
236 | ); |
237 | } |
238 | |
239 | // Clear all errors that might have been displayed if display_errors=On |
240 | $this->discardOutputBuffer(); |
241 | |
242 | $stream = $response->getBody(); |
243 | $stream->rewind(); |
244 | |
245 | $this->prepareForOutput(); |
246 | |
247 | if ( $stream instanceof CopyableStreamInterface ) { |
248 | $stream->copyToStream( fopen( 'php://output', 'w' ) ); |
249 | } else { |
250 | while ( true ) { |
251 | $buffer = $stream->read( 65536 ); |
252 | if ( $buffer === '' ) { |
253 | break; |
254 | } |
255 | $this->print( $buffer ); |
256 | } |
257 | } |
258 | } |
259 | |
260 | } |