Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
41 / 41
100.00% covered (success)
100.00%
5 / 5
CRAP
100.00% covered (success)
100.00%
1 / 1
AbstractProxy
100.00% covered (success)
100.00%
41 / 41
100.00% covered (success)
100.00%
5 / 5
15
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
37 / 37
100.00% covered (success)
100.00%
1 / 1
11
 logErrorWithClassName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getError
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getUrl
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isError
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 _getProxyPayload
n/a
0 / 0
n/a
0 / 0
0
 _extractShortUrl
n/a
0 / 0
n/a
0 / 0
0
 _getProxyUrl
n/a
0 / 0
n/a
0 / 0
0
1<?php declare(strict_types=1);
2/**
3 * PrivateBin
4 *
5 * a zero-knowledge paste bin
6 *
7 * @link      https://github.com/PrivateBin/PrivateBin
8 * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
9 * @license   https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
10 */
11
12namespace PrivateBin\Proxy;
13
14use Exception;
15use PrivateBin\Configuration;
16use PrivateBin\Json;
17
18/**
19 * AbstractProxy
20 *
21 * Forwards a URL for shortening and stores the result.
22 */
23abstract class AbstractProxy
24{
25    /**
26     * error message
27     *
28     * @access private
29     * @var    string
30     */
31    private $_error = '';
32
33    /**
34     * shortened URL
35     *
36     * @access private
37     * @var    string
38     */
39    private $_url = '';
40
41    /**
42     * constructor
43     *
44     * initializes and runs the proxy class
45     *
46     * @access public
47     * @param Configuration $conf
48     * @param string $link
49     */
50    public function __construct(Configuration $conf, string $link)
51    {
52        if (!filter_var($link, FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED | FILTER_FLAG_QUERY_REQUIRED)) {
53            $this->_error = 'Invalid URL given.';
54            return;
55        }
56
57        if (!str_starts_with($link, $conf->getKey('basepath') . '?') ||
58            parse_url($link, PHP_URL_HOST) != parse_url($conf->getKey('basepath'), PHP_URL_HOST)
59        ) {
60            $this->_error = 'Trying to shorten a URL that isn\'t pointing at our instance.';
61            return;
62        }
63
64        $proxyUrl = $this->_getProxyUrl($conf);
65
66        if (empty($proxyUrl)) {
67            $this->_error = 'Proxy error: Proxy URL is empty. This can be a configuration issue, like wrong or missing config keys.';
68            $this->logErrorWithClassName($this->_error);
69            return;
70        }
71
72        $data = file_get_contents($proxyUrl, false,
73            stream_context_create(
74                array(
75                    'http' => $this->_getProxyPayload($conf, $link),
76                )
77            )
78        );
79
80        if ($data === false) {
81            $http_response_header = $http_response_header ?? array();
82            $statusCode           = '';
83            if (!empty($http_response_header) && preg_match('/HTTP\/\d+\.\d+\s+(\d+)/', $http_response_header[0], $matches)) {
84                $statusCode = $matches[1];
85            }
86            $this->_error = 'Proxy error: Bad response. This can be a configuration issue, like wrong or missing config keys or a temporary outage.';
87            $this->logErrorWithClassName($this->_error . ' Status code: ' . $statusCode);
88            return;
89        }
90
91        try {
92            $jsonData = Json::decode($data);
93        } catch (Exception $e) {
94            $this->_error = 'Proxy error: Error parsing proxy response. This can be a configuration issue, like wrong or missing config keys.';
95            $this->logErrorWithClassName('Error calling proxy: ' . $e->getMessage());
96            return;
97        }
98
99        $url = $this->_extractShortUrl($jsonData);
100
101        if ($url === null || empty($url)) {
102            $this->_error = 'Proxy error: Error parsing proxy response. This can be a configuration issue, like wrong or missing config keys.';
103            $this->logErrorWithClassName('Error calling proxy: ' . $data);
104        } else {
105            $this->_url = $url;
106        }
107    }
108
109    private function logErrorWithClassName(string $error)
110    {
111        error_log('[' . get_class($this) . '] ' . $error);
112    }
113
114    /**
115     * Returns the (untranslated) error message
116     *
117     * @access public
118     * @return string
119     */
120    public function getError()
121    {
122        return $this->_error;
123    }
124
125    /**
126     * Returns the shortened URL
127     *
128     * @access public
129     * @return string
130     */
131    public function getUrl()
132    {
133        return $this->_url;
134    }
135
136    /**
137     * Returns true if any error has occurred
138     *
139     * @access public
140     * @return bool
141     */
142    public function isError()
143    {
144        return !empty($this->_error);
145    }
146
147    /**
148     * Abstract method to get the payload to send to the URL shortener
149     *
150     * @access protected
151     * @param Configuration $conf
152     * @param string $link
153     * @return array
154     */
155    abstract protected function _getProxyPayload(Configuration $conf, string $link): array;
156
157    /**
158     * Abstract method to extract the shortUrl from the response
159     *
160     * @param array $data
161     * @return ?string
162     */
163    abstract protected function _extractShortUrl(array $data): ?string;
164
165    /**
166     * Abstract method to get the proxy URL
167     *
168     * @param Configuration $conf
169     * @return string
170     */
171    abstract protected function _getProxyUrl(Configuration $conf): string;
172}