Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
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;
22
23use Amp\Cancellation;
24use Amp\Future;
25use Ndrstmr\Icap\DTO\IcapRequest;
26use Ndrstmr\Icap\DTO\IcapResponse;
27use Ndrstmr\Icap\DTO\ScanResult;
28
29/**
30 * Public contract for ICAP clients.
31 *
32 * Implementations are async-native and return Amp Futures. The synchronous
33 * decorator {@see SynchronousIcapClient} awaits those Futures behind a
34 * blocking facade.
35 *
36 * Every method takes an optional {@see Cancellation} so a caller can
37 * abort an in-flight scan from the outside (e.g. when an upstream HTTP
38 * upload has been aborted). A call ends at the first of three things: the
39 * caller's cancellation, which stays an `Amp\CancelledException` and wins
40 * when more than one applies; the end of the operation
41 * ({@see Config::getOperationTimeout()}, fixed when the method is called and
42 * covering every phase, and in the retry decorator every attempt and
43 * pause); and the limit of a phase ({@see Config::getStreamTimeout()}, the
44 * TLS handshake, the start of the answer). The last two are an
45 * {@see \Ndrstmr\Icap\Exception\IcapTimeoutException}. An expired call
46 * closes its socket and its file handle. Code that holds the event loop and
47 * does not return (a request-body producer, a slow cache) cannot be
48 * interrupted; the end is looked at when control comes back, and a result
49 * that comes in after it is not returned. A call into the operating system
50 * that hangs does not hold the loop, but is not interrupted either: the
51 * operation ends, and the file is closed when the call returns.
52 *
53 * Expected operational failures ({@see \Ndrstmr\Icap\Exception\IcapExceptionInterface})
54 * fail the returned Future, also when the answer comes from the OPTIONS cache;
55 * only invalid arguments throw at the call. What the code you pass in throws
56 * (producer, logger, strategy, cache, connector) passes unchanged.
57 */
58interface IcapClientInterface
59{
60    /**
61     * Send a raw ICAP request.
62     *
63     * A stream resource in the encapsulated body is read, never closed: it
64     * stays the caller's, whatever the outcome.
65     *
66     * @return Future<ScanResult>
67     */
68    public function request(IcapRequest $request, ?Cancellation $cancellation = null): Future;
69
70    /**
71     * Issue an OPTIONS request against the given service. Returns the raw
72     * {@see IcapResponse} on success so callers can inspect capability
73     * headers (`Preview`, `Options-TTL`, `Methods`, `Allow`, `Service`,
74     * `ISTag`, …) directly. Only a `200` is an answer to OPTIONS, whether it
75     * comes from the server or from the cache: 4xx throws
76     * {@see IcapClientException}, 5xx throws {@see IcapServerException}, and
77     * every other status, including `100 Continue`, `204` and `206`, throws
78     * {@see IcapProtocolException}.
79     *
80     * @return Future<IcapResponse>
81     */
82    public function options(string $service, ?Cancellation $cancellation = null): Future;
83
84    /**
85     * Scan a local file via RESPMOD.
86     *
87     * The file is opened once, inside the returned Future, read through
88     * amphp/file and closed when the operation ends, whatever the outcome.
89     * Its size is that of the open file, and the body goes out with exactly
90     * that many bytes. The application keeps the file unchanged until the
91     * Future completes and relates the result to that version: a change of
92     * size is detected, new content of the same size is not. Only regular,
93     * seekable files of the local file system are supported; a relative path
94     * is taken from the working directory of the call. The Future fails with
95     * an {@see \Ndrstmr\Icap\Exception\IcapStorageException} when the path
96     * is not a regular file, the file cannot be opened, sized, read to its end
97     * or closed, has another size than when it was opened, or the request did
98     * not read it to its end;
99     * nothing is sent as if it were an empty, shorter or longer file. The body
100     * of the response is kept, within `Config::getMaxResponseSize()`; for a
101     * server that sends a large file back, see {@see \Ndrstmr\Icap\DiscardingIcapClient}.
102     * See docs/file-sources.md.
103     *
104     * @param array<string, string|string[]> $extraHeaders Extra ICAP request
105     *        headers, e.g. `['X-Client-IP' => '203.0.113.5']`. `Host` and
106     *        `Encapsulated` are always set by the client; caller values for
107     *        them are dropped in any spelling. No preview is sent, so a
108     *        caller `Preview` is dropped as well; `Allow` passes.
109     * @return Future<ScanResult>
110     */
111    public function scanFile(
112        string $service,
113        string $filePath,
114        array $extraHeaders = [],
115        ?Cancellation $cancellation = null,
116    ): Future;
117
118    /**
119     * Scan a file using preview mode, streaming the remainder on demand.
120     *
121     * When $previewSize is null the client queries the OPTIONS cache for
122     * the server's advertised `Preview` header (RFC 3507 §4.10.2) and
123     * uses that value. Falls back to 1024 when the cache has no entry
124     * or the response carries no `Preview` header.
125     *
126     * The preview is 0 to 1 MiB (1048576 bytes). `0` sends the headers
127     * alone. An explicit size outside that range throws an
128     * \InvalidArgumentException before the file is opened; a cached value
129     * above it is clamped to it, and a cached value that is not a
130     * non-negative decimal integer throws an
131     * {@see \Ndrstmr\Icap\Exception\IcapMalformedResponseException}. The
132     * file is handled as in {@see scanFile()}, `ieof` is sent only
133     * after the end of the file was observed, and the rest of the file goes
134     * out with the length its envelope announced, on the connection of the
135     * preview (RFC 3507 §4.5): a client whose transport cannot do that throws
136     * a \LogicException at the call, before it opens the file.
137     *
138     * @param array<string, string|string[]> $extraHeaders See {@see scanFile()}.
139     *        `Preview` and `Allow` are library-managed and cannot be
140     *        overridden via this parameter.
141     * @return Future<ScanResult>
142     */
143    public function scanFileWithPreview(
144        string $service,
145        string $filePath,
146        ?int $previewSize = null,
147        array $extraHeaders = [],
148        ?Cancellation $cancellation = null,
149    ): Future;
150}