Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/aphront/configuration/AphrontApplicationConfiguration.php
12241 views
1
<?php
2
3
/**
4
* @task routing URI Routing
5
* @task response Response Handling
6
* @task exception Exception Handling
7
*/
8
final class AphrontApplicationConfiguration
9
extends Phobject {
10
11
private $request;
12
private $host;
13
private $path;
14
private $console;
15
16
public function buildRequest() {
17
$parser = new PhutilQueryStringParser();
18
19
$data = array();
20
$data += $_POST;
21
$data += $parser->parseQueryString(idx($_SERVER, 'QUERY_STRING', ''));
22
23
$cookie_prefix = PhabricatorEnv::getEnvConfig('phabricator.cookie-prefix');
24
25
$request = new AphrontRequest($this->getHost(), $this->getPath());
26
$request->setRequestData($data);
27
$request->setApplicationConfiguration($this);
28
$request->setCookiePrefix($cookie_prefix);
29
30
$request->updateEphemeralCookies();
31
32
return $request;
33
}
34
35
public function buildRedirectController($uri, $external) {
36
return array(
37
new PhabricatorRedirectController(),
38
array(
39
'uri' => $uri,
40
'external' => $external,
41
),
42
);
43
}
44
45
public function setRequest(AphrontRequest $request) {
46
$this->request = $request;
47
return $this;
48
}
49
50
public function getRequest() {
51
return $this->request;
52
}
53
54
public function getConsole() {
55
return $this->console;
56
}
57
58
public function setConsole($console) {
59
$this->console = $console;
60
return $this;
61
}
62
63
public function setHost($host) {
64
$this->host = $host;
65
return $this;
66
}
67
68
public function getHost() {
69
return $this->host;
70
}
71
72
public function setPath($path) {
73
$this->path = $path;
74
return $this;
75
}
76
77
public function getPath() {
78
return $this->path;
79
}
80
81
82
/**
83
* @phutil-external-symbol class PhabricatorStartup
84
*/
85
public static function runHTTPRequest(AphrontHTTPSink $sink) {
86
if (isset($_SERVER['HTTP_X_SETUP_SELFCHECK'])) {
87
$response = self::newSelfCheckResponse();
88
return self::writeResponse($sink, $response);
89
}
90
91
PhabricatorStartup::beginStartupPhase('multimeter');
92
$multimeter = MultimeterControl::newInstance();
93
$multimeter->setEventContext('<http-init>');
94
$multimeter->setEventViewer('<none>');
95
96
// Build a no-op write guard for the setup phase. We'll replace this with a
97
// real write guard later on, but we need to survive setup and build a
98
// request object first.
99
$write_guard = new AphrontWriteGuard('id');
100
101
PhabricatorStartup::beginStartupPhase('preflight');
102
103
$response = PhabricatorSetupCheck::willPreflightRequest();
104
if ($response) {
105
return self::writeResponse($sink, $response);
106
}
107
108
PhabricatorStartup::beginStartupPhase('env.init');
109
110
self::readHTTPPOSTData();
111
112
try {
113
PhabricatorEnv::initializeWebEnvironment();
114
$database_exception = null;
115
} catch (PhabricatorClusterStrandedException $ex) {
116
$database_exception = $ex;
117
}
118
119
// If we're in developer mode, set a flag so that top-level exception
120
// handlers can add more information.
121
if (PhabricatorEnv::getEnvConfig('phabricator.developer-mode')) {
122
$sink->setShowStackTraces(true);
123
}
124
125
if ($database_exception) {
126
$issue = PhabricatorSetupIssue::newDatabaseConnectionIssue(
127
$database_exception,
128
true);
129
$response = PhabricatorSetupCheck::newIssueResponse($issue);
130
return self::writeResponse($sink, $response);
131
}
132
133
$multimeter->setSampleRate(
134
PhabricatorEnv::getEnvConfig('debug.sample-rate'));
135
136
$debug_time_limit = PhabricatorEnv::getEnvConfig('debug.time-limit');
137
if ($debug_time_limit) {
138
PhabricatorStartup::setDebugTimeLimit($debug_time_limit);
139
}
140
141
// This is the earliest we can get away with this, we need env config first.
142
PhabricatorStartup::beginStartupPhase('log.access');
143
PhabricatorAccessLog::init();
144
$access_log = PhabricatorAccessLog::getLog();
145
PhabricatorStartup::setAccessLog($access_log);
146
147
$address = PhabricatorEnv::getRemoteAddress();
148
if ($address) {
149
$address_string = $address->getAddress();
150
} else {
151
$address_string = '-';
152
}
153
154
$access_log->setData(
155
array(
156
'R' => AphrontRequest::getHTTPHeader('Referer', '-'),
157
'r' => $address_string,
158
'M' => idx($_SERVER, 'REQUEST_METHOD', '-'),
159
));
160
161
DarkConsoleXHProfPluginAPI::hookProfiler();
162
163
// We just activated the profiler, so we don't need to keep track of
164
// startup phases anymore: it can take over from here.
165
PhabricatorStartup::beginStartupPhase('startup.done');
166
167
DarkConsoleErrorLogPluginAPI::registerErrorHandler();
168
169
$response = PhabricatorSetupCheck::willProcessRequest();
170
if ($response) {
171
return self::writeResponse($sink, $response);
172
}
173
174
$host = AphrontRequest::getHTTPHeader('Host');
175
$path = PhabricatorStartup::getRequestPath();
176
177
$application = new self();
178
179
$application->setHost($host);
180
$application->setPath($path);
181
$request = $application->buildRequest();
182
183
// Now that we have a request, convert the write guard into one which
184
// actually checks CSRF tokens.
185
$write_guard->dispose();
186
$write_guard = new AphrontWriteGuard(array($request, 'validateCSRF'));
187
188
// Build the server URI implied by the request headers. If an administrator
189
// has not configured "phabricator.base-uri" yet, we'll use this to generate
190
// links.
191
192
$request_protocol = ($request->isHTTPS() ? 'https' : 'http');
193
$request_base_uri = "{$request_protocol}://{$host}/";
194
PhabricatorEnv::setRequestBaseURI($request_base_uri);
195
196
$access_log->setData(
197
array(
198
'U' => (string)$request->getRequestURI()->getPath(),
199
));
200
201
$processing_exception = null;
202
try {
203
$response = $application->processRequest(
204
$request,
205
$access_log,
206
$sink,
207
$multimeter);
208
$response_code = $response->getHTTPResponseCode();
209
} catch (Exception $ex) {
210
$processing_exception = $ex;
211
$response_code = 500;
212
}
213
214
$write_guard->dispose();
215
216
$access_log->setData(
217
array(
218
'c' => $response_code,
219
'T' => PhabricatorStartup::getMicrosecondsSinceStart(),
220
));
221
222
$multimeter->newEvent(
223
MultimeterEvent::TYPE_REQUEST_TIME,
224
$multimeter->getEventContext(),
225
PhabricatorStartup::getMicrosecondsSinceStart());
226
227
$access_log->write();
228
229
$multimeter->saveEvents();
230
231
DarkConsoleXHProfPluginAPI::saveProfilerSample($access_log);
232
233
PhabricatorStartup::disconnectRateLimits(
234
array(
235
'viewer' => $request->getUser(),
236
));
237
238
if ($processing_exception) {
239
throw $processing_exception;
240
}
241
}
242
243
244
public function processRequest(
245
AphrontRequest $request,
246
PhutilDeferredLog $access_log,
247
AphrontHTTPSink $sink,
248
MultimeterControl $multimeter) {
249
250
$this->setRequest($request);
251
252
list($controller, $uri_data) = $this->buildController();
253
254
$controller_class = get_class($controller);
255
$access_log->setData(
256
array(
257
'C' => $controller_class,
258
));
259
$multimeter->setEventContext('web.'.$controller_class);
260
261
$request->setController($controller);
262
$request->setURIMap($uri_data);
263
264
$controller->setRequest($request);
265
266
// If execution throws an exception and then trying to render that
267
// exception throws another exception, we want to show the original
268
// exception, as it is likely the root cause of the rendering exception.
269
$original_exception = null;
270
try {
271
$response = $controller->willBeginExecution();
272
273
if ($request->getUser() && $request->getUser()->getPHID()) {
274
$access_log->setData(
275
array(
276
'u' => $request->getUser()->getUserName(),
277
'P' => $request->getUser()->getPHID(),
278
));
279
$multimeter->setEventViewer('user.'.$request->getUser()->getPHID());
280
}
281
282
if (!$response) {
283
$controller->willProcessRequest($uri_data);
284
$response = $controller->handleRequest($request);
285
$this->validateControllerResponse($controller, $response);
286
}
287
} catch (Exception $ex) {
288
$original_exception = $ex;
289
} catch (Throwable $ex) {
290
$original_exception = $ex;
291
}
292
293
$response_exception = null;
294
try {
295
if ($original_exception) {
296
$response = $this->handleThrowable($original_exception);
297
}
298
299
$response = $this->produceResponse($request, $response);
300
$response = $controller->willSendResponse($response);
301
$response->setRequest($request);
302
303
self::writeResponse($sink, $response);
304
} catch (Exception $ex) {
305
$response_exception = $ex;
306
} catch (Throwable $ex) {
307
$response_exception = $ex;
308
}
309
310
if ($response_exception) {
311
// If we encountered an exception while building a normal response, then
312
// encountered another exception while building a response for the first
313
// exception, throw an aggregate exception that will be unpacked by the
314
// higher-level handler. This is above our pay grade.
315
if ($original_exception) {
316
throw new PhutilAggregateException(
317
pht(
318
'Encountered a processing exception, then another exception when '.
319
'trying to build a response for the first exception.'),
320
array(
321
$response_exception,
322
$original_exception,
323
));
324
}
325
326
// If we built a response successfully and then ran into an exception
327
// trying to render it, try to handle and present that exception to the
328
// user using the standard handler.
329
330
// The problem here might be in rendering (more common) or in the actual
331
// response mechanism (less common). If it's in rendering, we can likely
332
// still render a nice exception page: the majority of rendering issues
333
// are in main page content, not content shared with the exception page.
334
335
$handling_exception = null;
336
try {
337
$response = $this->handleThrowable($response_exception);
338
339
$response = $this->produceResponse($request, $response);
340
$response = $controller->willSendResponse($response);
341
$response->setRequest($request);
342
343
self::writeResponse($sink, $response);
344
} catch (Exception $ex) {
345
$handling_exception = $ex;
346
} catch (Throwable $ex) {
347
$handling_exception = $ex;
348
}
349
350
// If we didn't have any luck with that, raise the original response
351
// exception. As above, this is the root cause exception and more likely
352
// to be useful. This will go to the fallback error handler at top
353
// level.
354
355
if ($handling_exception) {
356
throw $response_exception;
357
}
358
}
359
360
return $response;
361
}
362
363
private static function writeResponse(
364
AphrontHTTPSink $sink,
365
AphrontResponse $response) {
366
367
$unexpected_output = PhabricatorStartup::endOutputCapture();
368
if ($unexpected_output) {
369
$unexpected_output = pht(
370
"Unexpected output:\n\n%s",
371
$unexpected_output);
372
373
phlog($unexpected_output);
374
375
if ($response instanceof AphrontWebpageResponse) {
376
$response->setUnexpectedOutput($unexpected_output);
377
}
378
}
379
380
$sink->writeResponse($response);
381
}
382
383
384
/* -( URI Routing )-------------------------------------------------------- */
385
386
387
/**
388
* Build a controller to respond to the request.
389
*
390
* @return pair<AphrontController,dict> Controller and dictionary of request
391
* parameters.
392
* @task routing
393
*/
394
private function buildController() {
395
$request = $this->getRequest();
396
397
// If we're configured to operate in cluster mode, reject requests which
398
// were not received on a cluster interface.
399
//
400
// For example, a host may have an internal address like "170.0.0.1", and
401
// also have a public address like "51.23.95.16". Assuming the cluster
402
// is configured on a range like "170.0.0.0/16", we want to reject the
403
// requests received on the public interface.
404
//
405
// Ideally, nodes in a cluster should only be listening on internal
406
// interfaces, but they may be configured in such a way that they also
407
// listen on external interfaces, since this is easy to forget about or
408
// get wrong. As a broad security measure, reject requests received on any
409
// interfaces which aren't on the whitelist.
410
411
$cluster_addresses = PhabricatorEnv::getEnvConfig('cluster.addresses');
412
if ($cluster_addresses) {
413
$server_addr = idx($_SERVER, 'SERVER_ADDR');
414
if (!$server_addr) {
415
if (php_sapi_name() == 'cli') {
416
// This is a command line script (probably something like a unit
417
// test) so it's fine that we don't have SERVER_ADDR defined.
418
} else {
419
throw new AphrontMalformedRequestException(
420
pht('No %s', 'SERVER_ADDR'),
421
pht(
422
'This service is configured to operate in cluster mode, but '.
423
'%s is not defined in the request context. Your webserver '.
424
'configuration needs to forward %s to PHP so the software can '.
425
'reject requests received on external interfaces.',
426
'SERVER_ADDR',
427
'SERVER_ADDR'));
428
}
429
} else {
430
if (!PhabricatorEnv::isClusterAddress($server_addr)) {
431
throw new AphrontMalformedRequestException(
432
pht('External Interface'),
433
pht(
434
'This service is configured in cluster mode and the address '.
435
'this request was received on ("%s") is not whitelisted as '.
436
'a cluster address.',
437
$server_addr));
438
}
439
}
440
}
441
442
$site = $this->buildSiteForRequest($request);
443
444
if ($site->shouldRequireHTTPS()) {
445
if (!$request->isHTTPS()) {
446
447
// Don't redirect intracluster requests: doing so drops headers and
448
// parameters, imposes a performance penalty, and indicates a
449
// misconfiguration.
450
if ($request->isProxiedClusterRequest()) {
451
throw new AphrontMalformedRequestException(
452
pht('HTTPS Required'),
453
pht(
454
'This request reached a site which requires HTTPS, but the '.
455
'request is not marked as HTTPS.'));
456
}
457
458
$https_uri = $request->getRequestURI();
459
$https_uri->setDomain($request->getHost());
460
$https_uri->setProtocol('https');
461
462
// In this scenario, we'll be redirecting to HTTPS using an absolute
463
// URI, so we need to permit an external redirect.
464
return $this->buildRedirectController($https_uri, true);
465
}
466
}
467
468
$maps = $site->getRoutingMaps();
469
$path = $request->getPath();
470
471
$result = $this->routePath($maps, $path);
472
if ($result) {
473
return $result;
474
}
475
476
// If we failed to match anything but don't have a trailing slash, try
477
// to add a trailing slash and issue a redirect if that resolves.
478
479
// NOTE: We only do this for GET, since redirects switch to GET and drop
480
// data like POST parameters.
481
if (!preg_match('@/$@', $path) && $request->isHTTPGet()) {
482
$result = $this->routePath($maps, $path.'/');
483
if ($result) {
484
$target_uri = $request->getAbsoluteRequestURI();
485
486
// We need to restore URI encoding because the webserver has
487
// interpreted it. For example, this allows us to redirect a path
488
// like `/tag/aa%20bb` to `/tag/aa%20bb/`, which may eventually be
489
// resolved meaningfully by an application.
490
$target_path = phutil_escape_uri($path.'/');
491
$target_uri->setPath($target_path);
492
$target_uri = (string)$target_uri;
493
494
return $this->buildRedirectController($target_uri, true);
495
}
496
}
497
498
$result = $site->new404Controller($request);
499
if ($result) {
500
return array($result, array());
501
}
502
503
throw new Exception(
504
pht(
505
'Aphront site ("%s") failed to build a 404 controller.',
506
get_class($site)));
507
}
508
509
/**
510
* Map a specific path to the corresponding controller. For a description
511
* of routing, see @{method:buildController}.
512
*
513
* @param list<AphrontRoutingMap> List of routing maps.
514
* @param string Path to route.
515
* @return pair<AphrontController,dict> Controller and dictionary of request
516
* parameters.
517
* @task routing
518
*/
519
private function routePath(array $maps, $path) {
520
foreach ($maps as $map) {
521
$result = $map->routePath($path);
522
if ($result) {
523
return array($result->getController(), $result->getURIData());
524
}
525
}
526
}
527
528
private function buildSiteForRequest(AphrontRequest $request) {
529
$sites = PhabricatorSite::getAllSites();
530
531
$site = null;
532
foreach ($sites as $candidate) {
533
$site = $candidate->newSiteForRequest($request);
534
if ($site) {
535
break;
536
}
537
}
538
539
if (!$site) {
540
$path = $request->getPath();
541
$host = $request->getHost();
542
throw new AphrontMalformedRequestException(
543
pht('Site Not Found'),
544
pht(
545
'This request asked for "%s" on host "%s", but no site is '.
546
'configured which can serve this request.',
547
$path,
548
$host),
549
true);
550
}
551
552
$request->setSite($site);
553
554
return $site;
555
}
556
557
558
/* -( Response Handling )-------------------------------------------------- */
559
560
561
/**
562
* Tests if a response is of a valid type.
563
*
564
* @param wild Supposedly valid response.
565
* @return bool True if the object is of a valid type.
566
* @task response
567
*/
568
private function isValidResponseObject($response) {
569
if ($response instanceof AphrontResponse) {
570
return true;
571
}
572
573
if ($response instanceof AphrontResponseProducerInterface) {
574
return true;
575
}
576
577
return false;
578
}
579
580
581
/**
582
* Verifies that the return value from an @{class:AphrontController} is
583
* of an allowed type.
584
*
585
* @param AphrontController Controller which returned the response.
586
* @param wild Supposedly valid response.
587
* @return void
588
* @task response
589
*/
590
private function validateControllerResponse(
591
AphrontController $controller,
592
$response) {
593
594
if ($this->isValidResponseObject($response)) {
595
return;
596
}
597
598
throw new Exception(
599
pht(
600
'Controller "%s" returned an invalid response from call to "%s". '.
601
'This method must return an object of class "%s", or an object '.
602
'which implements the "%s" interface.',
603
get_class($controller),
604
'handleRequest()',
605
'AphrontResponse',
606
'AphrontResponseProducerInterface'));
607
}
608
609
610
/**
611
* Verifies that the return value from an
612
* @{class:AphrontResponseProducerInterface} is of an allowed type.
613
*
614
* @param AphrontResponseProducerInterface Object which produced
615
* this response.
616
* @param wild Supposedly valid response.
617
* @return void
618
* @task response
619
*/
620
private function validateProducerResponse(
621
AphrontResponseProducerInterface $producer,
622
$response) {
623
624
if ($this->isValidResponseObject($response)) {
625
return;
626
}
627
628
throw new Exception(
629
pht(
630
'Producer "%s" returned an invalid response from call to "%s". '.
631
'This method must return an object of class "%s", or an object '.
632
'which implements the "%s" interface.',
633
get_class($producer),
634
'produceAphrontResponse()',
635
'AphrontResponse',
636
'AphrontResponseProducerInterface'));
637
}
638
639
640
/**
641
* Verifies that the return value from an
642
* @{class:AphrontRequestExceptionHandler} is of an allowed type.
643
*
644
* @param AphrontRequestExceptionHandler Object which produced this
645
* response.
646
* @param wild Supposedly valid response.
647
* @return void
648
* @task response
649
*/
650
private function validateErrorHandlerResponse(
651
AphrontRequestExceptionHandler $handler,
652
$response) {
653
654
if ($this->isValidResponseObject($response)) {
655
return;
656
}
657
658
throw new Exception(
659
pht(
660
'Exception handler "%s" returned an invalid response from call to '.
661
'"%s". This method must return an object of class "%s", or an object '.
662
'which implements the "%s" interface.',
663
get_class($handler),
664
'handleRequestException()',
665
'AphrontResponse',
666
'AphrontResponseProducerInterface'));
667
}
668
669
670
/**
671
* Resolves a response object into an @{class:AphrontResponse}.
672
*
673
* Controllers are permitted to return actual responses of class
674
* @{class:AphrontResponse}, or other objects which implement
675
* @{interface:AphrontResponseProducerInterface} and can produce a response.
676
*
677
* If a controller returns a response producer, invoke it now and produce
678
* the real response.
679
*
680
* @param AphrontRequest Request being handled.
681
* @param AphrontResponse|AphrontResponseProducerInterface Response, or
682
* response producer.
683
* @return AphrontResponse Response after any required production.
684
* @task response
685
*/
686
private function produceResponse(AphrontRequest $request, $response) {
687
$original = $response;
688
689
// Detect cycles on the exact same objects. It's still possible to produce
690
// infinite responses as long as they're all unique, but we can only
691
// reasonably detect cycles, not guarantee that response production halts.
692
693
$seen = array();
694
while (true) {
695
// NOTE: It is permissible for an object to be both a response and a
696
// response producer. If so, being a producer is "stronger". This is
697
// used by AphrontProxyResponse.
698
699
// If this response is a valid response, hand over the request first.
700
if ($response instanceof AphrontResponse) {
701
$response->setRequest($request);
702
}
703
704
// If this isn't a producer, we're all done.
705
if (!($response instanceof AphrontResponseProducerInterface)) {
706
break;
707
}
708
709
$hash = spl_object_hash($response);
710
if (isset($seen[$hash])) {
711
throw new Exception(
712
pht(
713
'Failure while producing response for object of class "%s": '.
714
'encountered production cycle (identical object, of class "%s", '.
715
'was produced twice).',
716
get_class($original),
717
get_class($response)));
718
}
719
720
$seen[$hash] = true;
721
722
$new_response = $response->produceAphrontResponse();
723
$this->validateProducerResponse($response, $new_response);
724
$response = $new_response;
725
}
726
727
return $response;
728
}
729
730
731
/* -( Error Handling )----------------------------------------------------- */
732
733
734
/**
735
* Convert an exception which has escaped the controller into a response.
736
*
737
* This method delegates exception handling to available subclasses of
738
* @{class:AphrontRequestExceptionHandler}.
739
*
740
* @param Throwable Exception which needs to be handled.
741
* @return wild Response or response producer, or null if no available
742
* handler can produce a response.
743
* @task exception
744
*/
745
private function handleThrowable($throwable) {
746
$handlers = AphrontRequestExceptionHandler::getAllHandlers();
747
748
$request = $this->getRequest();
749
foreach ($handlers as $handler) {
750
if ($handler->canHandleRequestThrowable($request, $throwable)) {
751
$response = $handler->handleRequestThrowable($request, $throwable);
752
$this->validateErrorHandlerResponse($handler, $response);
753
return $response;
754
}
755
}
756
757
throw $throwable;
758
}
759
760
private static function newSelfCheckResponse() {
761
$path = PhabricatorStartup::getRequestPath();
762
$query = idx($_SERVER, 'QUERY_STRING', '');
763
764
$pairs = id(new PhutilQueryStringParser())
765
->parseQueryStringToPairList($query);
766
767
$params = array();
768
foreach ($pairs as $v) {
769
$params[] = array(
770
'name' => $v[0],
771
'value' => $v[1],
772
);
773
}
774
775
$raw_input = @file_get_contents('php://input');
776
if ($raw_input !== false) {
777
$base64_input = base64_encode($raw_input);
778
} else {
779
$base64_input = null;
780
}
781
782
$result = array(
783
'path' => $path,
784
'params' => $params,
785
'user' => idx($_SERVER, 'PHP_AUTH_USER'),
786
'pass' => idx($_SERVER, 'PHP_AUTH_PW'),
787
788
'raw.base64' => $base64_input,
789
790
// This just makes sure that the response compresses well, so reasonable
791
// algorithms should want to gzip or deflate it.
792
'filler' => str_repeat('Q', 1024 * 16),
793
);
794
795
return id(new AphrontJSONResponse())
796
->setAddJSONShield(false)
797
->setContent($result);
798
}
799
800
private static function readHTTPPOSTData() {
801
$request_method = idx($_SERVER, 'REQUEST_METHOD');
802
if ($request_method === 'PUT') {
803
// For PUT requests, do nothing: in particular, do NOT read input. This
804
// allows us to stream input later and process very large PUT requests,
805
// like those coming from Git LFS.
806
return;
807
}
808
809
810
// For POST requests, we're going to read the raw input ourselves here
811
// if we can. Among other things, this corrects variable names with
812
// the "." character in them, which PHP normally converts into "_".
813
814
// If "enable_post_data_reading" is on, the documentation suggests we
815
// can not read the body. In practice, we seem to be able to. This may
816
// need to be resolved at some point, likely by instructing installs
817
// to disable this option.
818
819
// If the content type is "multipart/form-data", we need to build both
820
// $_POST and $_FILES, which is involved. The body itself is also more
821
// difficult to parse than other requests.
822
823
$raw_input = PhabricatorStartup::getRawInput();
824
$parser = new PhutilQueryStringParser();
825
826
if (strlen($raw_input)) {
827
$content_type = idx($_SERVER, 'CONTENT_TYPE');
828
$is_multipart = preg_match('@^multipart/form-data@i', $content_type);
829
if ($is_multipart) {
830
$multipart_parser = id(new AphrontMultipartParser())
831
->setContentType($content_type);
832
833
$multipart_parser->beginParse();
834
$multipart_parser->continueParse($raw_input);
835
$parts = $multipart_parser->endParse();
836
837
// We're building and then parsing a query string so that requests
838
// with arrays (like "x[]=apple&x[]=banana") work correctly. This also
839
// means we can't use "phutil_build_http_querystring()", since it
840
// can't build a query string with duplicate names.
841
842
$query_string = array();
843
foreach ($parts as $part) {
844
if (!$part->isVariable()) {
845
continue;
846
}
847
848
$name = $part->getName();
849
$value = $part->getVariableValue();
850
$query_string[] = rawurlencode($name).'='.rawurlencode($value);
851
}
852
$query_string = implode('&', $query_string);
853
$post = $parser->parseQueryString($query_string);
854
855
$files = array();
856
foreach ($parts as $part) {
857
if ($part->isVariable()) {
858
continue;
859
}
860
861
$files[$part->getName()] = $part->getPHPFileDictionary();
862
}
863
$_FILES = $files;
864
} else {
865
$post = $parser->parseQueryString($raw_input);
866
}
867
868
$_POST = $post;
869
PhabricatorStartup::rebuildRequest();
870
} else if ($_POST) {
871
$post = filter_input_array(INPUT_POST, FILTER_UNSAFE_RAW);
872
if (is_array($post)) {
873
$_POST = $post;
874
PhabricatorStartup::rebuildRequest();
875
}
876
}
877
}
878
879
}
880
881