From f9cf36524e64d653e42c37b5836f2980d49dcfc4 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Wed, 6 Nov 2024 18:42:35 +0900 Subject: [PATCH] UrlRequests auth set allowed in requests call Removed the parseHeaders public call, headers must be set as array Throw errors on invalid headers before sending them: Key/Value check Add headers invalid check in phpunit Auth headers can be set per call and will override global settings if matching --- .../CoreLibsUrlRequestsCurlTest.php | 88 +++++-- www/admin/class_test.url-requests.curl.php | 119 +++++---- www/lib/CoreLibs/UrlRequests/Curl.php | 226 +++++++++++------- www/lib/CoreLibs/UrlRequests/CurlTrait.php | 41 ++-- .../Interface/RequestsInterface.php | 14 +- 5 files changed, 290 insertions(+), 198 deletions(-) diff --git a/4dev/tests/UrlRequests/CoreLibsUrlRequestsCurlTest.php b/4dev/tests/UrlRequests/CoreLibsUrlRequestsCurlTest.php index 5982efa1..9e4432e4 100644 --- a/4dev/tests/UrlRequests/CoreLibsUrlRequestsCurlTest.php +++ b/4dev/tests/UrlRequests/CoreLibsUrlRequestsCurlTest.php @@ -732,7 +732,7 @@ final class CoreLibsUrlRequestsCurlTest extends TestCase ); } - // MARK: test basi call provider + // MARK: test basic call provider /** * Undocumented function @@ -976,11 +976,61 @@ final class CoreLibsUrlRequestsCurlTest extends TestCase ); } + // MARK: auth header set via config + + /** + * Test auth settings and auth override + * + * @testdox UrlRequests\Curl auth test call + * + * @return void + */ + public function testUrlRequestsCurlAuthHeader() + { + $curl = new \CoreLibs\UrlRequests\Curl([ + "auth" => ["user", "pass", "basic"], + "http_errors" => false, + ]); + $curl->request('get', $this->url_basic); + // check that the auth header matches + $this->assertContains( + "Authorization:Basic dXNlcjpwYXNz", + $curl->getHeadersSent() + ); + // if we sent new request with auth header, this one should not be used + $curl->request('get', $this->url_basic, [ + "headers" => ["Authorization" => "Failed"] + ]); + // check that the auth header matches + $this->assertContains( + "Authorization:Basic dXNlcjpwYXNz", + $curl->getHeadersSent() + ); + // override auth: reset + $curl->request('get', $this->url_basic, [ + "auth" => null + ]); + $this->assertNotContains( + "Authorization:Basic dXNlcjpwYXNz", + $curl->getHeadersSent() + ); + // override auth: different auth + $curl->request('get', $this->url_basic, [ + "auth" => ["user2", "pass2", "basic"] + ]); + // check that the auth header matches + $this->assertContains( + "Authorization:Basic dXNlcjI6cGFzczI=", + $curl->getHeadersSent() + ); + } + // MARK: test exceptions /** - * Undocumented function + * Exception:InvalidRequestType * + * @covers ::request * @testdox UrlRequests\Curl Exception:InvalidRequestType * * @return void @@ -1006,9 +1056,10 @@ final class CoreLibsUrlRequestsCurlTest extends TestCase // } /** - * Undocumented function + * Exception:CurlExecError * - * @testdox UrlRequests\Curl Exception:CurlError + * @covers ::request + * @testdox UrlRequests\Curl Exception:CurlExecError * * @return void */ @@ -1024,6 +1075,7 @@ final class CoreLibsUrlRequestsCurlTest extends TestCase /** * Exception:ClientError * + * @covers ::request * @testdox UrlRequests\Curl Exception:ClientError * * @return void @@ -1033,7 +1085,7 @@ final class CoreLibsUrlRequestsCurlTest extends TestCase $curl = new \CoreLibs\UrlRequests\Curl(["http_errors" => true]); $this->expectException(\RuntimeException::class); $this->expectExceptionMessageMatches("/ClientError/"); - $curl->get($this->url_basic, [ + $curl->request('get', $this->url_basic, [ "headers" => [ "Authorization" => "schmalztiegel", "RunAuthTest" => "yes", @@ -1044,6 +1096,7 @@ final class CoreLibsUrlRequestsCurlTest extends TestCase /** * Exception:ClientError * + * @covers ::request * @testdox UrlRequests\Curl Exception:ClientError on call enable * * @return void @@ -1053,7 +1106,7 @@ final class CoreLibsUrlRequestsCurlTest extends TestCase $curl = new \CoreLibs\UrlRequests\Curl(["http_errors" => false]); $this->expectException(\RuntimeException::class); $this->expectExceptionMessageMatches("/ClientError/"); - $curl->get($this->url_basic, [ + $curl->request('get', $this->url_basic, [ "headers" => [ "Authorization" => "schmalztiegel", "RunAuthTest" => "yes", @@ -1065,6 +1118,7 @@ final class CoreLibsUrlRequestsCurlTest extends TestCase /** * Exception:ClientError * + * @covers ::request * @testdox UrlRequests\Curl Exception:ClientError unset on call * * @return void @@ -1073,7 +1127,7 @@ final class CoreLibsUrlRequestsCurlTest extends TestCase { // if true, with false it has to be off $curl = new \CoreLibs\UrlRequests\Curl(["http_errors" => true]); - $response = $curl->get($this->url_basic, [ + $response = $curl->request('get', $this->url_basic, [ "headers" => [ "Authorization" => "schmalztiegel", "RunAuthTest" => "yes", @@ -1087,7 +1141,7 @@ final class CoreLibsUrlRequestsCurlTest extends TestCase ); // if false, null should not change it $curl = new \CoreLibs\UrlRequests\Curl(["http_errors" => false]); - $response = $curl->get($this->url_basic, [ + $response = $curl->request('get', $this->url_basic, [ "headers" => [ "Authorization" => "schmalztiegel", "RunAuthTest" => "yes", @@ -1100,24 +1154,6 @@ final class CoreLibsUrlRequestsCurlTest extends TestCase 'Unset Exception failed with null' ); } - - /** - * Undocumented function - * - * @testdox UrlRequests\Curl Exception:DuplicatedArrayKey - * - * @return void - */ - public function testExceptionDuplicatedArrayKey(): void - { - $curl = new \CoreLibs\UrlRequests\Curl(); - $this->expectException(\UnexpectedValueException::class); - $this->expectExceptionMessageMatches("/DuplicatedArrayKey/"); - $curl->prepareHeaders([ - 'header-double:a', - 'header-double:b', - ]); - } } // __END__ diff --git a/www/admin/class_test.url-requests.curl.php b/www/admin/class_test.url-requests.curl.php index 87fa0053..f4a0848a 100644 --- a/www/admin/class_test.url-requests.curl.php +++ b/www/admin/class_test.url-requests.curl.php @@ -40,11 +40,11 @@ $data = $client->get( 'https://soba.egplusww.jp/developers/clemens/core_data/php_libraries/trunk/www/admin/UrlRequests.target.php' . '?other=get_a', [ - 'headers' => $client->prepareHeaders([ - 'test-header: ABC', - 'info-request-type: _GET', + 'headers' => [ + 'test-header' => 'ABC', + 'info-request-type' => '_GET', 'Funk-pop' => 'Semlly god' - ]), + ], 'query' => ['foo' => 'BAR'] ] ); @@ -78,11 +78,11 @@ $data = $client->request( . 'trunk/www/admin/UrlRequests.target.php' . '?other=get_a', [ - "headers" => $client->prepareHeaders([ - 'test-header: ABC', - 'info-request-type: _GET', + "headers" => [ + 'test-header' => 'ABC', + 'info-request-type' => '_GET', 'Funk-pop' => 'Semlly god' - ]), + ], "query" => ['foo' => 'BAR'], ], ); @@ -94,12 +94,12 @@ $data = $client->post( . '?other=post_a', [ 'body' => ['payload' => 'data post'], - 'headers' => $client->prepareHeaders([ - 'Content-Type: application/json', - 'Accept: application/json', - 'test-header: ABC', - 'info-request-type: _POST' - ]), + 'headers' => [ + 'Content-Type' => 'application/json', + 'Accept' => 'application/json', + 'test-header' => 'ABC', + 'info-request-type' => '_POST', + ], 'query' => ['foo' => 'BAR post'], ] ); @@ -111,12 +111,12 @@ $data = $client->request( . '?other=post_a', [ "body" => ['payload' => 'data post', 'request' => 'I am the request body'], - "headers" => $client->prepareHeaders([ - 'Content-Type: application/json', - 'Accept: application/json', - 'test-header: ABC', - 'info-request-type: _POST' - ]), + "headers" => [ + 'Content-Type' => 'application/json', + 'Accept' => 'application/json', + 'test-header' => 'ABC', + 'info-request-type' => '_POST', + ], "query" => ['foo' => 'BAR post'], ] ); @@ -128,12 +128,12 @@ $data = $client->put( . '?other=put_a', [ "body" => ['payload' => 'data put'], - "headers" => $client->prepareHeaders([ - 'Content-Type: application/json', - 'Accept: application/json', - 'test-header: ABC', - 'info-request-type: _PUT' - ]), + "headers" => [ + 'Content-Type' => 'application/json', + 'Accept' => 'application/json', + 'test-header' => 'ABC', + 'info-request-type' => '_PUT', + ], 'query' => ['foo' => 'BAR put'], ] ); @@ -145,12 +145,12 @@ $data = $client->patch( . '?other=patch_a', [ "body" => ['payload' => 'data patch'], - "headers" => $client->prepareHeaders([ - 'Content-Type: application/json', - 'Accept: application/json', - 'test-header: ABC', - 'info-request-type: _PATCH' - ]), + "headers" => [ + 'Content-Type' => 'application/json', + 'Accept' => 'application/json', + 'test-header' => 'ABC', + 'info-request-type' => '_PATCH', + ], 'query' => ['foo' => 'BAR patch'], ] ); @@ -162,12 +162,12 @@ $data = $client->delete( . '?other=delete_no_body_a', [ "body" => null, - "headers" => $client->prepareHeaders([ - 'Content-Type: application/json', - 'Accept: application/json', - 'test-header: ABC', - 'info-request-type: _DELETE' - ]), + "headers" => [ + 'Content-Type' => 'application/json', + 'Accept' => 'application/json', + 'test-header' => 'ABC', + 'info-request-type' => '_DELETE', + ], "query" => ['foo' => 'BAR delete'], ] ); @@ -179,12 +179,12 @@ $data = $client->delete( . '?other=delete_body_a', [ "body" => ['payload' => 'data delete'], - "headers" => $client->prepareHeaders([ - 'Content-Type: application/json', - 'Accept: application/json', - 'test-header: ABC', - 'info-request-type: _DELETE' - ]), + "headers" => [ + 'Content-Type' => 'application/json', + 'Accept' => 'application/json', + 'test-header' => 'ABC', + 'info-request-type' => '_DELETE', + ], "query" => ['foo' => 'BAR delete'], ] ); @@ -294,6 +294,24 @@ try { } catch (Exception $e) { print "Exception:
" . print_r(json_decode($e->getMessage(), true), true) . "

"; } +print "AUTH REQUEST HEADER SET:
"; +try { + $uc = new Curl([ + "base_uri" => 'https://soba.egplusww.jp/developers/clemens/core_data/php_libraries/trunk/www/admin/', + "auth" => ["user", "pass", "basic"], + "headers" => [ + "Authorization" => "schmalztiegel", + "RunAuthTest" => "yes", + ] + ]); + $response = $uc->get('UrlRequests.target.php'); + print "AUTH REQUEST (HEADER):
" . print_r($response, true) . "
"; + print "[uc] SENT URL: " . $uc->getUrlSent() . "
"; + print "[uc] SENT URL PARSED:
" . print_r($uc->getUrlParsedSent(), true) . "
"; + print "[uc] SENT HEADERS:
" . print_r($uc->getHeadersSent(), true) . "
"; +} catch (Exception $e) { + print "Exception:
" . print_r(json_decode($e->getMessage(), true), true) . "

"; +} print "
"; $uc = new Curl([ @@ -308,6 +326,19 @@ print "[uc] SENT URL: " . $uc->getUrlSent() . "
"; print "[uc] SENT URL PARSED:
" . print_r($uc->getUrlParsedSent(), true) . "
"; print "[uc] SENT HEADERS:
" . print_r($uc->getHeadersSent(), true) . "
"; +print "
"; +$uc = new Curl([ + "base_uri" => 'https://soba.egplusww.jp/developers/clemens/core_data/php_libraries/trunk/www/admin/', + "headers" => [ + 'bar' => 'foo:bar' + ] +]); +$response = $uc->get('UrlRequests.target.php'); +print "HEADER SET TEST REQUEST:
" . print_r($response, true) . "
"; +print "[uc] SENT URL: " . $uc->getUrlSent() . "
"; +print "[uc] SENT URL PARSED:
" . print_r($uc->getUrlParsedSent(), true) . "
"; +print "[uc] SENT HEADERS:
" . print_r($uc->getHeadersSent(), true) . "
"; + print ""; // __END__ diff --git a/www/lib/CoreLibs/UrlRequests/Curl.php b/www/lib/CoreLibs/UrlRequests/Curl.php index ffc428d7..d6ffab13 100644 --- a/www/lib/CoreLibs/UrlRequests/Curl.php +++ b/www/lib/CoreLibs/UrlRequests/Curl.php @@ -59,12 +59,11 @@ class Curl implements Interface\RequestsInterface /** @var array{auth?:array{0:string,1:string,2:string},http_errors:bool,base_uri:string,headers:array>,query:array,timeout:float,connection_timeout:float} config settings as *phpcs:enable Generic.Files.LineLength * auth: [0: user, 1: password, 2: auth type] + * http_errors: default true, bool true/false for throwing exception on >= 400 HTTP errors * base_uri: base url to set, will prefix all urls given in calls * headers: (array) base headers, can be overwritten by headers set in call * timeout: default 0, in seconds (CURLOPT_TIMEOUT_MS) * connect_timeout: default 300, in seconds (CURLOPT_CONNECTTIMEOUT_MS) - * : below is not a guzzleHttp config - * http_errors: default true, bool true/false for throwing exception on >= 400 HTTP errors */ private array $config = [ 'http_errors' => true, @@ -129,29 +128,10 @@ class Curl implements Interface\RequestsInterface ]; // auth string is array of 0: user, 1: password, 2: auth type if (!empty($config['auth']) && is_array($config['auth'])) { - // base auth sets the header actually - $type = isset($config['auth'][2]) ? strtolower($config['auth'][2]) : 'basic'; - $userpwd = $config['auth'][0] . ':' . $config['auth'][1]; - switch ($type) { - case 'basic': - $this->auth_basic_header = 'Basic ' . base64_encode( - $userpwd - ); - // if (!isset($config['headers']['Authorization'])) { - // $config['headers']['Authorization'] = 'Basic ' . base64_encode( - // $userpwd - // ); - // } - break; - case 'digest': - $this->auth_type = CURLAUTH_DIGEST; - $this->auth_userpwd = $userpwd; - break; - case 'ntlm': - $this->auth_type = CURLAUTH_NTLM; - $this->auth_userpwd = $userpwd; - break; - } + $auth_data = $this->authParser($config['auth']); + $this->auth_basic_header = $auth_data['auth_basic_header']; + $this->auth_type = $auth_data['auth_type']; + $this->auth_userpwd = $auth_data['auth_userpwd']; } // only set if bool if ( @@ -187,6 +167,46 @@ class Curl implements Interface\RequestsInterface $this->config = array_merge($default_config, $config); } + // MARK: auth parser + + /** + * set various auth parameters and return them as array for further processing + * + * @param array{0:string,1:string,2:string} $auth + * @return array{auth_basic_header:string,auth_type:int,auth_userpwd:string} + */ + private function authParser(?array $auth): array + { + $return_auth = [ + 'auth_basic_header' => '', + 'auth_type' => 0, + 'auth_userpwd' => '', + ]; + // on empty return as is, to force defaults + if ($auth === []) { + return $return_auth; + } + // base auth sets the header actually + $type = isset($auth[2]) ? strtolower($auth[2]) : 'basic'; + $userpwd = $auth[0] . ':' . $auth[1]; + switch ($type) { + case 'basic': + $return_auth['auth_basic_header'] = 'Basic ' . base64_encode( + $userpwd + ); + break; + case 'digest': + $return_auth['auth_type'] = CURLAUTH_DIGEST; + $return_auth['auth_userpwd'] = $userpwd; + break; + case 'ntlm': + $return_auth['auth_type'] = CURLAUTH_NTLM; + $return_auth['auth_userpwd'] = $userpwd; + break; + } + return $return_auth; + } + // MARK: parse and build url /** @@ -371,6 +391,7 @@ class Curl implements Interface\RequestsInterface private function convertHeaders(array $headers): array { $return_headers = []; + $header_keys = []; foreach ($headers as $key => $value) { if (!is_string($key)) { // TODO: throw error @@ -378,8 +399,19 @@ class Curl implements Interface\RequestsInterface } // bad if not valid header key if (!preg_match('/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/D', $key)) { - // TODO throw error - continue; + throw new \UnexpectedValueException( + Json::jsonConvertArrayTo([ + 'status' => 'ERROR', + 'code' => 'R002', + 'type' => 'InvalidHeaderKey', + 'message' => 'Header key contains invalid characters', + 'context' => [ + 'key' => $key, + 'allowed' => '/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/D', + ], + ]), + 1 + ); } // if value is array, join to string if (is_array($value)) { @@ -388,8 +420,20 @@ class Curl implements Interface\RequestsInterface $value = trim((string)$value, " \t"); // header values must be valid if (!preg_match('/^[\x20\x09\x21-\x7E\x80-\xFF]*$/D', $value)) { - // TODO throw error - continue; + throw new \UnexpectedValueException( + Json::jsonConvertArrayTo([ + 'status' => 'ERROR', + 'code' => 'R003', + 'type' => 'InvalidHeaderValue', + 'message' => 'Header value contains invalid characters', + 'context' => [ + 'key' => $key, + 'value' => $value, + 'allowed' => '/^[\x20\x09\x21-\x7E\x80-\xFF]*$/D', + ], + ]), + 1 + ); } $return_headers[] = (string)$key . ':' . $value; } @@ -402,14 +446,23 @@ class Curl implements Interface\RequestsInterface * Authorization * User-Agent * - * @param array> $headers already set headers + * @param array> $headers already set headers + * @param ?string $auth_basic_header * @return array> */ - private function buildDefaultHeaders($headers = []): array + private function buildDefaultHeaders(array $headers = [], ?string $auth_basic_header = ''): array { - // add auth header if set - if (!empty($this->auth_basic_header)) { - $headers['Authorization'] = $this->auth_basic_header; + // add auth header if set, will overwrite any already set auth header + if ($auth_basic_header !== null && $auth_basic_header == '' && !empty($this->auth_basic_header)) { + $auth_basic_header = $this->auth_basic_header; + } + if (!empty($auth_basic_header)) { + // check if there is any auth header set, remove that one + if (!empty($auth_header_set = $this->headers_named[strtolower('Authorization')] ?? null)) { + unset($headers[$auth_header_set]); + } + // set new auth header + $headers['Authorization'] = $auth_basic_header; } // always add HTTP_HOST and HTTP_USER_AGENT if (!isset($headers[strtolower('User-Agent')])) { @@ -422,14 +475,15 @@ class Curl implements Interface\RequestsInterface * Build headers, combine with global headers of they are set * * @param null|array> $headers + * @param ?string $auth_basic_header * @return array> */ - private function buildHeaders(null|array $headers): array + private function buildHeaders(null|array $headers, ?string $auth_basic_header): array { // if headers is null, return empty headers, do not set config default headers // but the automatic set User-Agent and Authorization headers are always set if ($headers === null) { - return $this->buildDefaultHeaders(); + return $this->buildDefaultHeaders(auth_basic_header: $auth_basic_header); } // merge master headers with sub headers, sub headers overwrite master headers if (!empty($this->config['headers'])) { @@ -447,7 +501,7 @@ class Curl implements Interface\RequestsInterface $headers[$key] = $this->config['headers'][$key]; } } - $headers = $this->buildDefaultHeaders($headers); + $headers = $this->buildDefaultHeaders($headers, $auth_basic_header); return $headers; } @@ -465,6 +519,7 @@ class Curl implements Interface\RequestsInterface * @param null|string|array $body Data body, converted to JSON * @param null|bool $http_errors Throw exception on http response * 400 or higher if set to true + * @param null|array{0:string,1:string,2:string} $auth auth array, if null reset global set auth * @return array{code:string,headers:array>,content:string} * @throws \RuntimeException if type param is not valid */ @@ -475,14 +530,29 @@ class Curl implements Interface\RequestsInterface null|array $query, null|string|array $body, null|bool $http_errors, + null|array $auth, ): array { + // set auth from override + if (is_array($auth)) { + $auth_data = $this->authParser($auth); + } else { + $auth_data = [ + 'auth_basic_header' => null, + 'auth_type' => null, + 'auth_userpwd' => null, + ]; + } + // build url $this->url = $this->buildQuery($url, $query); - $this->headers = $this->convertHeaders($this->buildHeaders($headers)); + $this->headers = $this->convertHeaders($this->buildHeaders( + $headers, + $auth_data['auth_basic_header'] + )); if (!in_array($type, self::VALID_REQUEST_TYPES)) { throw new \RuntimeException( Json::jsonConvertArrayTo([ 'status' => 'ERROR', - 'code' => 'R002', + 'code' => 'R001', 'type' => 'InvalidRequestType', 'message' => 'Invalid request type set: ' . $type, 'context' => [ @@ -497,7 +567,10 @@ class Curl implements Interface\RequestsInterface // init curl handle $handle = $this->handleCurleInit($this->url); // set the standard curl options - $this->setCurlOptions($handle, $this->headers); + $this->setCurlOptions($handle, $this->headers, [ + 'auth_type' => $auth_data['auth_type'], + 'auth_userpwd' => $auth_data['auth_userpwd'], + ]); // for post we set POST option if ($type == "post") { curl_setopt($handle, CURLOPT_POST, true); @@ -515,7 +588,7 @@ class Curl implements Interface\RequestsInterface // for debug // print "CURLINFO_HEADER_OUT:
" . curl_getinfo($handle, CURLINFO_HEADER_OUT) . "
"; // get response code and bail on not authorized - $http_response = $this->handleCurlResponse($http_result, $http_errors, $handle); + $http_response = $this->handleCurlResponse($handle, $http_result, $http_errors); // close handler $this->handleCurlClose($handle); // return response and result @@ -565,14 +638,26 @@ class Curl implements Interface\RequestsInterface * * @param \CurlHandle $handle * @param array $headers list of options + * @param array{auth_type:?int,auth_userpwd:?string} $auth_data auth options to override global * @return void */ - private function setCurlOptions(\CurlHandle $handle, array $headers): void + private function setCurlOptions(\CurlHandle $handle, array $headers, ?array $auth_data): void { - // for not Basic auth, basic auth sets its own header - if (!empty($this->auth_type) && !empty($this->auth_userpwd)) { - curl_setopt($handle, CURLOPT_HTTPAUTH, $this->auth_type); - curl_setopt($handle, CURLOPT_USERPWD, $this->auth_userpwd); + // for not Basic auth only, basic auth sets its own header + if ($auth_data['auth_type'] !== null || $auth_data['auth_userpwd'] !== null) { + // set global if any of the two is empty and both globals are set + if ( + (empty($auth_data['auth_type']) || empty($auth_data['auth_userpwd'])) && + !empty($this->auth_type) && !empty($this->auth_userpwd) + ) { + $auth_data['auth_type'] = $this->auth_type; + $auth_data['auth_userpwd'] = $this->auth_userpwd; + } + } + // set auth options for curl + if (!empty($auth_data['auth_type']) && !empty($auth_data['auth_userpwd'])) { + curl_setopt($handle, CURLOPT_HTTPAUTH, $auth_data['auth_type']); + curl_setopt($handle, CURLOPT_USERPWD, $auth_data['auth_userpwd']); } if ($headers !== []) { curl_setopt($handle, CURLOPT_HTTPHEADER, $headers); @@ -694,9 +779,9 @@ class Curl implements Interface\RequestsInterface * @throws \RuntimeException if http_errors is true then will throw exception on any response code >= 400 */ private function handleCurlResponse( + \CurlHandle $handle, string $http_result, - ?bool $http_errors, - \CurlHandle $handle + ?bool $http_errors ): string { $http_response = curl_getinfo($handle, CURLINFO_RESPONSE_CODE); if ( @@ -743,48 +828,6 @@ class Curl implements Interface\RequestsInterface // MARK: PUBLIC METHODS // ********************************************************************* - /** - * Convert an array with header strings like "foo: bar" to the interface - * needed "foo" => "bar" type - * Skips entries that are already in key => value type, by checking if the - * key is a not a number - * - * @param array $headers - * @return array - * @throws \UnexpectedValueException on duplicate header key - */ - public function prepareHeaders(array $headers): array - { - $return_headers = []; - foreach ($headers as $header_key => $header) { - // skip if header key is not numeric - if (!is_numeric($header_key)) { - $return_headers[$header_key] = $header; - continue; - } - list($_key, $_value) = explode(':', $header); - if (array_key_exists($_key, $return_headers)) { - // raise exception if key already exists - throw new \UnexpectedValueException( - Json::jsonConvertArrayTo([ - 'status' => 'ERROR', - 'code' => 'R001', - 'type' => 'DuplicatedArrayKey', - 'message' => 'Key already exists in the headers', - 'context' => [ - 'key' => $_key, - 'headers' => $headers, - 'return_headers' => $return_headers, - ], - ]), - 1 - ); - } - $return_headers[$_key] = $_value; - } - return $return_headers; - } - // MARK: get class vars /** @@ -947,7 +990,7 @@ class Curl implements Interface\RequestsInterface * phpcs:disable Generic.Files.LineLength * @param string $type * @param string $url - * @param array{headers?:null|array>,query?:null|array,body?:null|string|array,http_errors?:null|bool} $options + * @param array{auth?:null|array{0:string,1:string,2:string},headers?:null|array>,query?:null|array,body?:null|string|array,http_errors?:null|bool} $options * @return array{code:string,headers:array>,content:string} Result code, headers and content as array, content is json * @throws \UnexpectedValueException on missing body data when body data is needed * phpcs:enable Generic.Files.LineLength @@ -971,6 +1014,7 @@ class Curl implements Interface\RequestsInterface $options['query'] ?? null, $options['body'] ?? null, !array_key_exists('http_errors', $options) ? null : $options['http_errors'], + !array_key_exists('auth', $options) ? [] : $options['auth'], ); } } diff --git a/www/lib/CoreLibs/UrlRequests/CurlTrait.php b/www/lib/CoreLibs/UrlRequests/CurlTrait.php index b75b3ab3..afacf76c 100644 --- a/www/lib/CoreLibs/UrlRequests/CurlTrait.php +++ b/www/lib/CoreLibs/UrlRequests/CurlTrait.php @@ -25,25 +25,21 @@ trait CurlTrait * "get" calls do not set any body * * @param string $type if set as get do not add body, else add body - * @param array{headers?:null|array>,query?:null|array,body?:null|string|array,http_errors?:null|bool} $options Request options - * @return array{headers?:null|array>,query?:null|array,body?:null|string|array,http_errors?:null|bool} + * @param array{auth?:null|array{0:string,1:string,2:string},headers?:null|array>,query?:null|array,body?:null|string|array,http_errors?:null|bool} $options Request options + * @return array{auth?:array{0:string,1:string,2:string},headers?:null|array>,query?:null|array,body?:null|string|array,http_errors?:null|bool} */ private function setOptions(string $type, array $options): array { - if ($type == "get") { - return [ - "headers" => !array_key_exists('headers', $options) ? [] : $options['headers'], - "query" => $options['query'] ?? null, - "http_errors" => !array_key_exists('http_errors', $options) ? null : $options['http_errors'], - ]; - } else { - return [ - "headers" => !array_key_exists('headers', $options) ? [] : $options['headers'], - "query" => $options['query'] ?? null, - "body" => $options['body'] ?? null, - "http_errors" => !array_key_exists('http_errors', $options) ? null : $options['http_errors'], - ]; + $base = [ + "auth" => !array_key_exists('auth', $options) ? [] : $options['auth'], + "headers" => !array_key_exists('headers', $options) ? [] : $options['headers'], + "query" => $options['query'] ?? null, + "http_errors" => !array_key_exists('http_errors', $options) ? null : $options['http_errors'], + ]; + if ($type != "get") { + $base["body"] = $options['body'] ?? null; } + return $base; } /** @@ -55,7 +51,7 @@ trait CurlTrait * * @param string $type What type of request we send, will throw exception if not a valid one * @param string $url The url to send - * @param array{headers?:null|array>,query?:null|array,body?:null|string|array,http_errors?:null|bool} $options Request options + * @param array{auth?:null|array{0:string,1:string,2:string},headers?:null|array>,query?:null|array,body?:null|string|array,http_errors?:null|bool} $options Request options * @return array{code:string,headers:array>,content:string} [default=[]] Result code, headers and content as array, content is json * @throws \UnexpectedValueException on missing body data when body data is needed */ @@ -67,7 +63,7 @@ trait CurlTrait * * @param string $url The URL being requested, * including domain and protocol - * @param array{headers?:null|array>,query?:null|array,body?:null|string|array,http_errors?:null|bool} $options Options to set + * @param array{auth?:null|array{0:string,1:string,2:string},headers?:null|array>,query?:null|array,body?:null|string|array,http_errors?:null|bool} $options Options to set * @return array{code:string,headers:array>,content:string} [default=[]] Result code, headers and content as array, content is json */ public function get(string $url, array $options = []): array @@ -77,9 +73,6 @@ trait CurlTrait $url, $this->setOptions('get', $options), ); - - // array{headers?: array|string>|null, query?: array|null, body?: array|string|null}, - // array{headers?: array|string>|null, query?: array|string|null, body?: array|string|null} } /** @@ -88,7 +81,7 @@ trait CurlTrait * * @param string $url The URL being requested, * including domain and protocol - * @param array{headers?:null|array>,query?:null|array,body?:null|string|array,http_errors?:null|bool} $options Options to set + * @param array{auth?:null|array{0:string,1:string,2:string},headers?:null|array>,query?:null|array,body?:null|string|array,http_errors?:null|bool} $options Options to set * @return array{code:string,headers:array>,content:string} Result code, headers and content as array, content is json */ public function post(string $url, array $options): array @@ -106,7 +99,7 @@ trait CurlTrait * * @param string $url The URL being requested, * including domain and protocol - * @param array{headers?:null|array>,query?:null|array,body?:null|string|array,http_errors?:null|bool} $options Options to set + * @param array{auth?:null|array{0:string,1:string,2:string},headers?:null|array>,query?:null|array,body?:null|string|array,http_errors?:null|bool} $options Options to set * @return array{code:string,headers:array>,content:string} Result code, headers and content as array, content is json */ public function put(string $url, array $options): array @@ -124,7 +117,7 @@ trait CurlTrait * * @param string $url The URL being requested, * including domain and protocol - * @param array{headers?:null|array>,query?:null|array,body?:null|string|array,http_errors?:null|bool} $options Options to set + * @param array{auth?:null|array{0:string,1:string,2:string},headers?:null|array>,query?:null|array,body?:null|string|array,http_errors?:null|bool} $options Options to set * @return array{code:string,headers:array>,content:string} Result code, headers and content as array, content is json */ public function patch(string $url, array $options): array @@ -143,7 +136,7 @@ trait CurlTrait * * @param string $url The URL being requested, * including domain and protocol - * @param array{headers?:null|array>,query?:null|array,body?:null|string|array,http_errors?:null|bool} $options Options to set + * @param array{auth?:null|array{0:string,1:string,2:string},headers?:null|array>,query?:null|array,body?:null|string|array,http_errors?:null|bool} $options Options to set * @return array{code:string,headers:array>,content:string} [default=[]] Result code, headers and content as array, content is json */ public function delete(string $url, array $options = []): array diff --git a/www/lib/CoreLibs/UrlRequests/Interface/RequestsInterface.php b/www/lib/CoreLibs/UrlRequests/Interface/RequestsInterface.php index e59b469c..bc6bb0e3 100644 --- a/www/lib/CoreLibs/UrlRequests/Interface/RequestsInterface.php +++ b/www/lib/CoreLibs/UrlRequests/Interface/RequestsInterface.php @@ -11,18 +11,6 @@ namespace CoreLibs\UrlRequests\Interface; interface RequestsInterface { - /** - * Convert an array with header strings like "foo: bar" to the interface - * needed "foo" => "bar" type - * Skips entries that are already in key => value type, by checking if the - * key is a not a number - * - * @param array $headers - * @return array - * @throws \UnexpectedValueException on duplicate header key - */ - public function prepareHeaders(array $headers): array; - /** * get the config array with all settings * @@ -84,7 +72,7 @@ interface RequestsInterface * phpcs:disable Generic.Files.LineLength * @param string $type * @param string $url - * @param array{headers?:null|array>,query?:null|array,body?:null|string|array} $options + * @param array{auth?:null|array{0:string,1:string,2:string},headers?:null|array>,query?:null|array,body?:null|string|array,http_errors?:null|bool} $options * @return array{code:string,headers:array>,content:string} Result code, headers and content as array, content is json * @throws \UnexpectedValueException on missing body data when body data is needed * phpcs:enable Generic.Files.LineLength