Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
40.74% covered (danger)
40.74%
33 / 81
66.67% covered (warning)
66.67%
2 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApiEntryPoint
40.74% covered (danger)
40.74%
33 / 81
66.67% covered (warning)
66.67%
2 / 3
48.17
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 getContext
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 execute
35.14% covered (danger)
35.14%
26 / 74
0.00% covered (danger)
0.00%
0 / 1
44.02
1<?php
2/**
3 * Entry point implementation for all %Action API queries, handled by ApiMain
4 * and ApiBase subclasses.
5 *
6 * @see /api.php The corresponding HTTP entry point.
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License along
19 * with this program; if not, write to the Free Software Foundation, Inc.,
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 * http://www.gnu.org/copyleft/gpl.html
22 *
23 * @file
24 * @ingroup entrypoint
25 * @ingroup API
26 */
27
28namespace MediaWiki\Api;
29
30use ApiMain;
31use LogicException;
32use MediaWiki\Context\RequestContext;
33use MediaWiki\EntryPointEnvironment;
34use MediaWiki\HookContainer\HookRunner;
35use MediaWiki\Logger\LegacyLogger;
36use MediaWiki\MainConfigNames;
37use MediaWiki\MediaWikiEntryPoint;
38use MediaWiki\MediaWikiServices;
39use MediaWiki\Title\Title;
40use Throwable;
41
42/**
43 * Implementation of the API entry point, for web browser navigations, usually via an
44 * Action or SpecialPage subclass.
45 *
46 * This is used by bots to fetch content and information about the wiki,
47 * its pages, and its users. See <https://www.mediawiki.org/wiki/API> for more
48 * information.
49 *
50 * @see /api.php The corresponding HTTP entry point.
51 * @internal
52 */
53class ApiEntryPoint extends MediaWikiEntryPoint {
54
55    public function __construct(
56        RequestContext $context,
57        EntryPointEnvironment $environment,
58        MediaWikiServices $services
59    ) {
60        parent::__construct(
61            $context,
62            $environment,
63            $services
64        );
65    }
66
67    /**
68     * Overwritten to narrow the return type to RequestContext
69     * @return RequestContext
70     */
71    protected function getContext(): RequestContext {
72        /** @var RequestContext $context */
73        $context = parent::getContext();
74
75        // @phan-suppress-next-line PhanTypeMismatchReturnSuperType see $context in the constructor
76        return $context;
77    }
78
79    /**
80     * Executes a request to the action API.
81     *
82     * It begins by constructing a new ApiMain using the parameter passed to it
83     * as an argument in the URL ('?action='). It then invokes "execute()" on the
84     * ApiMain object instance, which produces output in the format specified in
85     * the URL.
86     */
87    protected function execute() {
88        global $wgTitle;
89
90        $context = $this->getContext();
91        $request = $this->getRequest();
92        $apiRequestLog = $this->getConfig( MainConfigNames::APIRequestLog );
93
94        $starttime = microtime( true );
95
96        $services = $this->getServiceContainer();
97
98        // PATH_INFO can be used for stupid things. We don't support it for api.php at
99        // all, so error out if it's present. (T128209)
100        $pathInfo = $this->environment->getServerInfo( 'PATH_INFO', '' );
101        if ( $pathInfo != '' ) {
102            $correctUrl = wfAppendQuery(
103                wfScript( 'api' ),
104                $request->getQueryValuesOnly()
105            );
106            $correctUrl = (string)$services->getUrlUtils()->expand(
107                $correctUrl,
108                PROTO_CANONICAL
109            );
110            $this->header(
111                "Location: $correctUrl",
112                true,
113                301
114            );
115            $this->print(
116                'This endpoint does not support "path info", i.e. extra text ' .
117                'between "api.php" and the "?". Remove any such text and try again.'
118            );
119            $this->exit( 1 );
120        }
121
122        // Set a dummy $wgTitle, because $wgTitle == null breaks various things
123        // In a perfect world this wouldn't be necessary
124        $wgTitle = Title::makeTitle(
125            NS_SPECIAL,
126            'Badtitle/dummy title for API calls set in api.php'
127        );
128
129        // RequestContext will read from $wgTitle, but it will also whine about it.
130        // In a perfect world this wouldn't be necessary either.
131        $context->setTitle( $wgTitle );
132
133        try {
134            // Construct an ApiMain with the arguments passed via the URL. What we get back
135            // is some form of an ApiMain, possibly even one that produces an error message,
136            // but we don't care here, as that is handled by the constructor.
137            $processor = new ApiMain(
138                $context,
139                true,
140                false
141            );
142
143            // Last chance hook before executing the API
144            ( new HookRunner( $services->getHookContainer() ) )->onApiBeforeMain( $processor );
145            if ( !$processor instanceof ApiMain ) {
146                throw new LogicException(
147                    'ApiBeforeMain hook set $processor to a non-ApiMain class'
148                );
149            }
150        } catch ( Throwable $e ) {
151            // Crap. Try to report the exception in API format to be friendly to clients.
152            ApiMain::handleApiBeforeMainException( $e );
153            $processor = false;
154        }
155
156        // Process data & print results
157        if ( $processor ) {
158            $processor->execute();
159        }
160
161        // Log what the user did, for book-keeping purposes.
162        $endtime = microtime( true );
163
164        // Log the request
165        if ( $apiRequestLog ) {
166            $items = [
167                wfTimestamp( TS_MW ),
168                $endtime - $starttime,
169                $request->getIP(),
170                $request->getHeader( 'User-agent' )
171            ];
172            $items[] = $request->wasPosted() ? 'POST' : 'GET';
173            if ( $processor ) {
174                try {
175                    $manager = $processor->getModuleManager();
176                    $module = $manager->getModule(
177                        $request->getRawVal( 'action' ),
178                        'action'
179                    );
180                } catch ( Throwable $ex ) {
181                    $module = null;
182                }
183                if ( !$module || $module->mustBePosted() ) {
184                    $items[] = "action=" . $request->getRawVal( 'action' );
185                } else {
186                    $items[] = wfArrayToCgi( $request->getValues() );
187                }
188            } else {
189                $items[] = "failed in ApiBeforeMain";
190            }
191            LegacyLogger::emit(
192                implode(
193                    ',',
194                    $items
195                ) . "\n",
196                $apiRequestLog
197            );
198            wfDebug( "Logged API request to $apiRequestLog" );
199        }
200    }
201}