Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
OptionsCacheKey
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
4 / 4
7
100.00% covered (success)
100.00%
1 / 1
 forService
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 backend
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 profile
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 digest
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3/**
4 * SPDX-License-Identifier: EUPL-1.2
5 *
6 * This file is part of icap-flow.
7 *
8 * Licensed under the EUPL, Version 1.2 only (the "Licence");
9 * you may not use this work except in compliance with the Licence.
10 * You may obtain a copy of the Licence at:
11 *
12 *     https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the Licence is distributed on an "AS IS" basis,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 */
18
19declare(strict_types=1);
20
21namespace Ndrstmr\Icap\Cache;
22
23use Amp\Socket\ClientTlsContext;
24
25/**
26 * The keys of the OPTIONS cache (M12).
27 *
28 * PSR-6 and PSR-16 guarantee a key only if it consists of A-Z, a-z, 0-9, "_"
29 * and "." and has at most 64 characters, and they reserve {}()/\@: for
30 * themselves. A key written as host:port/service breaks both rules, and a
31 * prefix in front of a 64-character hash breaks the length. Every key here is
32 * therefore the SHA-256 of its parts, each written with its length: parts that
33 * run into each other (prefix "a" and key "bc", prefix "ab" and key "c") give
34 * different keys, and whatever the parts contain, the key is 64 hex digits.
35 * The first part names the scheme and its version, so a new scheme never reads
36 * an entry of the old one.
37 *
38 * @internal
39 */
40final class OptionsCacheKey
41{
42    /**
43     * The key under which the client stores and finds the OPTIONS response of
44     * a service. Host, port, service and security profile stay separate parts,
45     * in options() and in the preview size lookup alike.
46     *
47     * The security profile is `plain`, or the TLS settings the context applies to the socket: a client with other
48     * settings has another profile and never meets this client's entry in a shared cache. The settings are hashed, not
49     * the object, so that processes with the same settings share an entry. The passphrase of a certificate is left
50     * out: a secret does not belong in a key, and it does not tell one profile from another. What the key cannot see
51     * (a tenant, a proxy of a custom transport) belongs in the cache or its prefix.
52     */
53    public static function forService(string $host, int $port, string $service, ?ClientTlsContext $tls = null): string
54    {
55        return self::digest('icap-flow/options/v2', $host, (string) $port, $service, self::profile($tls));
56    }
57
58    /**
59     * The key an adapter gives its PSR backend for one of its entries. The
60     * prefix is a part of the hash: it separates adapters that share a backend
61     * without making the key longer.
62     */
63    public static function backend(string $prefix, string $key): string
64    {
65        return self::digest('icap-flow/options-cache/v2', $prefix, $key);
66    }
67
68    private static function profile(?ClientTlsContext $tls): string
69    {
70        if ($tls === null) {
71            return 'plain';
72        }
73
74        $options = $tls->toStreamContextArray()['ssl'] ?? [];
75        if (is_array($options)) {
76            unset($options['passphrase']);
77        }
78
79        return 'tls:' . hash('sha256', serialize($options));
80    }
81
82    private static function digest(string ...$parts): string
83    {
84        $input = '';
85        foreach ($parts as $part) {
86            $input .= strlen($part) . ':' . $part . ';';
87        }
88
89        return hash('sha256', $input);
90    }
91}