CardDAV Client Library

HttpClientAdapterGuzzle extends HttpClientAdapter
in package

Adapter for the Guzzle HTTP client library.

Tags
psalm-import-type

Credentials from HttpClientAdapter

psalm-import-type

RequestOptions from HttpClientAdapter

psalm-type

GuzzleAllowRedirectCfg = array{ max?: int, strict?: bool, referer?: bool, protocols?: list, on_redirect?: callable(Psr7Request, Psr7Response, Psr7Uri): void, track_redirects?: bool }

psalm-type

GuzzleRequestOptions = array{ headers?: array<string, string | list>, body?: string | resource | \Psr\Http\Message\StreamInterface, allow_redirects?: bool | GuzzleAllowRedirectCfg, auth?: null | array{string, string} | array{string, string, string}, curl?: array, base_uri?: string, http_errors?: bool, handler?: HandlerStack }

Table of Contents

NEEDED_AUTHNFO  = ['basic' => ['username', 'password'], 'digest' => ['username', 'password'], 'bearer' => ['bearertoken']]
Defines which credential attributes are required for auth mechanisms.
GUZZLE_KNOWN_AUTHSCHEMES  = ['basic', 'digest', 'ntlm']
A list of authentication schemes that can be handled by Guzzle itself, independent on whether it works only with the Guzzle Curl HTTP handler or not. Strings must be lowercase!
$baseUri  : string
The base URI for requests.
$credentials  : Credentials
The credentials to use for authentication
$authScheme  : string|null
The HTTP authentication scheme to use. Null if not determined yet.
$client  : Client
The Client object of the Guzzle HTTP library.
$failedAuthSchemes  : array<int, string>
HTTP authentication schemes tried without success, to avoid trying again.
$known_authschemes  : array<int, string>
A list of authentication schemes that can be handled by this HttpClientAdapter.
$schemeToCurlOpt  : mixed
Maps lowercase auth-schemes to their CURLAUTH_XXX constant. Only values not part of GUZZLE_KNOWN_AUTHSCHEMES are relevant here.
__construct()  : mixed
Constructs a HttpClientAdapterGuzzle object.
sendRequest()  : ResponseInterface
Sends a PSR-7 request and returns a PSR-7 response.
checkCredentialsAvailable()  : bool
Checks if the needed credentials for an authentication scheme are available.
checkSameDomainAsBase()  : bool
Checks whether the given URI has the same domain as the base URI of this HTTP client.
getDomainFromSubdomain()  : string
Extracts the domain name from a subdomain.
checkSabreCurlIncompatibility()  : bool
Checks if a request was rejected because of an incompatibility between curl and sabre/dav.
getSupportedAuthSchemes()  : array<int, string>
Extracts HTTP authentication schemes from a WWW-Authenticate header.
prepareGuzzleOptions()  : array<string|int, mixed>
Prepares options for the Guzzle request.

Constants

NEEDED_AUTHNFO

Defines which credential attributes are required for auth mechanisms.

protected mixed NEEDED_AUTHNFO = ['basic' => ['username', 'password'], 'digest' => ['username', 'password'], 'bearer' => ['bearertoken']]

If a mechanism is not listed, it is assumed that no credentials are mandatory (e.g. GSSAPI/Kerberos).

GUZZLE_KNOWN_AUTHSCHEMES

A list of authentication schemes that can be handled by Guzzle itself, independent on whether it works only with the Guzzle Curl HTTP handler or not. Strings must be lowercase!

private array<int, string> GUZZLE_KNOWN_AUTHSCHEMES = ['basic', 'digest', 'ntlm']
Tags
psalm-var

list

Properties

$credentials

The credentials to use for authentication

protected Credentials $credentials

$authScheme

The HTTP authentication scheme to use. Null if not determined yet.

private string|null $authScheme

$failedAuthSchemes

HTTP authentication schemes tried without success, to avoid trying again.

private array<int, string> $failedAuthSchemes = []
Tags
psalm-var

list

$known_authschemes

A list of authentication schemes that can be handled by this HttpClientAdapter.

private array<int, string> $known_authschemes
Tags
psalm-var

list

$schemeToCurlOpt

Maps lowercase auth-schemes to their CURLAUTH_XXX constant. Only values not part of GUZZLE_KNOWN_AUTHSCHEMES are relevant here.

private static mixed $schemeToCurlOpt

Methods

__construct()

Constructs a HttpClientAdapterGuzzle object.

public __construct(string $base_uri, Credentials $credentials) : mixed
Parameters
$base_uri : string

Base URI to be used when relative URIs are given to requests.

$credentials : Credentials

Credentials used to authenticate with the server.

Return values
mixed

sendRequest()

Sends a PSR-7 request and returns a PSR-7 response.

public sendRequest(string $method, string $uri[, array<string, mixed> $options = [] ]) : ResponseInterface

The given URI may be relative to the base URI given on construction of this object or a full URL. Authentication is only attempted in case the domain name of the request URI matches that of the base URI (subdomains may differ).

Parameters
$method : string

The request method (GET, PROPFIND, etc.)

$uri : string

The target URI. If relative, taken relative to the internal base URI of the HTTP client

$options : array<string, mixed> = []

Options for the HTTP client, and default request options. May include any of the options accepted by HttpClientAdapter::sendRequest().

Tags
psalm-param

RequestOptions $options

Return values
ResponseInterface

The response retrieved from the server.

checkCredentialsAvailable()

Checks if the needed credentials for an authentication scheme are available.

protected checkCredentialsAvailable(string $scheme) : bool
Parameters
$scheme : string
Return values
bool

True if the credentials needed for the scheme are available.

checkSameDomainAsBase()

Checks whether the given URI has the same domain as the base URI of this HTTP client.

protected checkSameDomainAsBase(string $uri) : bool

If the given URI does not contain a domain part, true is returned (as when used, it will get that part from the base URI).

Parameters
$uri : string

The URI to check

Return values
bool

True if the URI shares the same domain as the base URI.

getDomainFromSubdomain()

Extracts the domain name from a subdomain.

protected static getDomainFromSubdomain(string $subdomain) : string

If the given string does not have a subdomain (i.e. top-level domain or domain only), it is returned as provided.

Parameters
$subdomain : string

The subdomain (e.g. sub.example.com)

Return values
string

The domain of $subdomain (e.g. example.com)

checkSabreCurlIncompatibility()

Checks if a request was rejected because of an incompatibility between curl and sabre/dav.

private checkSabreCurlIncompatibility(string $method, ResponseInterface $response) : bool

Background: When using DIGEST authentication, it is required to first send a request to the server to determine the parameters for the DIGEST authentication. This request is supposed to fail with 401 and the client can determine the parameters from the WWW-Authenticate header and try again with the proper Authentication header. Curl optimizes the first request by omitting the request body as it expects the request to fail anyway.

Now sabre/dav has a feature that allows to reply to certain REPORT requests without the need for authentication. This is specifically useful for Caldav, which may want to make available certain information from a calendar to anonymous users (e.g. free/busy time). Therefore, the authentication is done at a later time than the first attempt to evaluate the REPORT. A REPORT request requires a body, and thus sabre/dav will bail out with an internal server error instead of a 401, normally causing the client library to fail. The problem specifically only occurs for REPORT requests, for other requests such as PROPFIND the problem is not triggered in sabre and an expected 401 response is returned.

Read all about it here.

As a sidenote, nextcloud is not affected even though it uses sabre/dav, because the feature causing the server errors can be disabled and is in nextcloud. But there are other servers (Baïkal) using sabre/dav that are affected.

As a workaround, it is possible to ask curl to do negotiation of the authentication scheme to use, but providing the authentication scheme CURLAUTH_ANY. With this, curl will not assume that the initial request might fail (as not authentication may be needed), and thus the initial request will include the request body. The downside of this is that even when we know the authentication scheme supported by a server (e.g. basic), this setting will cause twice the number of requests being sent to the server.

Because it doesn't seem that this issue will get fixed, and the widespread usage of sabre/dav, I decided to include this workaround in the carddavclient library that specifically detects the situation and applies the above workaround without affecting the efficiency of communication when talking to other servers.

We detect the situation by the following indicators:

  • We have the curl extension loaded
  • REPORT request was sent
  • Result status code is 500
  • The server is a sabre/dav server (X-Sabre-Version header is set)
  • The response includes the known error message:
    <?xml version="1.0" encoding="utf-8"?>
    <d:error xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns">
      <s:sabredav-version>4.1.2</s:sabredav-version>
      <s:exception>Sabre\Xml\ParseException</s:exception>
      <s:message>The input element to parse is empty. Do not attempt to parse</s:message>
    </d:error>
    
Parameters
$method : string
$response : ResponseInterface
Return values
bool

getSupportedAuthSchemes()

Extracts HTTP authentication schemes from a WWW-Authenticate header.

private getSupportedAuthSchemes(ResponseInterface $response) : array<int, string>

The schemes offered by the server in the WWW-Authenticate header are intersected with those supported by Guzzle / curl. Schemes that have been tried with this object without success are filtered.

Parameters
$response : ResponseInterface

A status 401 response returned by the server.

Tags
psalm-return

list

Return values
array<int, string>

An array of authentication schemes that can be tried.

prepareGuzzleOptions()

Prepares options for the Guzzle request.

private prepareGuzzleOptions([array<string, mixed> $options = [] ][, bool $doAuth = false ]) : array<string|int, mixed>
Parameters
$options : array<string, mixed> = []
$doAuth : bool = false

True to attempt authentication. False will only try unauthenticated access.

Tags
psalm-param

RequestOptions $options

psalm-return

GuzzleRequestOptions

Return values
array<string|int, mixed>

Search results