PluginProbe
404 Solution / 4.2.0
404 Solution v4.2.0
4.3.5 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 4.2.0 4.1.19 4.1.18 4.1.17 4.1.16 4.1.15 4.1.13 4.1.12 4.1.11 4.1.10 4.1.9 4.1.8 4.1.7 4.1.6 4.1.5 4.1.4 4.1.3 trunk 2.30.0 All 109 releases
404-solution / includes / php / objs / UserRequest.php

UserRequest.php in 404 Solution 4.2.0, at includes/php/objs/UserRequest.php

231 lines 8.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3
4 if (!defined('ABSPATH')) {
5 exit;
6 }
7
8 /** Stores a message and its importance. */
9 class ABJ_404_Solution_UserRequest {
10
11 /** @var self|null */
12 private static $instance = null;
13
14 /** @var string|null */
15 private $requestURIWithoutCommentsPage = null;
16
17 /** @var string */
18 private $requestURI = null;
19
20 /** @var array<string, int|string>|null */
21 private $urlParts = null;
22
23 /** @var string */
24 private $queryString = null;
25
26 /** @var string */
27 private $commentPagePart = null;
28
29 /** @return self|null */
30 public static function getInstance() {
31 if (self::$instance == null) {
32 if (!self::initialize()) {
33 $abj404logging = abj_service('logging');
34 $abj404logging->errorMessage('Issue initializing ' . __CLASS__,
35 new Exception('Issue initializing ' . __CLASS__));
36 }
37 }
38
39 return self::$instance;
40 }
41
42 /** @return bool */
43 public static function initialize(): bool {
44 global $wp_rewrite;
45
46 $abj404logging = abj_service('logging');
47 $f = abj_service('functions');
48 $abj404logic = abj_service('plugin_logic');
49
50 $urlToParse = $f->normalizeUrlString($_SERVER['REQUEST_URI'] ?? '');
51
52 // if the user somehow requested an invalid URL that's too long then fix it.
53 if ($f->strlen($urlToParse) > ABJ404_MAX_URL_LENGTH) {
54 $matches = null;
55 $f->regexMatch("image (.+);base64,", $urlToParse, $matches);
56 if ($matches != null && $f->strlen($matches[0]) > 0) {
57 $instrPattern = $matches[0];
58 $truncateHere = $f->strpos($urlToParse, $instrPattern);
59 $truncatedRequest = $f->substr($urlToParse, 0, ($truncateHere !== false ? $truncateHere : null));
60 $urlToParse = $truncatedRequest;
61 }
62
63 if ($f->strlen($urlToParse) > ABJ404_MAX_URL_LENGTH) {
64 // just truncate it to something reasonable.
65 $urlToParse = $f->substr($urlToParse, 0, ABJ404_MAX_URL_LENGTH);
66 }
67 }
68
69 // hanlde the case where '///?gf_page=upload' is returned as the request URI.
70 $containsHost = $f->strpos($urlToParse, "://");
71
72 if (($containsHost === false) || ($containsHost >= 7) || (!is_array(parse_url(esc_url($urlToParse))))) {
73 // we have something like //login.php and it needs to be http://host.com/login.php
74 while ($f->strpos($urlToParse, "//") !== false) {
75 $urlToParse = $f->str_replace('//', '/', $urlToParse);
76 }
77 $urlToParse = ltrim($abj404logic->removeHomeDirectory($urlToParse), '/');
78 $urlToParse = get_site_url() . '/' . $urlToParse;
79 }
80
81 $urlParts = parse_url($urlToParse);
82 if (!is_array($urlParts)) {
83 $abj404logging->errorMessage('parse_url returned a non-array value. REQUEST_URI: "' .
84 $f->normalizeUrlString($_SERVER['REQUEST_URI']) . '", parse_url result: "' . json_encode($urlParts) . '", ' .
85 'urlToParse result: ' . $urlToParse);
86 return false;
87 }
88 // make things work with foreign languages while avoiding XSS issues.
89 foreach ($urlParts as $key => $value) {
90 if ($key === 'query') {
91 // For query strings, preserve reserved characters while removing invalid bytes.
92 parse_str($value, $queryArray);
93 $safeQueryArray = $f->sanitizeUrlComponent($queryArray);
94 $urlParts[$key] = http_build_query(is_array($safeQueryArray) ? $safeQueryArray : $queryArray);
95 } else {
96 // Sanitize path/host/etc. without stripping reserved URL characters.
97 $urlParts[$key] = $f->sanitizeUrlComponent($value);
98 }
99 }
100
101 // remove a pointless trailing /amp
102 $urlPath = isset($urlParts['path']) && is_string($urlParts['path']) ? $urlParts['path'] : '';
103 if ($urlPath !== '' &&
104 ($f->endsWithCaseInsensitive($urlPath, '/amp') ||
105 $f->endsWithCaseInsensitive($urlPath, '/amp/')
106 )
107 && $f->strlen($urlPath) >= 6) {
108 $urlParts['path'] = $f->substr($urlPath, 0, $f->strlen($urlPath) - 4);
109 }
110
111 // remove any "/comment-page-???/" if there is one.
112 /* tested with:
113 * http://localhost:8888/404solution-site/2019/02/hello-world2/comment-page-2/#comment-26
114 * http://localhost:8888/404solution-site/2019/02/hello-world2/comment-page-2/
115 * http://localhost:8888/404solution-site/2019/02/hello-world2/comment-page-2
116 * http://localhost:8888/404solution-site/2019/02/hello-world2/comment-page-2/?quer=true
117 */
118 // Fix for PHP 8.2: Handle URLs with no path component (e.g., http://example.com)
119 $urlWithoutCommentPage = (isset($urlParts['path']) && is_string($urlParts['path'])) ? $urlParts['path'] : '/';
120 $commentPagePart = '';
121 $results = array();
122 if (isset($wp_rewrite) && isset($wp_rewrite->comments_pagination_base)) {
123 $safeBase = preg_quote($wp_rewrite->comments_pagination_base);
124 $commentregex = '(.*)\/(' . $safeBase . '-[0-9]{1,})(\/|\z)?(.*)';
125 $f->regexMatch($commentregex, $urlWithoutCommentPage, $results);
126
127 if (!empty($results)) {
128 $urlWithoutCommentPage = $results[1];
129 $commentPagePart = $results[2];
130 $commentPagePart = ($commentPagePart == '') ? '' : $commentPagePart . '/';
131 }
132 }
133
134 $queryString = '';
135 if (!array_key_exists('query', $urlParts) || @$urlParts['query'] == "") {
136 $queryString = '';
137 } else {
138 $queryString = $urlParts['query'];
139 }
140
141 /** @var array<string, int|string> $urlPartsSafe */
142 $urlPartsSafe = $urlParts;
143 self::$instance = new ABJ_404_Solution_UserRequest($urlToParse, $urlPartsSafe, $urlWithoutCommentPage,
144 $commentPagePart, $queryString);
145
146 return true;
147 }
148
149 /**
150 * @param string $requestURI
151 * @param array<string, int|string> $urlParts
152 * @param string $urlWithoutCommentPage
153 * @param string $commentPagePart
154 * @param string $queryString
155 */
156 private function __construct(string $requestURI, array $urlParts, string $urlWithoutCommentPage, string $commentPagePart, string $queryString) {
157 $this->requestURI = $requestURI;
158 $this->urlParts = $urlParts;
159 $this->requestURIWithoutCommentsPage = $urlWithoutCommentPage;
160 $this->commentPagePart = $commentPagePart;
161
162 $this->queryString = $queryString;
163 }
164
165 /** @return string|null */
166 function getRequestURI() {
167 return $this->requestURI;
168 }
169
170 /** @return string|null */
171 function getRequestURIWithoutCommentsPage() {
172 return $this->requestURIWithoutCommentsPage;
173 }
174
175 /** http://s.com/404solution-site/hello-world/comment-page-2/#comment-26?query_info=true becomes
176 * /404solution-site/hello-world/comment-page-2/
177 * @return string
178 */
179 function getPath() {
180 if ($this->urlParts === null || !array_key_exists('path', $this->urlParts)) {
181 // this happens for a request with no path. like http://example.com
182 return '';
183 }
184
185 return (string)($this->urlParts['path']);
186 }
187
188 /** @return string */
189 function getPathWithSortedQueryString(): string {
190 $f = abj_service('functions');
191 $requestedURL = $this->getPath();
192 /** @var array<string, string> $urlPartsForSort */
193 $urlPartsForSort = $this->getUrlParts() ?? array();
194 $urlParts = $f->sortQueryString($urlPartsForSort);
195 if ($urlParts != null && trim($urlParts) != '') {
196 $requestedURL .= '?' . $urlParts;
197 }
198
199 // otherwise various queries break.
200 $requestedURL = $f->urlencodeEmojis($requestedURL);
201
202 return $requestedURL;
203 }
204
205 /** http://s.com/404solution-site/hello-world/comment-page-2/#comment-26?query_info=true becomes
206 * /hello-world/comment-page-2/
207 * @return string
208 */
209 function getOnlyTheSlug() {
210 $abj404logic = abj_service('plugin_logic');
211 $path = $this->getRequestURIWithoutCommentsPage();
212 return $abj404logic->removeHomeDirectory($path);
213 }
214
215 /** @return array<string, int|string>|null */
216 function getUrlParts() {
217 return $this->urlParts;
218 }
219
220 /** @return string|null */
221 function getQueryString() {
222 return $this->queryString;
223 }
224
225 /** @return string|null */
226 function getCommentPagePart() {
227 return $this->commentPagePart;
228 }
229
230 }
231