Repository: shlinkio/shlink (php)

Issue:
Html entities in titles are not parsed before saving
<!--
Before opening an issue, just take into account that this is a completely free of charge and open source project.
I'm always happy to help and provide support, but some understanding will be expected.
I do this in my own free time, so expect some delays when implementing new features and fixing bugs, and don't take it personal if an issue gets eventually closed.
You may also be asked to provide tests or ways to reproduce reported bugs.
Try to be polite, and understand it is impossible for an OSS project to cover all use cases.

With that said, please fill in the information requested next. More information might be requested next (like logs or system configs).
-->

https://www.php.net/manual/es/function.html-entity-decode.php

Pull request:
Feature/title html entities
Closes #1448 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 97153daf..56c66d15 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file.
 
 The format is based on [Keep a Changelog](https://keepachangelog.com), and this project adheres to [Semantic Versioning](https://semver.org).
 
+## [Unreleased]
+### Added
+* *Nothing*
+
+### Changed
+* *Nothing*
+
+### Deprecated
+* *Nothing*
+
+### Removed
+* *Nothing*
+
+### Fixed
+* [#1448](https://github.com/shlinkio/shlink/issues/1448) Fixed HTML entities not being properly parsed when auto-resolving page titles.
+
+
 ## [3.1.1] - 2022-05-09
 ### Added
 * *Nothing*
diff --git a/module/Core/src/Util/UrlValidator.php b/module/Core/src/Util/UrlValidator.php
index 6bd8d76f..2e2965b1 100644
--- a/module/Core/src/Util/UrlValidator.php
+++ b/module/Core/src/Util/UrlValidator.php
@@ -13,6 +13,7 @@ use Shlinkio\Shlink\Core\Exception\InvalidUrlException;
 use Shlinkio\Shlink\Core\Options\UrlShortenerOptions;
 use Throwable;
 
+use function html_entity_decode;
 use function preg_match;
 use function str_contains;
 use function str_starts_with;
@@ -71,7 +72,7 @@ class UrlValidator implements UrlValidatorInterface, RequestMethodInterface
             $collectedBody .= $body->read(1024);
         }
         preg_match(TITLE_TAG_VALUE, $collectedBody, $matches);
-        return isset($matches[1]) ? trim($matches[1]) : null;
+        return isset($matches[1]) ? $this->normalizeTitle($matches[1]) : null;
     }
 
     /**
@@ -101,4 +102,9 @@ class UrlValidator implements UrlValidatorInterface, RequestMethodInterface
             return null;
         }
     }
+
+    private function normalizeTitle(string $title): string
+    {
+        return html_entity_decode(trim($title));
+    }
 }


Test patch:
diff --git a/module/Core/test/Util/UrlValidatorTest.php b/module/Core/test/Util/UrlValidatorTest.php
index 7e1f9a1b..8aba6598 100644
--- a/module/Core/test/Util/UrlValidatorTest.php
+++ b/module/Core/test/Util/UrlValidatorTest.php
@@ -128,7 +128,7 @@ class UrlValidatorTest extends TestCase
 
         $result = $this->urlValidator->validateUrlWithTitle('http://foobar.com/12345/hello?foo=bar', true);
 
-        self::assertEquals('Resolved title', $result);
+        self::assertEquals('Resolved "title"', $result);
         $request->shouldHaveBeenCalledOnce();
     }
 
@@ -162,7 +162,7 @@ class UrlValidatorTest extends TestCase
 
     private function respWithTitle(): Response
     {
-        $body = $this->createStreamWithContent('<title data-foo="bar">  Resolved title</title>');
+        $body = $this->createStreamWithContent('<title data-foo="bar">  Resolved &quot;title&quot; </title>');
         return new Response($body, 200, ['Content-Type' => 'TEXT/html; charset=utf-8']);
     }