Merge pull request #7947 from wallabag/migrate-from-guzzle-to-symfony-httpclient

Migrate from Guzzle to Symfony HttpClient
This commit is contained in:
Kevin Decherf 2025-02-17 11:06:02 +01:00 committed by GitHub
commit 8e0b9d4d94
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 661 additions and 1303 deletions

View file

@ -32,7 +32,6 @@ class AppKernel extends Kernel
new BabDev\PagerfantaBundle\BabDevPagerfantaBundle(),
new FOS\JsRoutingBundle\FOSJsRoutingBundle(),
new OldSound\RabbitMqBundle\OldSoundRabbitMqBundle(),
new Http\HttplugBundle\HttplugBundle(),
new Sentry\SentryBundle\SentryBundle(),
new Twig\Extra\TwigExtraBundle\TwigExtraBundle(),
new Symfony\WebpackEncoreBundle\WebpackEncoreBundle(),

View file

@ -48,6 +48,10 @@ framework:
X-Accept: 'application/json'
request_html_function.client:
scope: '.*'
browser.client:
scope: '.*'
verify_host: false
verify_peer: false
# Twig Configuration
twig:
@ -452,17 +456,6 @@ sensio_framework_extra:
router:
annotations: false
httplug:
clients:
wallabag:
factory: Wallabag\Helper\HttpClientFactory
config:
defaults:
timeout: 10
plugins: ['httplug.plugin.logger']
discovery:
client: false
# define custom entity so we can override length attribute to fix utf8mb4 issue
craue_config:
entity_name: Wallabag\Entity\InternalSetting

View file

@ -191,11 +191,17 @@ services:
tags:
- { name: doctrine.event_subscriber }
psr18.wallabag.client:
class: Symfony\Component\HttpClient\Psr18Client
arguments:
$client: '@Wallabag\HttpClient\WallabagClient'
Graby\Graby:
arguments:
$config:
error_message: '%wallabag.fetching_error_message%'
error_message_title: '%wallabag.fetching_error_message_title%'
$client: '@psr18.wallabag.client'
calls:
- [ setLogger, [ "@logger" ] ]
tags:
@ -205,9 +211,6 @@ services:
arguments:
$config: {}
wallabag.http_client:
alias: 'httplug.client.wallabag'
Wallabag\SiteConfig\GrabySiteConfigBuilder:
tags:
- { name: monolog.logger, channel: graby }
@ -216,11 +219,9 @@ services:
Wallabag\SiteConfig\SiteConfigBuilder:
alias: Wallabag\SiteConfig\GrabySiteConfigBuilder
GuzzleHttp\Cookie\CookieJar: ~
Wallabag\Helper\HttpClientFactory:
calls:
- ['addSubscriber', ['@Wallabag\Guzzle\AuthenticatorSubscriber']]
Symfony\Component\BrowserKit\HttpBrowser:
arguments:
$client: '@browser.client'
RulerZ\RulerZ:
alias: rulerz

View file

@ -29,8 +29,6 @@ $config
'mnapoli/piwik-twig-extension',
'ocramius/proxy-manager',
'pagerfanta/twig',
'php-http/client-common',
'php-http/httplug',
'php-http/mock-client',
'phpstan/extension-installer',
'phpstan/phpstan',
@ -39,13 +37,11 @@ $config
'phpstan/phpstan-symfony',
'psr/http-client',
'psr/http-factory',
'psr/http-message',
'rulerz-php/doctrine-orm',
'scheb/2fa-qr-code',
'scheb/2fa-trusted-device',
'shipmonk/composer-dependency-analyser',
'symfony/asset',
'symfony/browser-kit',
'symfony/css-selector',
'symfony/doctrine-bridge',
'symfony/google-mailer',
@ -57,10 +53,8 @@ $config
'twig/string-extra',
], [ErrorType::UNUSED_DEPENDENCY])
->ignoreErrorsOnPackages([
'guzzlehttp/streams',
'monolog/monolog',
'symfony/filesystem',
'symfony/http-client',
], [ErrorType::PROD_DEPENDENCY_ONLY_IN_DEV])
->ignoreErrorsOnPackages([
'dama/doctrine-test-bundle',

View file

@ -75,9 +75,7 @@
"friendsofsymfony/oauth-server-bundle": "dev-master#dc8ff343363cf794d30eb1a123610d186a43f162",
"friendsofsymfony/rest-bundle": "^3.6",
"friendsofsymfony/user-bundle": "^3.2.1",
"guzzlehttp/guzzle": "^5.3.4",
"guzzlehttp/psr7": "^2.6.2",
"guzzlehttp/streams": "^3.0",
"html2text/html2text": "^4.3.1",
"incenteev/composer-parameter-handler": "^2.2",
"j0k3r/graby": "^2.4.5",
@ -99,10 +97,6 @@
"pagerfanta/twig": "^3.8",
"php-amqplib/php-amqplib": "^3.6.1",
"php-amqplib/rabbitmq-bundle": "^2.14.0",
"php-http/client-common": "^2.7.1",
"php-http/guzzle5-adapter": "^2.0",
"php-http/httplug": "^2.4",
"php-http/httplug-bundle": "^1.32",
"pragmarx/recovery": "^0.2.1",
"predis/predis": "^2.2.2",
"psr/http-client": "^1.0.3",
@ -122,6 +116,7 @@
"spiriitlabs/form-filter-bundle": "^10.0",
"stof/doctrine-extensions-bundle": "^1.11.0",
"symfony/asset": "^5.4.35",
"symfony/browser-kit": "^5.4.35",
"symfony/config": "^5.4.35",
"symfony/console": "^5.4.35",
"symfony/dependency-injection": "^5.4.35",
@ -182,7 +177,6 @@
"phpstan/phpstan-symfony": "^1.3.7",
"phpunit/phpunit": "^9.6.17",
"shipmonk/composer-dependency-analyser": "^1.7",
"symfony/browser-kit": "^5.4.35",
"symfony/css-selector": "^5.4.35",
"symfony/debug-bundle": "^5.4.35",
"symfony/maker-bundle": "^1.43",

751
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "1f0877b1f0f7fc3b074786b51d772cb6",
"content-hash": "a85056bec7fa90b9be4aa16c34464c0e",
"packages": [
{
"name": "babdev/pagerfanta-bundle",
@ -2980,63 +2980,6 @@
},
"time": "2015-05-14T08:18:23+00:00"
},
{
"name": "guzzlehttp/guzzle",
"version": "5.3.4",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
"reference": "b87eda7a7162f95574032da17e9323c9899cb6b2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/b87eda7a7162f95574032da17e9323c9899cb6b2",
"reference": "b87eda7a7162f95574032da17e9323c9899cb6b2",
"shasum": ""
},
"require": {
"guzzlehttp/ringphp": "^1.1",
"php": ">=5.4.0",
"react/promise": "^2.2"
},
"require-dev": {
"ext-curl": "*",
"phpunit/phpunit": "^4.0"
},
"type": "library",
"autoload": {
"psr-4": {
"GuzzleHttp\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
}
],
"description": "Guzzle is a PHP HTTP client library and framework for building RESTful web service clients",
"homepage": "http://guzzlephp.org/",
"keywords": [
"client",
"curl",
"framework",
"http",
"http client",
"rest",
"web service"
],
"support": {
"issues": "https://github.com/guzzle/guzzle/issues",
"source": "https://github.com/guzzle/guzzle/tree/5.3"
},
"time": "2019-10-30T09:32:00+00:00"
},
{
"name": "guzzlehttp/psr7",
"version": "2.7.0",
@ -3153,117 +3096,6 @@
],
"time": "2024-07-18T11:15:46+00:00"
},
{
"name": "guzzlehttp/ringphp",
"version": "1.1.1",
"source": {
"type": "git",
"url": "https://github.com/guzzle/RingPHP.git",
"reference": "5e2a174052995663dd68e6b5ad838afd47dd615b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/RingPHP/zipball/5e2a174052995663dd68e6b5ad838afd47dd615b",
"reference": "5e2a174052995663dd68e6b5ad838afd47dd615b",
"shasum": ""
},
"require": {
"guzzlehttp/streams": "~3.0",
"php": ">=5.4.0",
"react/promise": "~2.0"
},
"require-dev": {
"ext-curl": "*",
"phpunit/phpunit": "~4.0"
},
"suggest": {
"ext-curl": "Guzzle will use specific adapters if cURL is present"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.1-dev"
}
},
"autoload": {
"psr-4": {
"GuzzleHttp\\Ring\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
}
],
"description": "Provides a simple API and specification that abstracts away the details of HTTP into a single PHP function.",
"support": {
"issues": "https://github.com/guzzle/RingPHP/issues",
"source": "https://github.com/guzzle/RingPHP/tree/1.1.1"
},
"abandoned": true,
"time": "2018-07-31T13:22:33+00:00"
},
{
"name": "guzzlehttp/streams",
"version": "3.0.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/streams.git",
"reference": "47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/streams/zipball/47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5",
"reference": "47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5",
"shasum": ""
},
"require": {
"php": ">=5.4.0"
},
"require-dev": {
"phpunit/phpunit": "~4.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0-dev"
}
},
"autoload": {
"psr-4": {
"GuzzleHttp\\Stream\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
}
],
"description": "Provides a simple abstraction over streams of data",
"homepage": "http://guzzlephp.org/",
"keywords": [
"Guzzle",
"stream"
],
"support": {
"issues": "https://github.com/guzzle/streams/issues",
"source": "https://github.com/guzzle/streams/tree/master"
},
"abandoned": true,
"time": "2014-10-12T19:18:40+00:00"
},
{
"name": "hoa/compiler",
"version": "3.17.08.08",
@ -6511,74 +6343,6 @@
},
"time": "2024-10-02T11:20:13+00:00"
},
{
"name": "php-http/guzzle5-adapter",
"version": "2.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-http/guzzle5-adapter.git",
"reference": "cce48360b1f8a3467bd94e853e6107aa4532008e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-http/guzzle5-adapter/zipball/cce48360b1f8a3467bd94e853e6107aa4532008e",
"reference": "cce48360b1f8a3467bd94e853e6107aa4532008e",
"shasum": ""
},
"require": {
"guzzlehttp/guzzle": "^5.1",
"php": "^7.0",
"php-http/discovery": "^1.0",
"php-http/httplug": "^2.0"
},
"provide": {
"php-http/client-implementation": "1.0",
"psr/http-client-implementation": "1.0"
},
"require-dev": {
"ext-curl": "*",
"guzzlehttp/ringphp": "^1.1",
"php-http/client-integration-tests": "^2.0",
"phpunit/phpunit": "^6.0 || ^7.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0-dev"
}
},
"autoload": {
"psr-4": {
"Http\\Adapter\\Guzzle5\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Eric GELOEN",
"email": "geloen.eric@gmail.com"
},
{
"name": "Márk Sági-Kazár",
"email": "mark.sagikazar@gmail.com"
}
],
"description": "Guzzle 5 HTTP Adapter",
"homepage": "http://httplug.io",
"keywords": [
"Guzzle",
"http"
],
"support": {
"issues": "https://github.com/php-http/guzzle5-adapter/issues",
"source": "https://github.com/php-http/guzzle5-adapter/tree/2.0.0"
},
"abandoned": "php-http/guzzle7-adapter",
"time": "2019-02-05T12:28:45+00:00"
},
{
"name": "php-http/httplug",
"version": "2.4.1",
@ -6636,169 +6400,6 @@
},
"time": "2024-09-23T11:39:58+00:00"
},
{
"name": "php-http/httplug-bundle",
"version": "1.34.3",
"source": {
"type": "git",
"url": "https://github.com/php-http/HttplugBundle.git",
"reference": "87c61d27c025dd9d699a2208a6d06be58061e433"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-http/HttplugBundle/zipball/87c61d27c025dd9d699a2208a6d06be58061e433",
"reference": "87c61d27c025dd9d699a2208a6d06be58061e433",
"shasum": ""
},
"require": {
"php": "^7.3 || ^8.0",
"php-http/client-common": "^1.9 || ^2.0",
"php-http/client-implementation": "^1.0",
"php-http/discovery": "^1.14",
"php-http/httplug": "^2.0",
"php-http/logger-plugin": "^1.1",
"php-http/message": "^1.13",
"php-http/message-factory": "^1.0.2",
"php-http/stopwatch-plugin": "^1.2",
"psr/http-message": "^1.0 || ^2.0",
"symfony/config": "^4.4 || ^5.0 || ^6.0 || ^7.0",
"symfony/dependency-injection": "^4.4 || ^5.0 || ^6.0 || ^7.0",
"symfony/event-dispatcher": "^4.4 || ^5.0 || ^6.0 || ^7.0",
"symfony/http-kernel": "^4.4 || ^5.0 || ^6.0 || ^7.0",
"symfony/options-resolver": "^4.4 || ^5.0 || ^6.0 || ^7.0"
},
"conflict": {
"php-http/cache-plugin": "<1.7.0",
"php-http/curl-client": "<2.0",
"php-http/guzzle6-adapter": "<1.1",
"php-http/socket-client": "<2.0",
"php-http/throttle-plugin": "<1.1"
},
"require-dev": {
"guzzlehttp/psr7": "^1.7 || ^2.0",
"matthiasnoback/symfony-config-test": "^4.3 || ^5.0",
"matthiasnoback/symfony-dependency-injection-test": "^4.3.1 || ^5.0",
"nyholm/nsa": "^1.1",
"nyholm/psr7": "^1.2.1",
"php-http/cache-plugin": "^1.7",
"php-http/mock-client": "^1.2",
"php-http/promise": "^1.0",
"phpunit/phpunit": "^9.6",
"symfony/browser-kit": "^4.4 || ^5.0 || ^6.0 || ^7.0",
"symfony/cache": "^4.4 || ^5.0 || ^6.0 || ^7.0",
"symfony/dom-crawler": "^4.4 || ^5.0 || ^6.0 || ^7.0",
"symfony/framework-bundle": "^4.4 || ^5.0 || ^6.0 || ^7.0",
"symfony/http-foundation": "^4.4.19 || ^5.0 || ^6.0 || ^7.0",
"symfony/stopwatch": "^4.4 || ^5.0 || ^6.0 || ^7.0",
"symfony/twig-bundle": "^4.4 || ^5.0 || ^6.0 || ^7.0",
"symfony/web-profiler-bundle": "^4.4.19 || ^5.0 || ^6.0 || ^7.0",
"twig/twig": "^1.41 || ^2.10 || ^3.0"
},
"suggest": {
"php-http/cache-plugin": "To configure clients that cache responses",
"php-http/mock-client": "Add this to your require-dev section to mock HTTP responses easily",
"twig/twig": "Add this to your require-dev section when using the WebProfilerBundle"
},
"type": "symfony-bundle",
"autoload": {
"psr-4": {
"Http\\HttplugBundle\\": "src/"
},
"exclude-from-classmap": [
"/Tests/Resources/MyPsr18TestClient.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "David Buchmann",
"email": "mail@davidbu.ch"
},
{
"name": "Tobias Nyholm",
"email": "tobias.nyholm@gmail.com"
}
],
"description": "Symfony integration for HTTPlug",
"homepage": "http://httplug.io",
"keywords": [
"adapter",
"bundle",
"discovery",
"factory",
"http",
"httplug",
"message",
"php-http"
],
"support": {
"issues": "https://github.com/php-http/HttplugBundle/issues",
"source": "https://github.com/php-http/HttplugBundle/tree/1.34.3"
},
"time": "2024-09-01T08:25:40+00:00"
},
{
"name": "php-http/logger-plugin",
"version": "1.3.1",
"source": {
"type": "git",
"url": "https://github.com/php-http/logger-plugin.git",
"reference": "bf47eb5cb379962d276c94da14861669c2313563"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-http/logger-plugin/zipball/bf47eb5cb379962d276c94da14861669c2313563",
"reference": "bf47eb5cb379962d276c94da14861669c2313563",
"shasum": ""
},
"require": {
"php": "^7.0 || ^8.0",
"php-http/client-common": "^1.9 || ^2.0",
"php-http/message": "^1.0",
"psr/log": "^1.0 || ^2 || ^3",
"symfony/polyfill-php73": "^1.17"
},
"require-dev": {
"phpspec/phpspec": "^5.1 || ^6.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.2-dev"
}
},
"autoload": {
"psr-4": {
"Http\\Client\\Common\\Plugin\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Márk Sági-Kazár",
"email": "mark.sagikazar@gmail.com"
}
],
"description": "PSR-3 Logger plugin for HTTPlug",
"homepage": "http://httplug.io",
"keywords": [
"http",
"httplug",
"logger",
"plugin"
],
"support": {
"issues": "https://github.com/php-http/logger-plugin/issues",
"source": "https://github.com/php-http/logger-plugin/tree/1.3.1"
},
"time": "2024-09-01T06:51:51+00:00"
},
{
"name": "php-http/message",
"version": "1.16.2",
@ -6975,64 +6576,6 @@
},
"time": "2024-03-15T13:55:21+00:00"
},
{
"name": "php-http/stopwatch-plugin",
"version": "1.4.2",
"source": {
"type": "git",
"url": "https://github.com/php-http/stopwatch-plugin.git",
"reference": "11862cfbc719afade4ff407964ab3fbfe9ec2baa"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-http/stopwatch-plugin/zipball/11862cfbc719afade4ff407964ab3fbfe9ec2baa",
"reference": "11862cfbc719afade4ff407964ab3fbfe9ec2baa",
"shasum": ""
},
"require": {
"php": "^7.3 || ^8.0",
"php-http/client-common": "^1.9 || ^2.0",
"symfony/stopwatch": "^3.4 || ^4.0 || ^5.0 || ^6.0 || ^7.0"
},
"require-dev": {
"guzzlehttp/psr7": "^2.1",
"symfony/phpunit-bridge": "^6.4.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.3-dev"
}
},
"autoload": {
"psr-4": {
"Http\\Client\\Common\\Plugin\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Márk Sági-Kazár",
"email": "mark.sagikazar@gmail.com"
}
],
"description": "Symfony Stopwatch plugin for HTTPlug",
"homepage": "http://httplug.io",
"keywords": [
"http",
"httplug",
"plugin",
"stopwatch"
],
"support": {
"issues": "https://github.com/php-http/stopwatch-plugin/issues",
"source": "https://github.com/php-http/stopwatch-plugin/tree/1.4.2"
},
"time": "2023-12-05T14:36:57+00:00"
},
{
"name": "phpdocumentor/reflection-common",
"version": "2.2.0",
@ -8126,78 +7669,6 @@
},
"time": "2019-03-08T08:55:37+00:00"
},
{
"name": "react/promise",
"version": "v2.11.0",
"source": {
"type": "git",
"url": "https://github.com/reactphp/promise.git",
"reference": "1a8460931ea36dc5c76838fec5734d55c88c6831"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/reactphp/promise/zipball/1a8460931ea36dc5c76838fec5734d55c88c6831",
"reference": "1a8460931ea36dc5c76838fec5734d55c88c6831",
"shasum": ""
},
"require": {
"php": ">=5.4.0"
},
"require-dev": {
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36"
},
"type": "library",
"autoload": {
"files": [
"src/functions_include.php"
],
"psr-4": {
"React\\Promise\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jan Sorgalla",
"email": "jsorgalla@gmail.com",
"homepage": "https://sorgalla.com/"
},
{
"name": "Christian Lück",
"email": "christian@clue.engineering",
"homepage": "https://clue.engineering/"
},
{
"name": "Cees-Jan Kiewiet",
"email": "reactphp@ceesjankiewiet.nl",
"homepage": "https://wyrihaximus.net/"
},
{
"name": "Chris Boden",
"email": "cboden@gmail.com",
"homepage": "https://cboden.dev/"
}
],
"description": "A lightweight implementation of CommonJS Promises/A for PHP",
"keywords": [
"promise",
"promises"
],
"support": {
"issues": "https://github.com/reactphp/promise/issues",
"source": "https://github.com/reactphp/promise/tree/v2.11.0"
},
"funding": [
{
"url": "https://opencollective.com/reactphp",
"type": "open_collective"
}
],
"time": "2023-11-16T16:16:50+00:00"
},
{
"name": "rulerz-php/doctrine-orm",
"version": "dev-master",
@ -9325,6 +8796,78 @@
],
"time": "2024-10-22T13:05:35+00:00"
},
{
"name": "symfony/browser-kit",
"version": "v5.4.45",
"source": {
"type": "git",
"url": "https://github.com/symfony/browser-kit.git",
"reference": "03cce39764429e07fbab9b989a1182a24578341d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/browser-kit/zipball/03cce39764429e07fbab9b989a1182a24578341d",
"reference": "03cce39764429e07fbab9b989a1182a24578341d",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"symfony/dom-crawler": "^4.4|^5.0|^6.0",
"symfony/polyfill-php80": "^1.16"
},
"require-dev": {
"symfony/css-selector": "^4.4|^5.0|^6.0",
"symfony/http-client": "^4.4|^5.0|^6.0",
"symfony/mime": "^4.4|^5.0|^6.0",
"symfony/process": "^4.4|^5.0|^6.0"
},
"suggest": {
"symfony/process": ""
},
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\BrowserKit\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/browser-kit/tree/v5.4.45"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-10-22T13:05:35+00:00"
},
{
"name": "symfony/cache",
"version": "v5.4.46",
@ -18533,6 +18076,78 @@
],
"time": "2023-11-13T13:48:05+00:00"
},
{
"name": "react/promise",
"version": "v2.11.0",
"source": {
"type": "git",
"url": "https://github.com/reactphp/promise.git",
"reference": "1a8460931ea36dc5c76838fec5734d55c88c6831"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/reactphp/promise/zipball/1a8460931ea36dc5c76838fec5734d55c88c6831",
"reference": "1a8460931ea36dc5c76838fec5734d55c88c6831",
"shasum": ""
},
"require": {
"php": ">=5.4.0"
},
"require-dev": {
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36"
},
"type": "library",
"autoload": {
"files": [
"src/functions_include.php"
],
"psr-4": {
"React\\Promise\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jan Sorgalla",
"email": "jsorgalla@gmail.com",
"homepage": "https://sorgalla.com/"
},
{
"name": "Christian Lück",
"email": "christian@clue.engineering",
"homepage": "https://clue.engineering/"
},
{
"name": "Cees-Jan Kiewiet",
"email": "reactphp@ceesjankiewiet.nl",
"homepage": "https://wyrihaximus.net/"
},
{
"name": "Chris Boden",
"email": "cboden@gmail.com",
"homepage": "https://cboden.dev/"
}
],
"description": "A lightweight implementation of CommonJS Promises/A for PHP",
"keywords": [
"promise",
"promises"
],
"support": {
"issues": "https://github.com/reactphp/promise/issues",
"source": "https://github.com/reactphp/promise/tree/v2.11.0"
},
"funding": [
{
"url": "https://opencollective.com/reactphp",
"type": "open_collective"
}
],
"time": "2023-11-16T16:16:50+00:00"
},
{
"name": "react/socket",
"version": "v1.16.0",
@ -19720,78 +19335,6 @@
},
"time": "2024-12-30T12:31:04+00:00"
},
{
"name": "symfony/browser-kit",
"version": "v5.4.45",
"source": {
"type": "git",
"url": "https://github.com/symfony/browser-kit.git",
"reference": "03cce39764429e07fbab9b989a1182a24578341d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/browser-kit/zipball/03cce39764429e07fbab9b989a1182a24578341d",
"reference": "03cce39764429e07fbab9b989a1182a24578341d",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"symfony/dom-crawler": "^4.4|^5.0|^6.0",
"symfony/polyfill-php80": "^1.16"
},
"require-dev": {
"symfony/css-selector": "^4.4|^5.0|^6.0",
"symfony/http-client": "^4.4|^5.0|^6.0",
"symfony/mime": "^4.4|^5.0|^6.0",
"symfony/process": "^4.4|^5.0|^6.0"
},
"suggest": {
"symfony/process": ""
},
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\BrowserKit\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/browser-kit/tree/v5.4.45"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-10-22T13:05:35+00:00"
},
{
"name": "symfony/css-selector",
"version": "v5.4.45",
@ -20407,9 +19950,9 @@
"ext-tokenizer": "*",
"ext-xml": "*"
},
"platform-dev": [],
"platform-dev": {},
"platform-overrides": {
"php": "7.4.29"
},
"plugin-api-version": "2.3.0"
"plugin-api-version": "2.6.0"
}

View file

@ -1,121 +0,0 @@
<?php
namespace Wallabag\Guzzle;
use GuzzleHttp\Event\BeforeEvent;
use GuzzleHttp\Event\CompleteEvent;
use GuzzleHttp\Event\SubscriberInterface;
use GuzzleHttp\Message\RequestInterface;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Wallabag\SiteConfig\LoginFormAuthenticator;
use Wallabag\SiteConfig\SiteConfig;
use Wallabag\SiteConfig\SiteConfigBuilder;
class AuthenticatorSubscriber implements SubscriberInterface, LoggerAwareInterface
{
// avoid loop when login failed which can just be a bad login/password
// after 2 attempts, we skip the login
public const MAX_RETRIES = 2;
private int $retries = 0;
/** @var SiteConfigBuilder */
private $configBuilder;
/** @var LoginFormAuthenticator */
private $authenticator;
/** @var LoggerInterface */
private $logger;
/**
* AuthenticatorSubscriber constructor.
*/
public function __construct(SiteConfigBuilder $configBuilder, LoginFormAuthenticator $authenticator)
{
$this->configBuilder = $configBuilder;
$this->authenticator = $authenticator;
$this->logger = new NullLogger();
}
public function setLogger(LoggerInterface $logger): void
{
$this->logger = $logger;
}
public function getEvents(): array
{
return [
'before' => ['loginIfRequired'],
'complete' => ['loginIfRequested'],
];
}
public function loginIfRequired(BeforeEvent $event)
{
$config = $this->buildSiteConfig($event->getRequest());
if (false === $config || !$config->requiresLogin()) {
$this->logger->debug('loginIfRequired> will not require login');
return;
}
$client = $event->getClient();
if (!$this->authenticator->isLoggedIn($config, $client)) {
$this->logger->debug('loginIfRequired> user is not logged in, attach authenticator');
$emitter = $client->getEmitter();
$emitter->detach($this);
$this->authenticator->login($config, $client);
$emitter->attach($this);
}
}
public function loginIfRequested(CompleteEvent $event)
{
$config = $this->buildSiteConfig($event->getRequest());
if (false === $config || !$config->requiresLogin()) {
$this->logger->debug('loginIfRequested> will not require login');
return;
}
$body = $event->getResponse()->getBody();
if (
null === $body
|| '' === $body->getContents()
) {
$this->logger->debug('loginIfRequested> empty body, ignoring');
return;
}
$isLoginRequired = $this->authenticator->isLoginRequired($config, $body);
$this->logger->debug('loginIfRequested> retry #' . $this->retries . ' with login ' . ($isLoginRequired ? '' : 'not ') . 'required');
if ($isLoginRequired && $this->retries < self::MAX_RETRIES) {
$client = $event->getClient();
$emitter = $client->getEmitter();
$emitter->detach($this);
$this->authenticator->login($config, $client);
$emitter->attach($this);
$event->retry();
++$this->retries;
}
}
/**
* @return SiteConfig|false
*/
private function buildSiteConfig(RequestInterface $request)
{
return $this->configBuilder->buildForHost($request->getHost());
}
}

View file

@ -1,74 +0,0 @@
<?php
namespace Wallabag\Helper;
use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\Cookie\CookieJar;
use GuzzleHttp\Event\SubscriberInterface;
use Http\Adapter\Guzzle5\Client as GuzzleAdapter;
use Http\Client\HttpClient;
use Http\HttplugBundle\ClientFactory\ClientFactory;
use Psr\Log\LoggerInterface;
/**
* Builds and configures the HTTP client.
*/
class HttpClientFactory implements ClientFactory
{
/** @var SubscriberInterface[] */
private $subscribers = [];
/** @var CookieJar */
private $cookieJar;
private $restrictedAccess;
private $logger;
/**
* HttpClientFactory constructor.
*
* @param string $restrictedAccess This param is a kind of boolean. Values: 0 or 1
*/
public function __construct(CookieJar $cookieJar, $restrictedAccess, LoggerInterface $logger)
{
$this->cookieJar = $cookieJar;
$this->restrictedAccess = $restrictedAccess;
$this->logger = $logger;
}
/**
* Adds a subscriber to the HTTP client.
*/
public function addSubscriber(SubscriberInterface $subscriber)
{
$this->subscribers[] = $subscriber;
}
/**
* Input an array of configuration to be able to create a HttpClient.
*
* @return HttpClient
*/
public function createClient(array $config = [])
{
$this->logger->log('debug', 'Restricted access config enabled?', ['enabled' => (int) $this->restrictedAccess]);
if (0 === (int) $this->restrictedAccess) {
return new GuzzleAdapter(new GuzzleClient($config));
}
// we clear the cookie to avoid websites who use cookies for analytics
$this->cookieJar->clear();
if (!isset($config['defaults']['cookies'])) {
// need to set the (shared) cookie jar
$config['defaults']['cookies'] = $this->cookieJar;
}
$guzzle = new GuzzleClient($config);
foreach ($this->subscribers as $subscriber) {
$guzzle->getEmitter()->attach($subscriber);
}
return new GuzzleAdapter($guzzle);
}
}

View file

@ -0,0 +1,95 @@
<?php
namespace Wallabag\HttpClient;
use GuzzleHttp\Psr7\Uri;
use Psr\Http\Message\UriInterface;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Symfony\Contracts\HttpClient\ResponseInterface;
use Wallabag\SiteConfig\LoginFormAuthenticator;
use Wallabag\SiteConfig\SiteConfig;
use Wallabag\SiteConfig\SiteConfigBuilder;
class Authenticator implements LoggerAwareInterface
{
/** @var SiteConfigBuilder */
private $configBuilder;
/** @var LoginFormAuthenticator */
private $authenticator;
/** @var LoggerInterface */
private $logger;
public function __construct(SiteConfigBuilder $configBuilder, LoginFormAuthenticator $authenticator)
{
$this->configBuilder = $configBuilder;
$this->authenticator = $authenticator;
$this->logger = new NullLogger();
}
public function setLogger(LoggerInterface $logger): void
{
$this->logger = $logger;
}
public function loginIfRequired(string $url): bool
{
$config = $this->buildSiteConfig(new Uri($url));
if (false === $config || !$config->requiresLogin()) {
$this->logger->debug('loginIfRequired> will not require login');
return false;
}
if ($this->authenticator->isLoggedIn($config)) {
return false;
}
$this->logger->debug('loginIfRequired> user is not logged in, attach authenticator');
$this->authenticator->login($config);
return true;
}
public function loginIfRequested(ResponseInterface $response): bool
{
$config = $this->buildSiteConfig(new Uri($response->getInfo('url')));
if (false === $config || !$config->requiresLogin()) {
$this->logger->debug('loginIfRequested> will not require login');
return false;
}
$body = $response->getContent();
if ('' === $body) {
$this->logger->debug('loginIfRequested> empty body, ignoring');
return false;
}
$isLoginRequired = $this->authenticator->isLoginRequired($config, $body);
$this->logger->debug('loginIfRequested> retry with login ' . ($isLoginRequired ? '' : 'not ') . 'required');
if (!$isLoginRequired) {
return false;
}
$this->authenticator->login($config);
return true;
}
/**
* @return SiteConfig|false
*/
private function buildSiteConfig(UriInterface $uri)
{
return $this->configBuilder->buildForHost($uri->getHost());
}
}

View file

@ -0,0 +1,84 @@
<?php
namespace Wallabag\HttpClient;
use Psr\Log\LoggerInterface;
use Symfony\Component\BrowserKit\HttpBrowser;
use Symfony\Component\HttpClient\HttpClient;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\HttpClient\ResponseInterface;
use Symfony\Contracts\HttpClient\ResponseStreamInterface;
class WallabagClient implements HttpClientInterface
{
private $restrictedAccess;
private HttpClientInterface $httpClient;
private HttpBrowser $browser;
private Authenticator $authenticator;
private LoggerInterface $logger;
public function __construct($restrictedAccess, HttpBrowser $browser, Authenticator $authenticator, LoggerInterface $logger)
{
$this->restrictedAccess = $restrictedAccess;
$this->browser = $browser;
$this->authenticator = $authenticator;
$this->logger = $logger;
$this->httpClient = HttpClient::create([
'timeout' => 10,
]);
}
public function request(string $method, string $url, array $options = []): ResponseInterface
{
$this->logger->log('debug', 'Restricted access config enabled?', ['enabled' => (int) $this->restrictedAccess]);
if (0 === (int) $this->restrictedAccess) {
return $this->httpClient->request($method, $url, $options);
}
$login = $this->authenticator->loginIfRequired($url);
if (!$login) {
return $this->httpClient->request($method, $url, $options);
}
if (null !== $cookieHeader = $this->getCookieHeader($url)) {
$options['headers']['cookie'] = $cookieHeader;
}
$response = $this->httpClient->request($method, $url, $options);
$login = $this->authenticator->loginIfRequested($response);
if (!$login) {
return $response;
}
if (null !== $cookieHeader = $this->getCookieHeader($url)) {
$options['headers']['cookie'] = $cookieHeader;
}
return $this->httpClient->request($method, $url, $options);
}
public function stream($responses, ?float $timeout = null): ResponseStreamInterface
{
return $this->httpClient->stream($responses, $timeout);
}
private function getCookieHeader(string $url): ?string
{
$cookies = [];
foreach ($this->browser->getCookieJar()->allRawValues($url) as $name => $value) {
$cookies[] = $name . '=' . $value;
}
if ([] === $cookies) {
return null;
}
return implode('; ', $cookies);
}
}

View file

@ -2,18 +2,19 @@
namespace Wallabag\SiteConfig;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Cookie\CookieJar;
use Symfony\Component\BrowserKit\HttpBrowser;
use Symfony\Component\DomCrawler\Crawler;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Wallabag\ExpressionLanguage\AuthenticatorProvider;
class LoginFormAuthenticator
{
private HttpBrowser $browser;
private ExpressionLanguage $expressionLanguage;
public function __construct(AuthenticatorProvider $authenticatorProvider)
public function __construct(HttpBrowser $browser, AuthenticatorProvider $authenticatorProvider)
{
$this->browser = $browser;
$this->expressionLanguage = new ExpressionLanguage(null, [$authenticatorProvider]);
}
@ -22,17 +23,14 @@ class LoginFormAuthenticator
*
* @return self
*/
public function login(SiteConfig $siteConfig, ClientInterface $guzzle)
public function login(SiteConfig $siteConfig)
{
$postFields = [
$siteConfig->getUsernameField() => $siteConfig->getUsername(),
$siteConfig->getPasswordField() => $siteConfig->getPassword(),
] + $this->getExtraFields($siteConfig);
$guzzle->post(
$siteConfig->getLoginUri(),
['body' => $postFields, 'allow_redirects' => true, 'verify' => false]
);
$this->browser->request('POST', $siteConfig->getLoginUri(), $postFields);
return $this;
}
@ -42,17 +40,14 @@ class LoginFormAuthenticator
*
* @return bool
*/
public function isLoggedIn(SiteConfig $siteConfig, ClientInterface $guzzle)
public function isLoggedIn(SiteConfig $siteConfig)
{
if (($cookieJar = $guzzle->getDefaultOption('cookies')) instanceof CookieJar) {
/** @var \GuzzleHttp\Cookie\SetCookie $cookie */
foreach ($cookieJar as $cookie) {
foreach ($this->browser->getCookieJar()->all() as $cookie) {
// check required cookies
if ($cookie->getDomain() === $siteConfig->getHost()) {
return true;
}
}
}
return false;
}

View file

@ -1,314 +0,0 @@
<?php
namespace Tests\Wallabag\Guzzle;
use GuzzleHttp\Client;
use GuzzleHttp\Event\BeforeEvent;
use GuzzleHttp\Event\CompleteEvent;
use GuzzleHttp\Message\Request;
use GuzzleHttp\Message\Response;
use GuzzleHttp\Stream\Stream;
use GuzzleHttp\Subscriber\Mock;
use Monolog\Handler\TestHandler;
use Monolog\Logger;
use PHPUnit\Framework\TestCase;
use Wallabag\Guzzle\AuthenticatorSubscriber;
use Wallabag\SiteConfig\ArraySiteConfigBuilder;
use Wallabag\SiteConfig\LoginFormAuthenticator;
class AuthenticatorSubscriberTest extends TestCase
{
public function testGetEvents()
{
$authenticator = $this->getMockBuilder(LoginFormAuthenticator::class)
->disableOriginalConstructor()
->getMock();
$subscriber = new AuthenticatorSubscriber(
new ArraySiteConfigBuilder(),
$authenticator
);
$events = $subscriber->getEvents();
$this->assertArrayHasKey('before', $events);
$this->assertArrayHasKey('complete', $events);
$this->assertSame('loginIfRequired', $events['before'][0]);
$this->assertSame('loginIfRequested', $events['complete'][0]);
}
public function testLoginIfRequiredNotRequired()
{
$authenticator = $this->getMockBuilder(LoginFormAuthenticator::class)
->disableOriginalConstructor()
->getMock();
$builder = new ArraySiteConfigBuilder(['example.com' => []]);
$subscriber = new AuthenticatorSubscriber($builder, $authenticator);
$logger = new Logger('foo');
$handler = new TestHandler();
$logger->pushHandler($handler);
$subscriber->setLogger($logger);
$request = new Request('GET', 'http://www.example.com');
$event = $this->getMockBuilder(BeforeEvent::class)
->disableOriginalConstructor()
->getMock();
$event->expects($this->once())
->method('getRequest')
->willReturn($request);
$subscriber->loginIfRequired($event);
$records = $handler->getRecords();
$this->assertCount(1, $records);
$this->assertSame('loginIfRequired> will not require login', $records[0]['message']);
}
public function testLoginIfRequiredWithNotLoggedInUser()
{
$authenticator = $this->getMockBuilder(LoginFormAuthenticator::class)
->disableOriginalConstructor()
->getMock();
$authenticator->expects($this->once())
->method('isLoggedIn')
->willReturn(false);
$authenticator->expects($this->once())
->method('login');
$builder = new ArraySiteConfigBuilder(['example.com' => ['requiresLogin' => true]]);
$subscriber = new AuthenticatorSubscriber($builder, $authenticator);
$logger = new Logger('foo');
$handler = new TestHandler();
$logger->pushHandler($handler);
$subscriber->setLogger($logger);
$response = new Response(
200,
['content-type' => 'text/html'],
Stream::factory('')
);
$guzzle = new Client();
$guzzle->getEmitter()->attach(new Mock([$response]));
$request = new Request('GET', 'http://www.example.com');
$event = $this->getMockBuilder(BeforeEvent::class)
->disableOriginalConstructor()
->getMock();
$event->expects($this->once())
->method('getRequest')
->willReturn($request);
$event->expects($this->once())
->method('getClient')
->willReturn($guzzle);
$subscriber->loginIfRequired($event);
$records = $handler->getRecords();
$this->assertCount(1, $records);
$this->assertSame('loginIfRequired> user is not logged in, attach authenticator', $records[0]['message']);
}
public function testLoginIfRequestedNotRequired()
{
$authenticator = $this->getMockBuilder(LoginFormAuthenticator::class)
->disableOriginalConstructor()
->getMock();
$builder = new ArraySiteConfigBuilder(['example.com' => []]);
$subscriber = new AuthenticatorSubscriber($builder, $authenticator);
$logger = new Logger('foo');
$handler = new TestHandler();
$logger->pushHandler($handler);
$subscriber->setLogger($logger);
$request = new Request('GET', 'http://www.example.com');
$event = $this->getMockBuilder(CompleteEvent::class)
->disableOriginalConstructor()
->getMock();
$event->expects($this->once())
->method('getRequest')
->willReturn($request);
$subscriber->loginIfRequested($event);
$records = $handler->getRecords();
$this->assertCount(1, $records);
$this->assertSame('loginIfRequested> will not require login', $records[0]['message']);
}
public function testLoginIfRequestedNotRequested()
{
$authenticator = $this->getMockBuilder(LoginFormAuthenticator::class)
->disableOriginalConstructor()
->getMock();
$authenticator->expects($this->once())
->method('isLoginRequired')
->willReturn(false);
$builder = new ArraySiteConfigBuilder(['example.com' => [
'requiresLogin' => true,
'notLoggedInXpath' => '//html',
]]);
$subscriber = new AuthenticatorSubscriber($builder, $authenticator);
$logger = new Logger('foo');
$handler = new TestHandler();
$logger->pushHandler($handler);
$subscriber->setLogger($logger);
$response = new Response(
200,
['content-type' => 'text/html'],
Stream::factory('<html><body/></html>')
);
$request = new Request('GET', 'http://www.example.com');
$event = $this->getMockBuilder(CompleteEvent::class)
->disableOriginalConstructor()
->getMock();
$event->expects($this->once())
->method('getResponse')
->willReturn($response);
$event->expects($this->once())
->method('getRequest')
->willReturn($request);
$subscriber->loginIfRequested($event);
$records = $handler->getRecords();
$this->assertCount(1, $records);
$this->assertSame('loginIfRequested> retry #0 with login not required', $records[0]['message']);
}
public function testLoginIfRequestedRequested()
{
$authenticator = $this->getMockBuilder(LoginFormAuthenticator::class)
->disableOriginalConstructor()
->getMock();
$authenticator->expects($this->once())
->method('isLoginRequired')
->willReturn(true);
$authenticator->expects($this->once())
->method('login');
$builder = new ArraySiteConfigBuilder(['example.com' => [
'requiresLogin' => true,
'notLoggedInXpath' => '//html',
]]);
$subscriber = new AuthenticatorSubscriber($builder, $authenticator);
$logger = new Logger('foo');
$handler = new TestHandler();
$logger->pushHandler($handler);
$subscriber->setLogger($logger);
$response = new Response(
200,
['content-type' => 'text/html'],
Stream::factory('<html><body/></html>')
);
$guzzle = new Client();
$guzzle->getEmitter()->attach(new Mock([$response]));
$request = new Request('GET', 'http://www.example.com');
$event = $this->getMockBuilder(CompleteEvent::class)
->disableOriginalConstructor()
->getMock();
$event->expects($this->once())
->method('getResponse')
->willReturn($response);
$event->expects($this->once())
->method('getRequest')
->willReturn($request);
$event->expects($this->any())
->method('getClient')
->willReturn($guzzle);
$subscriber->loginIfRequested($event);
$records = $handler->getRecords();
$this->assertCount(1, $records);
$this->assertSame('loginIfRequested> retry #0 with login required', $records[0]['message']);
}
public function testLoginIfRequestedRedirect()
{
$authenticator = $this->getMockBuilder(LoginFormAuthenticator::class)
->disableOriginalConstructor()
->getMock();
$builder = new ArraySiteConfigBuilder(['example.com' => [
'requiresLogin' => true,
'notLoggedInXpath' => '//html',
]]);
$subscriber = new AuthenticatorSubscriber($builder, $authenticator);
$logger = new Logger('foo');
$handler = new TestHandler();
$logger->pushHandler($handler);
$subscriber->setLogger($logger);
$response = new Response(
301,
[],
Stream::factory('')
);
$guzzle = new Client();
$guzzle->getEmitter()->attach(new Mock([$response]));
$request = new Request('GET', 'http://www.example.com');
$event = $this->getMockBuilder(CompleteEvent::class)
->disableOriginalConstructor()
->getMock();
$event->expects($this->once())
->method('getResponse')
->willReturn($response);
$event->expects($this->once())
->method('getRequest')
->willReturn($request);
$event->expects($this->any())
->method('getClient')
->willReturn($guzzle);
$subscriber->loginIfRequested($event);
$records = $handler->getRecords();
$this->assertCount(1, $records);
$this->assertSame('loginIfRequested> empty body, ignoring', $records[0]['message']);
}
}

View file

@ -2,13 +2,12 @@
namespace Tests\Wallabag\SiteConfig;
use GuzzleHttp\Client;
use GuzzleHttp\Message\Response;
use GuzzleHttp\Stream\Stream;
use GuzzleHttp\Subscriber\Mock;
use PHPUnit\Framework\TestCase;
use Symfony\Component\BrowserKit\HttpBrowser;
use Symfony\Component\HttpClient\MockHttpClient;
use Symfony\Component\HttpClient\Response\MockResponse;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\HttpClient\ResponseInterface;
use Wallabag\ExpressionLanguage\AuthenticatorProvider;
use Wallabag\SiteConfig\LoginFormAuthenticator;
use Wallabag\SiteConfig\SiteConfig;
@ -17,16 +16,6 @@ class LoginFormAuthenticatorTest extends TestCase
{
public function testLoginPost()
{
$response = new Response(
200,
['content-type' => 'text/html'],
Stream::factory('')
);
$guzzle = new Client();
$guzzle->getEmitter()->attach(new Mock([$response]));
$mockHttpClient = new MockHttpClient([new MockResponse('', ['http_code' => 200, 'response_headers' => ['content-type' => 'text/html']])]);
$siteConfig = new SiteConfig([
'host' => 'example.com',
'loginUri' => 'http://example.com/login',
@ -40,25 +29,23 @@ class LoginFormAuthenticatorTest extends TestCase
'password' => 'unkn0wn',
]);
$authenticatorProvider = new AuthenticatorProvider($mockHttpClient);
$auth = new LoginFormAuthenticator($authenticatorProvider);
$res = $auth->login($siteConfig, $guzzle);
$browserResponse = new MockResponse('<html></html>', ['http_code' => 200, 'response_headers' => ['content-type' => 'text/html']]);
$browserClient = new MockHttpClient([$browserResponse]);
$browser = new HttpBrowser($browserClient);
$requestHtmlFunctionResponse = new MockResponse('<html></html>', ['http_code' => 200, 'response_headers' => ['content-type' => 'text/html']]);
$requestHtmlFunctionClient = new MockHttpClient([$requestHtmlFunctionResponse]);
$authenticatorProvider = new AuthenticatorProvider($requestHtmlFunctionClient);
$auth = new LoginFormAuthenticator($browser, $authenticatorProvider);
$res = $auth->login($siteConfig);
$this->assertInstanceOf(LoginFormAuthenticator::class, $res);
}
public function testLoginPostWithExtraFieldsButEmptyHtml()
{
$response = new Response(
200,
['content-type' => 'text/html'],
Stream::factory('<html></html>')
);
$guzzle = new Client();
$guzzle->getEmitter()->attach(new Mock([$response, $response]));
$mockHttpClient = new MockHttpClient([new MockResponse('<html></html>', ['http_code' => 200, 'response_headers' => ['content-type' => 'text/html']])]);
$siteConfig = new SiteConfig([
'host' => 'example.com',
'loginUri' => 'http://example.com/login',
@ -73,9 +60,17 @@ class LoginFormAuthenticatorTest extends TestCase
'password' => 'unkn0wn',
]);
$authenticatorProvider = new AuthenticatorProvider($mockHttpClient);
$auth = new LoginFormAuthenticator($authenticatorProvider);
$res = $auth->login($siteConfig, $guzzle);
$browserResponse = new MockResponse('<html></html>', ['http_code' => 200, 'response_headers' => ['content-type' => 'text/html']]);
$browserClient = new MockHttpClient([$browserResponse]);
$browser = new HttpBrowser($browserClient);
$requestHtmlFunctionResponse = new MockResponse('<html></html>', ['http_code' => 200, 'response_headers' => ['content-type' => 'text/html']]);
$requestHtmlFunctionClient = new MockHttpClient([$requestHtmlFunctionResponse]);
$authenticatorProvider = new AuthenticatorProvider($requestHtmlFunctionClient);
$auth = new LoginFormAuthenticator($browser, $authenticatorProvider);
$res = $auth->login($siteConfig);
$this->assertInstanceOf(LoginFormAuthenticator::class, $res);
}
@ -83,49 +78,6 @@ class LoginFormAuthenticatorTest extends TestCase
// testing preg_match
public function testLoginPostWithExtraFieldsWithRegex()
{
$response = $this->getMockBuilder(Response::class)
->disableOriginalConstructor()
->getMock();
$response->expects($this->any())
->method('getBody')
->willReturn(file_get_contents(__DIR__ . '/../fixtures/aoc.media.html'));
$response->expects($this->any())
->method('getStatusCode')
->willReturn(200);
$mockHttpClient = new MockHttpClient([new MockResponse(file_get_contents(__DIR__ . '/../fixtures/aoc.media.html'), ['http_code' => 200, 'response_headers' => ['content-type' => 'text/html']])]);
$client = $this->getMockBuilder(Client::class)
->disableOriginalConstructor()
->getMock();
$client->expects($this->any())
->method('post')
->with(
$this->equalTo('https://aoc.media/wp-admin/admin-ajax.php'),
$this->equalTo([
'body' => [
'nom' => 'johndoe',
'password' => 'unkn0wn',
'security' => 'c506c1b8bc',
'action' => 'login_user',
],
'allow_redirects' => true,
'verify' => false,
])
)
->willReturn($response);
$client->expects($this->any())
->method('get')
->with(
$this->equalTo('https://aoc.media/'),
$this->equalTo([])
)
->willReturn($response);
$siteConfig = new SiteConfig([
'host' => 'aoc.media',
'loginUri' => 'https://aoc.media/wp-admin/admin-ajax.php',
@ -139,78 +91,44 @@ class LoginFormAuthenticatorTest extends TestCase
'password' => 'unkn0wn',
]);
$authenticatorProvider = new AuthenticatorProvider($mockHttpClient);
$auth = new LoginFormAuthenticator($authenticatorProvider);
$res = $auth->login($siteConfig, $client);
$this->assertInstanceOf(LoginFormAuthenticator::class, $res);
}
public function testLoginPostWithExtraFieldsWithData()
{
$response = $this->getMockBuilder(Response::class)
->disableOriginalConstructor()
$browserResponse = new MockResponse('<html></html>', ['http_code' => 200, 'response_headers' => ['content-type' => 'text/html']]);
$browserClient = new MockHttpClient([$browserResponse]);
$browser = $this->getMockBuilder(HttpBrowser::class)
->setConstructorArgs([$browserClient])
->getMock();
$response->expects($this->any())
->method('getBody')
->willReturn(file_get_contents(__DIR__ . '/../fixtures/nextinpact-login.html'));
$response->expects($this->any())
->method('getStatusCode')
->willReturn(200);
$mockHttpClient = new MockHttpClient([new MockResponse(file_get_contents(__DIR__ . '/../fixtures/nextinpact-login.html'), ['http_code' => 200, 'response_headers' => ['content-type' => 'text/html']])]);
$client = $this->getMockBuilder(Client::class)
->disableOriginalConstructor()
->getMock();
$client->expects($this->any())
->method('post')
$browser->expects($this->any())
->method('request')
->with(
$this->equalTo('https://compte.nextinpact.com/Account/Login'),
$this->equalTo('POST'),
$this->equalTo('https://aoc.media/wp-admin/admin-ajax.php'),
$this->equalTo([
'body' => [
'UserName' => 'johndoe',
'Password' => 'unkn0wn',
'__RequestVerificationToken' => 's6x2QcnQDUL92mkKSi_JuUBXcgUYx_Plf-KyQ2eJypKAjQZIeTvaFHOsfEdTrcSXt3dt2CW39V7r9V16LUtvjszodAU1',
'returnUrl' => 'https://www.nextinpact.com/news/102835-pour-cour-comptes-fonctionnement-actuel-vote-par-internet-nest-pas-satisfaisant.htm',
],
'allow_redirects' => true,
'verify' => false,
])
)
->willReturn($response);
$client->expects($this->any())
->method('get')
->with(
$this->equalTo('https://compte.nextinpact.com/Account/Login?http://www.nextinpact.com/'),
$this->equalTo([
'headers' => [
'X-Requested-With' => 'XMLHttpRequest',
],
])
)
->willReturn($response);
$siteConfig = new SiteConfig([
'host' => 'nextinpact.com',
'loginUri' => 'https://compte.nextinpact.com/Account/Login',
'usernameField' => 'UserName',
'passwordField' => 'Password',
'extraFields' => [
'__RequestVerificationToken' => '@=xpath(\'//form[@action="/Account/Login"]/input[@name="__RequestVerificationToken"]\', request_html(\'https://compte.nextinpact.com/Account/Login?http://www.nextinpact.com/\', {\'headers\': {\'X-Requested-With\':\'XMLHttpRequest\'}}))',
'returnUrl' => 'https://www.nextinpact.com/news/102835-pour-cour-comptes-fonctionnement-actuel-vote-par-internet-nest-pas-satisfaisant.htm',
],
'username' => 'johndoe',
'nom' => 'johndoe',
'password' => 'unkn0wn',
]);
'security' => 'c506c1b8bc',
'action' => 'login_user',
])
)
;
$authenticatorProvider = new AuthenticatorProvider($mockHttpClient);
$auth = new LoginFormAuthenticator($authenticatorProvider);
$res = $auth->login($siteConfig, $client);
$requestHtmlFunctionResponse = $this->getMockBuilder(ResponseInterface::class)->getMock();
$requestHtmlFunctionResponse->expects($this->any())
->method('getContent')
->willReturn(file_get_contents(__DIR__ . '/../fixtures/aoc.media.html'))
;
$requestHtmlFunctionClient = $this->getMockBuilder(HttpClientInterface::class)->getMock();
$requestHtmlFunctionClient->expects($this->any())
->method('request')
->with(
$this->equalTo('GET'),
$this->equalTo('https://aoc.media/'),
)
->willReturn($requestHtmlFunctionResponse)
;
$authenticatorProvider = new AuthenticatorProvider($requestHtmlFunctionClient);
$auth = new LoginFormAuthenticator($browser, $authenticatorProvider);
$res = $auth->login($siteConfig);
$this->assertInstanceOf(LoginFormAuthenticator::class, $res);
}
@ -225,10 +143,16 @@ class LoginFormAuthenticatorTest extends TestCase
'password' => 'unkn0wn',
]);
$mockHttpClient = new MockHttpClient();
$browserResponse = new MockResponse('<html></html>', ['http_code' => 200, 'response_headers' => ['content-type' => 'text/html']]);
$browserClient = new MockHttpClient([$browserResponse]);
$browser = new HttpBrowser($browserClient);
$requestHtmlFunctionResponse = new MockResponse('<html></html>', ['http_code' => 200, 'response_headers' => ['content-type' => 'text/html']]);
$requestHtmlFunctionClient = new MockHttpClient([$requestHtmlFunctionResponse]);
$authenticatorProvider = new AuthenticatorProvider($requestHtmlFunctionClient);
$auth = new LoginFormAuthenticator($browser, $authenticatorProvider);
$authenticatorProvider = new AuthenticatorProvider($mockHttpClient);
$auth = new LoginFormAuthenticator($authenticatorProvider);
$loginRequired = $auth->isLoginRequired($siteConfig, file_get_contents(__DIR__ . '/../fixtures/nextinpact-login.html'));
$this->assertFalse($loginRequired);
@ -245,10 +169,16 @@ class LoginFormAuthenticatorTest extends TestCase
'notLoggedInXpath' => '//h2[@class="title_reserve_article"]',
]);
$mockHttpClient = new MockHttpClient();
$browserResponse = new MockResponse('<html></html>', ['http_code' => 200, 'response_headers' => ['content-type' => 'text/html']]);
$browserClient = new MockHttpClient([$browserResponse]);
$browser = new HttpBrowser($browserClient);
$requestHtmlFunctionResponse = new MockResponse('<html></html>', ['http_code' => 200, 'response_headers' => ['content-type' => 'text/html']]);
$requestHtmlFunctionClient = new MockHttpClient([$requestHtmlFunctionResponse]);
$authenticatorProvider = new AuthenticatorProvider($requestHtmlFunctionClient);
$auth = new LoginFormAuthenticator($browser, $authenticatorProvider);
$authenticatorProvider = new AuthenticatorProvider($mockHttpClient);
$auth = new LoginFormAuthenticator($authenticatorProvider);
$loginRequired = $auth->isLoginRequired($siteConfig, file_get_contents(__DIR__ . '/../fixtures/nextinpact-article.html'));
$this->assertTrue($loginRequired);

View file

@ -0,0 +1,239 @@
<?php
namespace Tests\Wallabag\HttpClient;
use Monolog\Handler\TestHandler;
use Monolog\Logger;
use PHPUnit\Framework\TestCase;
use Symfony\Contracts\HttpClient\ResponseInterface;
use Wallabag\HttpClient\Authenticator;
use Wallabag\SiteConfig\ArraySiteConfigBuilder;
use Wallabag\SiteConfig\LoginFormAuthenticator;
class AuthenticatorTest extends TestCase
{
public function testLoginIfRequiredNotRequired()
{
$authenticator = $this->getMockBuilder(LoginFormAuthenticator::class)
->disableOriginalConstructor()
->getMock();
$builder = new ArraySiteConfigBuilder(['example.com' => []]);
$subscriber = new Authenticator($builder, $authenticator);
$logger = new Logger('foo');
$handler = new TestHandler();
$logger->pushHandler($handler);
$subscriber->setLogger($logger);
$login = $subscriber->loginIfRequired('http://www.example.com');
$this->assertFalse($login);
$records = $handler->getRecords();
$this->assertCount(1, $records);
$this->assertSame('loginIfRequired> will not require login', $records[0]['message']);
}
public function testLoginIfRequiredWithNotLoggedInUser()
{
$authenticator = $this->getMockBuilder(LoginFormAuthenticator::class)
->disableOriginalConstructor()
->getMock();
$authenticator->expects($this->once())
->method('isLoggedIn')
->willReturn(false);
$authenticator->expects($this->once())
->method('login');
$builder = new ArraySiteConfigBuilder(['example.com' => ['requiresLogin' => true]]);
$subscriber = new Authenticator($builder, $authenticator);
$logger = new Logger('foo');
$handler = new TestHandler();
$logger->pushHandler($handler);
$subscriber->setLogger($logger);
$login = $subscriber->loginIfRequired('http://www.example.com');
$this->assertTrue($login);
$records = $handler->getRecords();
$this->assertCount(1, $records);
$this->assertSame('loginIfRequired> user is not logged in, attach authenticator', $records[0]['message']);
}
public function testLoginIfRequestedNotRequired()
{
$authenticator = $this->getMockBuilder(LoginFormAuthenticator::class)
->disableOriginalConstructor()
->getMock();
$builder = new ArraySiteConfigBuilder(['example.com' => []]);
$subscriber = new Authenticator($builder, $authenticator);
$logger = new Logger('foo');
$handler = new TestHandler();
$logger->pushHandler($handler);
$subscriber->setLogger($logger);
$response = $this->getMockBuilder(ResponseInterface::class)
->disableOriginalConstructor()
->getMock();
$response->expects($this->once())
->method('getInfo')
->with($this->equalTo('url'))
->willReturn('http://www.example.com');
$login = $subscriber->loginIfRequested($response);
$this->assertFalse($login);
$records = $handler->getRecords();
$this->assertCount(1, $records);
$this->assertSame('loginIfRequested> will not require login', $records[0]['message']);
}
public function testLoginIfRequestedNotRequested()
{
$authenticator = $this->getMockBuilder(LoginFormAuthenticator::class)
->disableOriginalConstructor()
->getMock();
$authenticator->expects($this->once())
->method('isLoginRequired')
->willReturn(false);
$builder = new ArraySiteConfigBuilder(['example.com' => [
'requiresLogin' => true,
'notLoggedInXpath' => '//html',
]]);
$subscriber = new Authenticator($builder, $authenticator);
$logger = new Logger('foo');
$handler = new TestHandler();
$logger->pushHandler($handler);
$subscriber->setLogger($logger);
$response = $this->getMockBuilder(ResponseInterface::class)
->disableOriginalConstructor()
->getMock();
$response->expects($this->once())
->method('getInfo')
->with($this->equalTo('url'))
->willReturn('http://www.example.com');
$response->expects($this->once())
->method('getContent')
->willReturn('<html><body/></html>');
$login = $subscriber->loginIfRequested($response);
$this->assertFalse($login);
$records = $handler->getRecords();
$this->assertCount(1, $records);
$this->assertSame('loginIfRequested> retry with login not required', $records[0]['message']);
}
public function testLoginIfRequestedRequested()
{
$authenticator = $this->getMockBuilder(LoginFormAuthenticator::class)
->disableOriginalConstructor()
->getMock();
$authenticator->expects($this->once())
->method('isLoginRequired')
->willReturn(true);
$authenticator->expects($this->once())
->method('login');
$builder = new ArraySiteConfigBuilder(['example.com' => [
'requiresLogin' => true,
'notLoggedInXpath' => '//html',
]]);
$subscriber = new Authenticator($builder, $authenticator);
$logger = new Logger('foo');
$handler = new TestHandler();
$logger->pushHandler($handler);
$subscriber->setLogger($logger);
$response = $this->getMockBuilder(ResponseInterface::class)
->disableOriginalConstructor()
->getMock();
$response->expects($this->once())
->method('getInfo')
->with($this->equalTo('url'))
->willReturn('http://www.example.com');
$response->expects($this->once())
->method('getContent')
->willReturn('<html><body/></html>');
$login = $subscriber->loginIfRequested($response);
$this->assertTrue($login);
$records = $handler->getRecords();
$this->assertCount(1, $records);
$this->assertSame('loginIfRequested> retry with login required', $records[0]['message']);
}
public function testLoginIfRequestedRedirect()
{
$authenticator = $this->getMockBuilder(LoginFormAuthenticator::class)
->disableOriginalConstructor()
->getMock();
$builder = new ArraySiteConfigBuilder(['example.com' => [
'requiresLogin' => true,
'notLoggedInXpath' => '//html',
]]);
$subscriber = new Authenticator($builder, $authenticator);
$logger = new Logger('foo');
$handler = new TestHandler();
$logger->pushHandler($handler);
$subscriber->setLogger($logger);
$response = $this->getMockBuilder(ResponseInterface::class)
->disableOriginalConstructor()
->getMock();
$response->expects($this->once())
->method('getInfo')
->with($this->equalTo('url'))
->willReturn('http://www.example.com');
$response->expects($this->once())
->method('getContent')
->willReturn('');
$login = $subscriber->loginIfRequested($response);
$this->assertFalse($login);
$records = $handler->getRecords();
$this->assertCount(1, $records);
$this->assertSame('loginIfRequested> empty body, ignoring', $records[0]['message']);
}
}