3434use GuzzleHttp \RequestOptions ;
3535use OCP \Http \Client \IClient ;
3636use OCP \Http \Client \IResponse ;
37+ use OCP \Http \Client \LocalServerException ;
3738use OCP \ICertificateManager ;
3839use OCP \IConfig ;
40+ use OCP \ILogger ;
3941
4042/**
4143 * Class Client
@@ -47,20 +49,19 @@ class Client implements IClient {
4749 private $ client ;
4850 /** @var IConfig */
4951 private $ config ;
52+ /** @var ILogger */
53+ private $ logger ;
5054 /** @var ICertificateManager */
5155 private $ certificateManager ;
5256
53- /**
54- * @param IConfig $config
55- * @param ICertificateManager $certificateManager
56- * @param GuzzleClient $client
57- */
5857 public function __construct (
5958 IConfig $ config ,
59+ ILogger $ logger ,
6060 ICertificateManager $ certificateManager ,
6161 GuzzleClient $ client
6262 ) {
6363 $ this ->config = $ config ;
64+ $ this ->logger = $ logger ;
6465 $ this ->client = $ client ;
6566 $ this ->certificateManager = $ certificateManager ;
6667 }
@@ -144,6 +145,53 @@ private function getProxyUri(): ?array {
144145 return $ proxy ;
145146 }
146147
148+ protected function preventLocalAddress (string $ uri , array $ options ): void {
149+ if (($ options ['nextcloud ' ]['allow_local_address ' ] ?? false ) ||
150+ $ this ->config ->getSystemValueBool ('allow_local_remote_servers ' , false )) {
151+ return ;
152+ }
153+
154+ $ host = parse_url ($ uri , PHP_URL_HOST );
155+ if ($ host === false ) {
156+ $ this ->logger ->warning ("Could not detect any host in $ uri " );
157+ throw new LocalServerException ('Could not detect any host ' );
158+ }
159+
160+ $ host = strtolower ($ host );
161+ // remove brackets from IPv6 addresses
162+ if (strpos ($ host , '[ ' ) === 0 && substr ($ host , -1 ) === '] ' ) {
163+ $ host = substr ($ host , 1 , -1 );
164+ }
165+
166+ // Disallow localhost and local network
167+ if ($ host === 'localhost ' || substr ($ host , -6 ) === '.local ' || substr ($ host , -10 ) === '.localhost ' ) {
168+ $ this ->logger ->warning ("Host $ host was not connected to because it violates local access rules " );
169+ throw new LocalServerException ('Host violates local access rules ' );
170+ }
171+
172+ // Disallow hostname only
173+ if (substr_count ($ host , '. ' ) === 0 ) {
174+ $ this ->logger ->warning ("Host $ host was not connected to because it violates local access rules " );
175+ throw new LocalServerException ('Host violates local access rules ' );
176+ }
177+
178+ if ((bool )filter_var ($ host , FILTER_VALIDATE_IP ) && !filter_var ($ host , FILTER_VALIDATE_IP , FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE )) {
179+ $ this ->logger ->warning ("Host $ host was not connected to because it violates local access rules " );
180+ throw new LocalServerException ('Host violates local access rules ' );
181+ }
182+
183+ // Also check for IPv6 IPv4 nesting, because that's not covered by filter_var
184+ if ((bool )filter_var ($ host , FILTER_VALIDATE_IP , FILTER_FLAG_IPV6 ) && substr_count ($ host , '. ' ) > 0 ) {
185+ $ delimiter = strrpos ($ host , ': ' ); // Get last colon
186+ $ ipv4Address = substr ($ host , $ delimiter + 1 );
187+
188+ if (!filter_var ($ ipv4Address , FILTER_VALIDATE_IP , FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE )) {
189+ $ this ->logger ->warning ("Host $ host was not connected to because it violates local access rules " );
190+ throw new LocalServerException ('Host violates local access rules ' );
191+ }
192+ }
193+ }
194+
147195 /**
148196 * Sends a GET request
149197 *
@@ -174,6 +222,7 @@ private function getProxyUri(): ?array {
174222 * @throws \Exception If the request could not get completed
175223 */
176224 public function get (string $ uri , array $ options = []): IResponse {
225+ $ this ->preventLocalAddress ($ uri , $ options );
177226 $ response = $ this ->client ->request ('get ' , $ uri , $ this ->buildRequestOptions ($ options ));
178227 $ isStream = isset ($ options ['stream ' ]) && $ options ['stream ' ];
179228 return new Response ($ response , $ isStream );
@@ -204,6 +253,7 @@ public function get(string $uri, array $options = []): IResponse {
204253 * @throws \Exception If the request could not get completed
205254 */
206255 public function head (string $ uri , array $ options = []): IResponse {
256+ $ this ->preventLocalAddress ($ uri , $ options );
207257 $ response = $ this ->client ->request ('head ' , $ uri , $ this ->buildRequestOptions ($ options ));
208258 return new Response ($ response );
209259 }
@@ -238,6 +288,8 @@ public function head(string $uri, array $options = []): IResponse {
238288 * @throws \Exception If the request could not get completed
239289 */
240290 public function post (string $ uri , array $ options = []): IResponse {
291+ $ this ->preventLocalAddress ($ uri , $ options );
292+
241293 if (isset ($ options ['body ' ]) && is_array ($ options ['body ' ])) {
242294 $ options ['form_params ' ] = $ options ['body ' ];
243295 unset($ options ['body ' ]);
@@ -276,6 +328,7 @@ public function post(string $uri, array $options = []): IResponse {
276328 * @throws \Exception If the request could not get completed
277329 */
278330 public function put (string $ uri , array $ options = []): IResponse {
331+ $ this ->preventLocalAddress ($ uri , $ options );
279332 $ response = $ this ->client ->request ('put ' , $ uri , $ this ->buildRequestOptions ($ options ));
280333 return new Response ($ response );
281334 }
@@ -310,6 +363,7 @@ public function put(string $uri, array $options = []): IResponse {
310363 * @throws \Exception If the request could not get completed
311364 */
312365 public function delete (string $ uri , array $ options = []): IResponse {
366+ $ this ->preventLocalAddress ($ uri , $ options );
313367 $ response = $ this ->client ->request ('delete ' , $ uri , $ this ->buildRequestOptions ($ options ));
314368 return new Response ($ response );
315369 }
@@ -344,6 +398,7 @@ public function delete(string $uri, array $options = []): IResponse {
344398 * @throws \Exception If the request could not get completed
345399 */
346400 public function options (string $ uri , array $ options = []): IResponse {
401+ $ this ->preventLocalAddress ($ uri , $ options );
347402 $ response = $ this ->client ->request ('options ' , $ uri , $ this ->buildRequestOptions ($ options ));
348403 return new Response ($ response );
349404 }
0 commit comments