@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.)
hq.recaptime.dev/wiki/Phorge
phorge
phabricator
1<?php
2
3/**
4 * TODO: Should be final but isn't because of AphrontReloadResponse.
5 */
6class AphrontRedirectResponse extends AphrontResponse {
7
8 private $uri;
9 private $stackWhenCreated;
10 private $isExternal;
11 private $closeDialogBeforeRedirect;
12
13 public function setIsExternal($external) {
14 $this->isExternal = $external;
15 return $this;
16 }
17
18 public function __construct() {
19 if ($this->shouldStopForDebugging()) {
20 // If we're going to stop, capture the stack so we can print it out.
21 $this->stackWhenCreated = id(new Exception())->getTrace();
22 }
23 }
24
25 public function setURI($uri) {
26 $this->uri = $uri;
27 return $this;
28 }
29
30 public function getURI() {
31 // NOTE: When we convert a RedirectResponse into an AjaxResponse, we pull
32 // the URI through this method. Make sure it passes checks before we
33 // hand it over to callers.
34 return self::getURIForRedirect($this->uri, $this->isExternal);
35 }
36
37 public function shouldStopForDebugging() {
38 return PhabricatorEnv::getEnvConfig('debug.stop-on-redirect');
39 }
40
41 public function setCloseDialogBeforeRedirect($close) {
42 $this->closeDialogBeforeRedirect = $close;
43 return $this;
44 }
45
46 public function getCloseDialogBeforeRedirect() {
47 return $this->closeDialogBeforeRedirect;
48 }
49
50 public function getHeaders() {
51 $headers = array();
52 if (!$this->shouldStopForDebugging()) {
53 $uri = self::getURIForRedirect($this->uri, $this->isExternal);
54 $headers[] = array('Location', $uri);
55 }
56 $headers = array_merge(parent::getHeaders(), $headers);
57 return $headers;
58 }
59
60 public function buildResponseString() {
61 if ($this->shouldStopForDebugging()) {
62 $request = $this->getRequest();
63 $viewer = $request->getUser();
64
65 $view = new PhabricatorStandardPageView();
66 $view->setRequest($this->getRequest());
67 $view->setApplicationName(pht('Debug'));
68 $view->setTitle(pht('Stopped on Redirect'));
69
70 $dialog = new AphrontDialogView();
71 $dialog->setUser($viewer);
72 $dialog->setTitle(pht('Stopped on Redirect'));
73
74 $dialog->appendParagraph(
75 pht(
76 'You were stopped here because %s is set in your configuration.',
77 phutil_tag('tt', array(), 'debug.stop-on-redirect')));
78
79 $dialog->appendParagraph(
80 pht(
81 'You are being redirected to: %s',
82 phutil_tag('tt', array(), $this->getURI())));
83
84 $dialog->addCancelButton($this->getURI(), pht('Continue'));
85
86 $dialog->appendChild(phutil_tag('br'));
87
88 $dialog->appendChild(
89 id(new AphrontStackTraceView())
90 ->setUser($viewer)
91 ->setTrace($this->stackWhenCreated));
92
93 $dialog->setIsStandalone(true);
94 $dialog->setWidth(AphrontDialogView::WIDTH_FULL);
95
96 $box = id(new PHUIBoxView())
97 ->addMargin(PHUI::MARGIN_LARGE)
98 ->appendChild($dialog);
99
100 $view->appendChild($box);
101
102 return $view->render();
103 }
104
105 return '';
106 }
107
108
109 /**
110 * Format a URI for use in a "Location:" header.
111 *
112 * Verifies that a URI redirects to the expected type of resource (local or
113 * remote) and formats it for use in a "Location:" header.
114 *
115 * The HTTP spec says "Location:" headers must use absolute URIs. Although
116 * browsers work with relative URIs, we return absolute URIs to avoid
117 * ambiguity. For example, Chrome interprets "Location: /\evil.com" to mean
118 * "perform a protocol-relative redirect to evil.com".
119 *
120 * @param string $uri URI to redirect to.
121 * @param bool $is_external True if this URI identifies a remote
122 * resource.
123 * @return string URI for use in a "Location:" header.
124 */
125 public static function getURIForRedirect($uri, $is_external) {
126 $uri_object = new PhutilURI($uri);
127 if ($is_external) {
128 // If this is a remote resource it must have a domain set. This
129 // would also be caught below, but testing for it explicitly first allows
130 // us to raise a better error message.
131 if (!strlen($uri_object->getDomain())) {
132 throw new Exception(
133 pht(
134 'Refusing to redirect to external URI "%s". This URI '.
135 'is not fully qualified, and is missing a domain name. To '.
136 'redirect to a local resource, remove the external flag.',
137 (string)$uri));
138 }
139
140 // Check that it's a valid remote resource.
141 if (!PhabricatorEnv::isValidURIForLink($uri)) {
142 throw new Exception(
143 pht(
144 'Refusing to redirect to external URI "%s". This URI '.
145 'is not a valid remote web resource.',
146 (string)$uri));
147 }
148 } else {
149 // If this is a local resource, it must not have a domain set. This allows
150 // us to raise a better error message than the check below can.
151 if (strlen($uri_object->getDomain())) {
152 throw new Exception(
153 pht(
154 'Refusing to redirect to local resource "%s". The URI has a '.
155 'domain, but the redirect is not marked external. Mark '.
156 'redirects as external to allow redirection off the local '.
157 'domain.',
158 (string)$uri));
159 }
160
161 // If this is a local resource, it must be a valid local resource.
162 if (!PhabricatorEnv::isValidLocalURIForLink($uri)) {
163 throw new Exception(
164 pht(
165 'Refusing to redirect to local resource "%s". This URI is not '.
166 'formatted in a recognizable way.',
167 (string)$uri));
168 }
169
170 // Fully qualify the result URI.
171 $uri = PhabricatorEnv::getURI((string)$uri);
172 }
173
174 return (string)$uri;
175 }
176
177}