transport = $transport; } /** * @return array */ public function core_checksums( string $version, string $locale ): array { if ( 1 !== preg_match( '/^[0-9]{1,3}\.[0-9]{1,3}(?:\.[0-9]{1,3})?(?:-[A-Za-z0-9.-]+)?$/D', $version ) || 1 !== preg_match( '/^[A-Za-z_]{2,12}$/D', $locale ) ) { throw new \InvalidArgumentException( 'wordpress_org_core_identity_invalid' ); } $url = self::CORE_ENDPOINT . '?' . http_build_query( array( 'version' => $version, 'locale' => $locale, ), '', '&', PHP_QUERY_RFC3986 ); $response = $this->transport->get( $url, array(), 2097152, 15 ); if ( 200 !== $response['status'] ) { throw new ExternalRequestException( 'wordpress_org_core_unavailable' ); } $value = $this->decode( $response['body'] ); if ( ! isset( $value['checksums'] ) || ! is_array( $value['checksums'] ) || count( $value['checksums'] ) > 5000 ) { throw new ExternalRequestException( 'wordpress_org_core_schema_invalid' ); } $checksums = array(); foreach ( $value['checksums'] as $path => $checksum ) { if ( ! is_string( $path ) || ! is_string( $checksum ) || 1 !== preg_match( '/^[a-f0-9]{32}$/Di', $checksum ) ) { throw new ExternalRequestException( 'wordpress_org_core_schema_invalid' ); } $checksums[ $path ] = strtolower( $checksum ); } return $checksums; } /** * A missing checksum set is an honest unsupported result, not a transport error. * * @return array>>|null */ public function plugin_checksums( string $slug, string $version ): ?array { if ( 1 !== preg_match( '/^[a-z0-9][a-z0-9-]{0,99}$/D', $slug ) || 1 !== preg_match( '/^[0-9A-Za-z][0-9A-Za-z._+-]{0,49}$/D', $version ) ) { throw new \InvalidArgumentException( 'wordpress_org_plugin_identity_invalid' ); } $url = self::PLUGIN_ENDPOINT . rawurlencode( $slug ) . '/' . rawurlencode( $version ) . '.json'; $response = $this->transport->get( $url, array(), 4194304, 20 ); if ( 404 === $response['status'] ) { return null; } if ( 200 !== $response['status'] ) { throw new ExternalRequestException( 'wordpress_org_plugin_unavailable' ); } $value = $this->decode( $response['body'] ); if ( ! isset( $value['files'] ) || ! is_array( $value['files'] ) || count( $value['files'] ) > 20000 ) { throw new ExternalRequestException( 'wordpress_org_plugin_schema_invalid' ); } $files = array(); foreach ( $value['files'] as $path => $algorithms ) { if ( ! is_string( $path ) || ! is_array( $algorithms ) ) { throw new ExternalRequestException( 'wordpress_org_plugin_schema_invalid' ); } $normalized = array(); foreach ( array( 'sha256' => 64, 'md5' => 32, ) as $algorithm => $length ) { if ( ! isset( $algorithms[ $algorithm ] ) ) { continue; } $values = is_array( $algorithms[ $algorithm ] ) ? $algorithms[ $algorithm ] : array( $algorithms[ $algorithm ] ); foreach ( $values as $checksum ) { if ( ! is_string( $checksum ) || 1 !== preg_match( '/^[a-f0-9]{' . $length . '}$/Di', $checksum ) ) { throw new ExternalRequestException( 'wordpress_org_plugin_schema_invalid' ); } $normalized[ $algorithm ][] = strtolower( $checksum ); } } if ( array() === $normalized ) { throw new ExternalRequestException( 'wordpress_org_plugin_schema_invalid' ); } $files[ $path ] = $normalized; } return $files; } /** * @return array */ private function decode( string $json ): array { try { $value = json_decode( $json, true, 32, JSON_THROW_ON_ERROR ); } catch ( JsonException $exception ) { throw new ExternalRequestException( 'external_response_json_invalid' ); } if ( ! is_array( $value ) || array_values( $value ) === $value ) { throw new ExternalRequestException( 'external_response_json_invalid' ); } return $value; } }