PluginProbe
ActivityPub / 8.0.2
ActivityPub v8.0.2
9.3.1 9.3.0 9.2.2 9.2.1 9.2.0 9.1.0 9.0.2 9.0.1 9.0.0 8.3.0 8.2.1 8.2.0 8.1.1 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.2.0 1.3.0 2.0.0 2.0.1 2.1.0 2.1.1 All 160 releases
← All changes | includes/functions-request.php +16 -194 9.0.28.0.2 View file →
@@ -53,8 +53,24 @@
53 53 return apply_filters( 'activitypub_use_authorized_fetch', $use );
54 54 }
55 55
56 56 /**
57 + * Check for Tombstone Objects.
58 + *
59 + * @deprecated 7.3.0 Use {@see Tombstone::exists_in_error()}.
60 + * @see https://www.w3.org/TR/activitypub/#delete-activity-outbox
61 + *
62 + * @param \WP_Error $wp_error A WP_Error-Response of an HTTP-Request.
63 + *
64 + * @return boolean True if HTTP-Code is 410 or 404.
65 + */
66 +function is_tombstone( $wp_error ) {
67 + \_deprecated_function( __FUNCTION__, '7.3.0', 'Activitypub\Tombstone::exists_in_error' );
68 +
69 + return Tombstone::exists_in_error( $wp_error );
70 +}
71 +
72 +/**
57 73 * Check if a request is for an ActivityPub request.
58 74 *
59 75 * @return bool False by default.
60 76 */
@@ -101,199 +117,5 @@
101 117 return $remote_actor;
102 118 }
103 119
104 120 return json_decode( $remote_actor->post_content, true );
105 -}
106 -
107 -/**
108 - * Resolve a hostname or IP literal to a public IP address.
109 - *
110 - * Used as an SSRF guard before opening connections to user-supplied URLs.
111 - * `wp_safe_remote_get()` ultimately calls `wp_http_validate_url()`, which has
112 - * a same-host carve-out that lets local/private addresses through when the
113 - * WordPress site itself is hosted on one. This helper performs an explicit
114 - * resolve-and-validate without that carve-out, and returns the resolved IP so
115 - * callers can pin the connection to it (defends against DNS rebinding).
116 - *
117 - * Both IPv4 and IPv6 literals are accepted (bracketed IPv6 like `[::1]` is
118 - * normalised first). For hostnames, A records are looked up via
119 - * `gethostbynamel()` and AAAA records via `dns_get_record()` when available.
120 - * Every returned address is validated against private/reserved ranges; a
121 - * single bad address fails the whole resolution, defending against
122 - * split-horizon DNS that returns a public answer to one resolver and a
123 - * private one to another. IPv4 addresses are preferred over IPv6 when both
124 - * exist, mirroring `wp_safe_remote_get()`'s default.
125 - *
126 - * @param string $host The hostname or IP literal to resolve.
127 - *
128 - * @return string|false A safe public IP, or false when no safe address is available.
129 - */
130 -function resolve_public_host( $host ) {
131 - if ( ! is_string( $host ) || '' === $host ) {
132 - return false;
133 - }
134 -
135 - // Normalise bracketed IPv6 literals (parse_url returns "[::1]").
136 - $host = \trim( $host, '[]' );
137 -
138 - // Already an IP literal — validate directly. Accepts IPv4 and IPv6.
139 - if ( \filter_var( $host, FILTER_VALIDATE_IP ) ) {
140 - if ( is_unsafe_ipv6_literal( $host ) ) {
141 - return false;
142 - }
143 -
144 - return \filter_var( $host, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE )
145 - ? $host
146 - : false;
147 - }
148 -
149 - /**
150 - * Filters the resolved addresses for a hostname before validation.
151 - *
152 - * Returning a non-null array of `array{ipv4: string[], ipv6: string[]}` skips
153 - * the DNS lookup. Tests use this to exercise the validation/preference logic
154 - * without making real DNS queries; production code should leave it null.
155 - *
156 - * @param array{ipv4: string[], ipv6: string[]}|null $pre Pre-resolved addresses, or null to perform DNS lookup.
157 - * @param string $host The hostname being resolved.
158 - */
159 - $pre = \apply_filters( 'activitypub_pre_resolve_public_host', null, $host );
160 -
161 - if ( \is_array( $pre ) ) {
162 - $ipv4 = isset( $pre['ipv4'] ) && \is_array( $pre['ipv4'] ) ? $pre['ipv4'] : array();
163 - $ipv6 = isset( $pre['ipv6'] ) && \is_array( $pre['ipv6'] ) ? $pre['ipv6'] : array();
164 - } else {
165 - $ipv4 = \gethostbynamel( $host ) ?: array();
166 - $ipv6 = array();
167 -
168 - if ( \function_exists( 'dns_get_record' ) ) {
169 - // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- dns_get_record() emits a warning on lookup failure; we already handle the empty case.
170 - $aaaa = @\dns_get_record( $host, DNS_AAAA );
171 - if ( \is_array( $aaaa ) ) {
172 - foreach ( $aaaa as $record ) {
173 - if ( ! empty( $record['ipv6'] ) ) {
174 - $ipv6[] = $record['ipv6'];
175 - }
176 - }
177 - }
178 - }
179 - }
180 -
181 - if ( ! $ipv4 && ! $ipv6 ) {
182 - return false;
183 - }
184 -
185 - foreach ( $ipv4 as $ip ) {
186 - if ( ! \filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) ) {
187 - return false;
188 - }
189 - }
190 -
191 - foreach ( $ipv6 as $ip ) {
192 - if ( is_unsafe_ipv6_literal( $ip ) ) {
193 - return false;
194 - }
195 - if ( ! \filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) ) {
196 - return false;
197 - }
198 - }
199 -
200 - return $ipv4[0] ?? $ipv6[0];
201 -}
202 -
203 -/**
204 - * Detect IPv4-mapped IPv6 literals (`::ffff:0:0/96`).
205 - *
206 - * PHP's FILTER_FLAG_NO_RES_RANGE catches this range on some builds but not
207 - * others. These forms serve no legitimate purpose for the SSRF-guard callers,
208 - * so reject the entire range explicitly via packed-byte comparison.
209 - *
210 - * @param string $ip An IP literal.
211 - *
212 - * @return bool True if the value is an IPv4-mapped IPv6 address.
213 - */
214 -function is_ipv4_mapped_ipv6( $ip ) {
215 - // Short-circuit before inet_pton() so it doesn't emit a warning for non-IP input.
216 - if ( ! is_string( $ip ) || ! \filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 ) ) {
217 - return false;
218 - }
219 -
220 - $packed = \inet_pton( $ip );
221 -
222 - return false !== $packed
223 - && 16 === \strlen( $packed )
224 - && "\0\0\0\0\0\0\0\0\0\0\xff\xff" === \substr( $packed, 0, 12 );
225 -}
226 -
227 -/**
228 - * Detect IPv6 literals in transitional / special-use ranges that PHP's
229 - * FILTER_FLAG_NO_RES_RANGE doesn't reliably block.
230 - *
231 - * Covers, in addition to the IPv4-mapped range handled by
232 - * {@see is_ipv4_mapped_ipv6()}:
233 - *
234 - * - `2002::/16` — 6to4 (RFC 3056). Embeds an IPv4 address in the next 32 bits,
235 - * so e.g. `2002:7f00:0001::1` routes back to `127.0.0.1` on a host with 6to4.
236 - * - `2001:0000::/32` — Teredo tunneling (RFC 4380). The check matches the
237 - * exact 32-bit prefix `2001:0000`, so legitimate `2001::/16` global unicast
238 - * allocations (e.g. Google DNS `2001:4860::/32`) are unaffected. The
239 - * `2001:db8::/32` documentation range is also blocked, by its own entry
240 - * below — they're separate `2001::/16` sub-allocations.
241 - * - `2001:db8::/32` — Documentation prefix (RFC 3849); should never be routed.
242 - * - `64:ff9b::/96` — NAT64 well-known prefix (RFC 6052).
243 - * - `64:ff9b:1::/48` — NAT64 local-use prefix (RFC 8215).
244 - * - `100::/64` — Discard prefix (RFC 6666).
245 - *
246 - * Returns false for IPv4 literals, hostnames, and IPv6 literals outside the
247 - * listed ranges.
248 - *
249 - * @param string $ip An IP literal.
250 - *
251 - * @return bool True if the value is an unsafe IPv6 literal.
252 - */
253 -function is_unsafe_ipv6_literal( $ip ) {
254 - if ( ! is_string( $ip ) || ! \filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 ) ) {
255 - return false;
256 - }
257 -
258 - $packed = \inet_pton( $ip );
259 - if ( false === $packed || 16 !== \strlen( $packed ) ) {
260 - return false;
261 - }
262 -
263 - // IPv4-mapped IPv6 prefix.
264 - if ( "\0\0\0\0\0\0\0\0\0\0\xff\xff" === \substr( $packed, 0, 12 ) ) {
265 - return true;
266 - }
267 -
268 - // 6to4 prefix.
269 - if ( "\x20\x02" === \substr( $packed, 0, 2 ) ) {
270 - return true;
271 - }
272 -
273 - // Teredo prefix.
274 - if ( "\x20\x01\x00\x00" === \substr( $packed, 0, 4 ) ) {
275 - return true;
276 - }
277 -
278 - // Documentation prefix.
279 - if ( "\x20\x01\x0d\xb8" === \substr( $packed, 0, 4 ) ) {
280 - return true;
281 - }
282 -
283 - // NAT64 well-known prefix.
284 - if ( "\x00\x64\xff\x9b\x00\x00\x00\x00\x00\x00\x00\x00" === \substr( $packed, 0, 12 ) ) {
285 - return true;
286 - }
287 -
288 - // NAT64 local-use prefix.
289 - if ( "\x00\x64\xff\x9b\x00\x01" === \substr( $packed, 0, 6 ) ) {
290 - return true;
291 - }
292 -
293 - // Discard prefix.
294 - if ( "\x01\x00\x00\x00\x00\x00\x00\x00" === \substr( $packed, 0, 8 ) ) {
295 - return true;
296 - }
297 -
298 - return false;
299 121 }