aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJeremy Benoist <jeremy.benoist@gmail.com>2017-06-08 21:51:46 +0200
committerJeremy Benoist <jeremy.benoist@gmail.com>2017-06-08 21:51:46 +0200
commit0d349ea67073c535e1aa7f19f3cf842a54458bfe (patch)
treec5449f49c941ea7cc09f7606e87464c3d03367cf
parent3f474025d889c3eff20b481f005f4d292f1ef29d (diff)
downloadwallabag-0d349ea67073c535e1aa7f19f3cf842a54458bfe.tar.gz
wallabag-0d349ea67073c535e1aa7f19f3cf842a54458bfe.tar.zst
wallabag-0d349ea67073c535e1aa7f19f3cf842a54458bfe.zip
Validate language & preview picture fields
Instead of saving the value of each field right into the content without any validation, it seems better to validate them. This might sounds obvious now we say that.
-rw-r--r--src/Wallabag/CoreBundle/Helper/ContentProxy.php78
-rw-r--r--src/Wallabag/CoreBundle/Resources/config/services.yml1
-rw-r--r--tests/Wallabag/CoreBundle/Helper/ContentProxyTest.php129
3 files changed, 185 insertions, 23 deletions
diff --git a/src/Wallabag/CoreBundle/Helper/ContentProxy.php b/src/Wallabag/CoreBundle/Helper/ContentProxy.php
index d5820e66..dd9170ad 100644
--- a/src/Wallabag/CoreBundle/Helper/ContentProxy.php
+++ b/src/Wallabag/CoreBundle/Helper/ContentProxy.php
@@ -7,6 +7,9 @@ use Psr\Log\LoggerInterface;
7use Wallabag\CoreBundle\Entity\Entry; 7use Wallabag\CoreBundle\Entity\Entry;
8use Wallabag\CoreBundle\Tools\Utils; 8use Wallabag\CoreBundle\Tools\Utils;
9use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeExtensionGuesser; 9use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeExtensionGuesser;
10use Symfony\Component\Validator\Constraints\Language as LanguageConstraint;
11use Symfony\Component\Validator\Constraints\Url as UrlConstraint;
12use Symfony\Component\Validator\Validator\ValidatorInterface;
10 13
11/** 14/**
12 * This kind of proxy class take care of getting the content from an url 15 * This kind of proxy class take care of getting the content from an url
@@ -21,10 +24,11 @@ class ContentProxy
21 protected $fetchingErrorMessage; 24 protected $fetchingErrorMessage;
22 protected $eventDispatcher; 25 protected $eventDispatcher;
23 26
24 public function __construct(Graby $graby, RuleBasedTagger $tagger, LoggerInterface $logger, $fetchingErrorMessage) 27 public function __construct(Graby $graby, RuleBasedTagger $tagger, ValidatorInterface $validator, LoggerInterface $logger, $fetchingErrorMessage)
25 { 28 {
26 $this->graby = $graby; 29 $this->graby = $graby;
27 $this->tagger = $tagger; 30 $this->tagger = $tagger;
31 $this->validator = $validator;
28 $this->logger = $logger; 32 $this->logger = $logger;
29 $this->mimeGuesser = new MimeTypeExtensionGuesser(); 33 $this->mimeGuesser = new MimeTypeExtensionGuesser();
30 $this->fetchingErrorMessage = $fetchingErrorMessage; 34 $this->fetchingErrorMessage = $fetchingErrorMessage;
@@ -113,7 +117,24 @@ class ContentProxy
113 $entry->setHeaders($content['all_headers']); 117 $entry->setHeaders($content['all_headers']);
114 } 118 }
115 119
116 $entry->setLanguage(isset($content['language']) ? $content['language'] : ''); 120 $this->validateAndSetLanguage(
121 $entry,
122 isset($content['language']) ? $content['language'] : ''
123 );
124
125 $this->validateAndSetPreviewPicture(
126 $entry,
127 isset($content['open_graph']['og_image']) ? $content['open_graph']['og_image'] : ''
128 );
129
130 // if content is an image define as a preview too
131 if (!empty($content['content_type']) && in_array($this->mimeGuesser->guess($content['content_type']), ['jpeg', 'jpg', 'gif', 'png'], true)) {
132 $this->validateAndSetPreviewPicture(
133 $entry,
134 $content['url']
135 );
136 }
137
117 $entry->setMimetype(isset($content['content_type']) ? $content['content_type'] : ''); 138 $entry->setMimetype(isset($content['content_type']) ? $content['content_type'] : '');
118 $entry->setReadingTime(Utils::getReadingTime($html)); 139 $entry->setReadingTime(Utils::getReadingTime($html));
119 140
@@ -122,15 +143,6 @@ class ContentProxy
122 $entry->setDomainName($domainName); 143 $entry->setDomainName($domainName);
123 } 144 }
124 145
125 if (!empty($content['open_graph']['og_image'])) {
126 $entry->setPreviewPicture($content['open_graph']['og_image']);
127 }
128
129 // if content is an image define as a preview too
130 if (!empty($content['content_type']) && in_array($this->mimeGuesser->guess($content['content_type']), ['jpeg', 'jpg', 'gif', 'png'], true)) {
131 $entry->setPreviewPicture($content['url']);
132 }
133
134 try { 146 try {
135 $this->tagger->tag($entry); 147 $this->tagger->tag($entry);
136 } catch (\Exception $e) { 148 } catch (\Exception $e) {
@@ -152,4 +164,48 @@ class ContentProxy
152 { 164 {
153 return !empty($content['title']) && !empty($content['html']) && !empty($content['url']); 165 return !empty($content['title']) && !empty($content['html']) && !empty($content['url']);
154 } 166 }
167
168 /**
169 * Use a Symfony validator to ensure the language is well formatted.
170 *
171 * @param Entry $entry
172 * @param string $value Language to validate
173 */
174 private function validateAndSetLanguage($entry, $value)
175 {
176 $errors = $this->validator->validate(
177 $value,
178 (new LanguageConstraint())
179 );
180
181 if (0 === count($errors)) {
182 $entry->setLanguage($value);
183
184 return;
185 }
186
187 $this->logger->warning('Language validation failed. '.(string) $errors);
188 }
189
190 /**
191 * Use a Symfony validator to ensure the preview picture is a real url.
192 *
193 * @param Entry $entry
194 * @param string $value URL to validate
195 */
196 private function validateAndSetPreviewPicture($entry, $value)
197 {
198 $errors = $this->validator->validate(
199 $value,
200 (new UrlConstraint())
201 );
202
203 if (0 === count($errors)) {
204 $entry->setPreviewPicture($value);
205
206 return;
207 }
208
209 $this->logger->warning('PreviewPicture validation failed. '.(string) $errors);
210 }
155} 211}
diff --git a/src/Wallabag/CoreBundle/Resources/config/services.yml b/src/Wallabag/CoreBundle/Resources/config/services.yml
index a9b0d2d5..2ae5d27f 100644
--- a/src/Wallabag/CoreBundle/Resources/config/services.yml
+++ b/src/Wallabag/CoreBundle/Resources/config/services.yml
@@ -90,6 +90,7 @@ services:
90 arguments: 90 arguments:
91 - "@wallabag_core.graby" 91 - "@wallabag_core.graby"
92 - "@wallabag_core.rule_based_tagger" 92 - "@wallabag_core.rule_based_tagger"
93 - "@validator"
93 - "@logger" 94 - "@logger"
94 - '%wallabag_core.fetching_error_message%' 95 - '%wallabag_core.fetching_error_message%'
95 96
diff --git a/tests/Wallabag/CoreBundle/Helper/ContentProxyTest.php b/tests/Wallabag/CoreBundle/Helper/ContentProxyTest.php
index a3570125..95dd75ba 100644
--- a/tests/Wallabag/CoreBundle/Helper/ContentProxyTest.php
+++ b/tests/Wallabag/CoreBundle/Helper/ContentProxyTest.php
@@ -11,6 +11,9 @@ use Wallabag\CoreBundle\Entity\Tag;
11use Wallabag\UserBundle\Entity\User; 11use Wallabag\UserBundle\Entity\User;
12use Wallabag\CoreBundle\Helper\RuleBasedTagger; 12use Wallabag\CoreBundle\Helper\RuleBasedTagger;
13use Graby\Graby; 13use Graby\Graby;
14use Symfony\Component\Validator\Validator\RecursiveValidator;
15use Symfony\Component\Validator\ConstraintViolationList;
16use Symfony\Component\Validator\ConstraintViolation;
14 17
15class ContentProxyTest extends \PHPUnit_Framework_TestCase 18class ContentProxyTest extends \PHPUnit_Framework_TestCase
16{ 19{
@@ -37,7 +40,7 @@ class ContentProxyTest extends \PHPUnit_Framework_TestCase
37 'language' => '', 40 'language' => '',
38 ]); 41 ]);
39 42
40 $proxy = new ContentProxy($graby, $tagger, $this->getLogger(), $this->fetchingErrorMessage); 43 $proxy = new ContentProxy($graby, $tagger, $this->getValidator(), $this->getLogger(), $this->fetchingErrorMessage);
41 $entry = new Entry(new User()); 44 $entry = new Entry(new User());
42 $proxy->updateEntry($entry, 'http://user@:80'); 45 $proxy->updateEntry($entry, 'http://user@:80');
43 46
@@ -72,7 +75,7 @@ class ContentProxyTest extends \PHPUnit_Framework_TestCase
72 'language' => '', 75 'language' => '',
73 ]); 76 ]);
74 77
75 $proxy = new ContentProxy($graby, $tagger, $this->getLogger(), $this->fetchingErrorMessage); 78 $proxy = new ContentProxy($graby, $tagger, $this->getValidator(), $this->getLogger(), $this->fetchingErrorMessage);
76 $entry = new Entry(new User()); 79 $entry = new Entry(new User());
77 $proxy->updateEntry($entry, 'http://0.0.0.0'); 80 $proxy->updateEntry($entry, 'http://0.0.0.0');
78 81
@@ -112,7 +115,7 @@ class ContentProxyTest extends \PHPUnit_Framework_TestCase
112 ], 115 ],
113 ]); 116 ]);
114 117
115 $proxy = new ContentProxy($graby, $tagger, $this->getLogger(), $this->fetchingErrorMessage); 118 $proxy = new ContentProxy($graby, $tagger, $this->getValidator(), $this->getLogger(), $this->fetchingErrorMessage);
116 $entry = new Entry(new User()); 119 $entry = new Entry(new User());
117 $proxy->updateEntry($entry, 'http://domain.io'); 120 $proxy->updateEntry($entry, 'http://domain.io');
118 121
@@ -154,7 +157,7 @@ class ContentProxyTest extends \PHPUnit_Framework_TestCase
154 ], 157 ],
155 ]); 158 ]);
156 159
157 $proxy = new ContentProxy($graby, $tagger, $this->getLogger(), $this->fetchingErrorMessage); 160 $proxy = new ContentProxy($graby, $tagger, $this->getValidator(), $this->getLogger(), $this->fetchingErrorMessage);
158 $entry = new Entry(new User()); 161 $entry = new Entry(new User());
159 $proxy->updateEntry($entry, 'http://0.0.0.0'); 162 $proxy->updateEntry($entry, 'http://0.0.0.0');
160 163
@@ -192,18 +195,112 @@ class ContentProxyTest extends \PHPUnit_Framework_TestCase
192 'open_graph' => [ 195 'open_graph' => [
193 'og_title' => 'my OG title', 196 'og_title' => 'my OG title',
194 'og_description' => 'OG desc', 197 'og_description' => 'OG desc',
195 'og_image' => false, 198 'og_image' => null,
196 ], 199 ],
197 ]); 200 ]);
198 201
199 $proxy = new ContentProxy($graby, $tagger, $this->getLogger(), $this->fetchingErrorMessage); 202 $proxy = new ContentProxy($graby, $tagger, $this->getValidator(), $this->getLogger(), $this->fetchingErrorMessage);
200 $entry = new Entry(new User()); 203 $entry = new Entry(new User());
201 $proxy->updateEntry($entry, 'http://0.0.0.0'); 204 $proxy->updateEntry($entry, 'http://0.0.0.0');
202 205
203 $this->assertEquals('http://1.1.1.1', $entry->getUrl()); 206 $this->assertEquals('http://1.1.1.1', $entry->getUrl());
204 $this->assertEquals('this is my title', $entry->getTitle()); 207 $this->assertEquals('this is my title', $entry->getTitle());
205 $this->assertContains('this is my content', $entry->getContent()); 208 $this->assertContains('this is my content', $entry->getContent());
206 $this->assertNull($entry->getPreviewPicture()); 209 $this->assertEmpty($entry->getPreviewPicture());
210 $this->assertEquals('text/html', $entry->getMimetype());
211 $this->assertEquals('fr', $entry->getLanguage());
212 $this->assertEquals('200', $entry->getHttpStatus());
213 $this->assertEquals(4.0, $entry->getReadingTime());
214 $this->assertEquals('1.1.1.1', $entry->getDomainName());
215 }
216
217 public function testWithContentAndBadLanguage()
218 {
219 $tagger = $this->getTaggerMock();
220 $tagger->expects($this->once())
221 ->method('tag');
222
223 $validator = $this->getValidator();
224 $validator->expects($this->exactly(2))
225 ->method('validate')
226 ->will($this->onConsecutiveCalls(
227 new ConstraintViolationList([new ConstraintViolation('oops', 'oops', [], 'oops', 'language', 'dontexist')]),
228 new ConstraintViolationList()
229 ));
230
231 $graby = $this->getMockBuilder('Graby\Graby')
232 ->setMethods(['fetchContent'])
233 ->disableOriginalConstructor()
234 ->getMock();
235
236 $graby->expects($this->any())
237 ->method('fetchContent')
238 ->willReturn([
239 'html' => str_repeat('this is my content', 325),
240 'title' => 'this is my title',
241 'url' => 'http://1.1.1.1',
242 'content_type' => 'text/html',
243 'language' => 'dontexist',
244 'status' => '200',
245 ]);
246
247 $proxy = new ContentProxy($graby, $tagger, $validator, $this->getLogger(), $this->fetchingErrorMessage);
248 $entry = new Entry(new User());
249 $proxy->updateEntry($entry, 'http://0.0.0.0');
250
251 $this->assertEquals('http://1.1.1.1', $entry->getUrl());
252 $this->assertEquals('this is my title', $entry->getTitle());
253 $this->assertContains('this is my content', $entry->getContent());
254 $this->assertEquals('text/html', $entry->getMimetype());
255 $this->assertEmpty($entry->getLanguage());
256 $this->assertEquals('200', $entry->getHttpStatus());
257 $this->assertEquals(4.0, $entry->getReadingTime());
258 $this->assertEquals('1.1.1.1', $entry->getDomainName());
259 }
260
261 public function testWithContentAndBadOgImage()
262 {
263 $tagger = $this->getTaggerMock();
264 $tagger->expects($this->once())
265 ->method('tag');
266
267 $validator = $this->getValidator();
268 $validator->expects($this->exactly(2))
269 ->method('validate')
270 ->will($this->onConsecutiveCalls(
271 new ConstraintViolationList(),
272 new ConstraintViolationList([new ConstraintViolation('oops', 'oops', [], 'oops', 'url', 'https://')])
273 ));
274
275 $graby = $this->getMockBuilder('Graby\Graby')
276 ->setMethods(['fetchContent'])
277 ->disableOriginalConstructor()
278 ->getMock();
279
280 $graby->expects($this->any())
281 ->method('fetchContent')
282 ->willReturn([
283 'html' => str_repeat('this is my content', 325),
284 'title' => 'this is my title',
285 'url' => 'http://1.1.1.1',
286 'content_type' => 'text/html',
287 'language' => 'fr',
288 'status' => '200',
289 'open_graph' => [
290 'og_title' => 'my OG title',
291 'og_description' => 'OG desc',
292 'og_image' => 'https://',
293 ],
294 ]);
295
296 $proxy = new ContentProxy($graby, $tagger, $validator, $this->getLogger(), $this->fetchingErrorMessage);
297 $entry = new Entry(new User());
298 $proxy->updateEntry($entry, 'http://0.0.0.0');
299
300 $this->assertEquals('http://1.1.1.1', $entry->getUrl());
301 $this->assertEquals('this is my title', $entry->getTitle());
302 $this->assertContains('this is my content', $entry->getContent());
303 $this->assertEmpty($entry->getPreviewPicture());
207 $this->assertEquals('text/html', $entry->getMimetype()); 304 $this->assertEquals('text/html', $entry->getMimetype());
208 $this->assertEquals('fr', $entry->getLanguage()); 305 $this->assertEquals('fr', $entry->getLanguage());
209 $this->assertEquals('200', $entry->getHttpStatus()); 306 $this->assertEquals('200', $entry->getHttpStatus());
@@ -217,7 +314,7 @@ class ContentProxyTest extends \PHPUnit_Framework_TestCase
217 $tagger->expects($this->once()) 314 $tagger->expects($this->once())
218 ->method('tag'); 315 ->method('tag');
219 316
220 $proxy = new ContentProxy((new Graby()), $tagger, $this->getLogger(), $this->fetchingErrorMessage); 317 $proxy = new ContentProxy((new Graby()), $tagger, $this->getValidator(), $this->getLogger(), $this->fetchingErrorMessage);
221 $entry = new Entry(new User()); 318 $entry = new Entry(new User());
222 $proxy->updateEntry( 319 $proxy->updateEntry(
223 $entry, 320 $entry,
@@ -259,7 +356,7 @@ class ContentProxyTest extends \PHPUnit_Framework_TestCase
259 $logHandler = new TestHandler(); 356 $logHandler = new TestHandler();
260 $logger = new Logger('test', [$logHandler]); 357 $logger = new Logger('test', [$logHandler]);
261 358
262 $proxy = new ContentProxy((new Graby()), $tagger, $logger, $this->fetchingErrorMessage); 359 $proxy = new ContentProxy((new Graby()), $tagger, $this->getValidator(), $logger, $this->fetchingErrorMessage);
263 $entry = new Entry(new User()); 360 $entry = new Entry(new User());
264 $proxy->updateEntry( 361 $proxy->updateEntry(
265 $entry, 362 $entry,
@@ -294,7 +391,7 @@ class ContentProxyTest extends \PHPUnit_Framework_TestCase
294 $handler = new TestHandler(); 391 $handler = new TestHandler();
295 $logger->pushHandler($handler); 392 $logger->pushHandler($handler);
296 393
297 $proxy = new ContentProxy((new Graby()), $tagger, $logger, $this->fetchingErrorMessage); 394 $proxy = new ContentProxy((new Graby()), $tagger, $this->getValidator(), $logger, $this->fetchingErrorMessage);
298 $entry = new Entry(new User()); 395 $entry = new Entry(new User());
299 $proxy->updateEntry( 396 $proxy->updateEntry(
300 $entry, 397 $entry,
@@ -331,7 +428,7 @@ class ContentProxyTest extends \PHPUnit_Framework_TestCase
331 ->method('tag') 428 ->method('tag')
332 ->will($this->throwException(new \Exception())); 429 ->will($this->throwException(new \Exception()));
333 430
334 $proxy = new ContentProxy((new Graby()), $tagger, $this->getLogger(), $this->fetchingErrorMessage); 431 $proxy = new ContentProxy((new Graby()), $tagger, $this->getValidator(), $this->getLogger(), $this->fetchingErrorMessage);
335 $entry = new Entry(new User()); 432 $entry = new Entry(new User());
336 $proxy->updateEntry( 433 $proxy->updateEntry(
337 $entry, 434 $entry,
@@ -371,7 +468,7 @@ class ContentProxyTest extends \PHPUnit_Framework_TestCase
371 $tagger->expects($this->once()) 468 $tagger->expects($this->once())
372 ->method('tag'); 469 ->method('tag');
373 470
374 $proxy = new ContentProxy((new Graby()), $tagger, $this->getLogger(), $this->fetchingErrorMessage); 471 $proxy = new ContentProxy((new Graby()), $tagger, $this->getValidator(), $this->getLogger(), $this->fetchingErrorMessage);
375 $entry = new Entry(new User()); 472 $entry = new Entry(new User());
376 $proxy->updateEntry( 473 $proxy->updateEntry(
377 $entry, 474 $entry,
@@ -413,4 +510,12 @@ class ContentProxyTest extends \PHPUnit_Framework_TestCase
413 { 510 {
414 return new NullLogger(); 511 return new NullLogger();
415 } 512 }
513
514 private function getValidator()
515 {
516 return $this->getMockBuilder(RecursiveValidator::class)
517 ->setMethods(['validate'])
518 ->disableOriginalConstructor()
519 ->getMock();
520 }
416} 521}