From fa2bb932e6bf42697ddb63d1df0a5978c51d2ebd Mon Sep 17 00:00:00 2001 From: Chris Mayo Date: Mon, 13 Nov 2023 19:22:12 +0000 Subject: [PATCH 1/8] Check config file has sections RawConfigParser does not raise an error if the file is empty. --- linkcheck/configuration/confparse.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/linkcheck/configuration/confparse.py b/linkcheck/configuration/confparse.py index 9d9ff875..45b6026d 100644 --- a/linkcheck/configuration/confparse.py +++ b/linkcheck/configuration/confparse.py @@ -57,6 +57,9 @@ class LCConfigParser(RawConfigParser): assert isinstance(files, list), "Invalid file list %r" % files try: self.read_ok = super().read(files) + if not self.sections(): + raise LinkCheckerError( + _("configuration files %s contain no sections.") % files) if len(self.read_ok) < len(files): failed_files = set(files) - set(self.read_ok) log.warn( From db30833511a65a9eb5131612538cb76d9b6cdac9 Mon Sep 17 00:00:00 2001 From: Chris Mayo Date: Mon, 13 Nov 2023 19:22:12 +0000 Subject: [PATCH 2/8] Fix setting config["cookiefile"] regardless of check config["cookiefile"] is set correctly later on in setup_config(). Not working since check added in: 7b34be590 ("Introduce check plugins, use Python requests for http/s connections, and some code cleanups and improvements.", 2014-03-01) --- linkcheck/command/setup_config.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/linkcheck/command/setup_config.py b/linkcheck/command/setup_config.py index a85bd310..3f7a1210 100644 --- a/linkcheck/command/setup_config.py +++ b/linkcheck/command/setup_config.py @@ -187,8 +187,6 @@ def setup_config(config, options): if options.verbose: config["verbose"] = True config["warnings"] = True - if options.cookiefile is not None: - config["cookiefile"] = options.cookiefile if constructauth: config.add_auth(pattern=".+", user=_username, password=_password) # read missing passwords From 82a38e6bd053cde2a83526e2ed10e8dd26aaed22 Mon Sep 17 00:00:00 2001 From: Chris Mayo Date: Mon, 13 Nov 2023 19:22:12 +0000 Subject: [PATCH 3/8] Check cookie file exists --- linkcheck/command/setup_config.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/linkcheck/command/setup_config.py b/linkcheck/command/setup_config.py index 3f7a1210..5d7e6a51 100644 --- a/linkcheck/command/setup_config.py +++ b/linkcheck/command/setup_config.py @@ -202,8 +202,11 @@ def setup_config(config, options): if options.useragent is not None: config["useragent"] = options.useragent if options.cookiefile is not None: - if fileutil.is_readable(options.cookiefile): - config["cookiefile"] = options.cookiefile - else: + if not fileutil.is_valid_config_source(options.cookiefile): + log.error( + LOG_CMDLINE, _("Cookie file %s does not exist."), options.cookiefile) + elif not fileutil.is_readable(options.cookiefile): msg = _("Could not read cookie file %s") % options.cookiefile log.error(LOG_CMDLINE, msg) + else: + config["cookiefile"] = options.cookiefile From 2e8cd48f3cbc76c1f1b12e864f2f169a30610628 Mon Sep 17 00:00:00 2001 From: Chris Mayo Date: Mon, 13 Nov 2023 19:22:12 +0000 Subject: [PATCH 4/8] Catch exception if cookie file could not be parsed --- linkcheck/director/aggregator.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/linkcheck/director/aggregator.py b/linkcheck/director/aggregator.py index cace9edf..75f46269 100644 --- a/linkcheck/director/aggregator.py +++ b/linkcheck/director/aggregator.py @@ -45,8 +45,14 @@ def new_request_session(config, cookies): {"User-Agent": config["useragent"]} ) if config["cookiefile"]: - for cookie in from_file(config["cookiefile"]): - session.cookies.set_cookie(cookie) + try: + for cookie in from_file(config["cookiefile"]): + session.cookies.set_cookie(cookie) + except Exception as msg: + log.error( + LOG_CHECK, + _("Could not parse cookie file: %s. %s"), config["cookiefile"], msg + ) return session From eeee80ec4ffc41a6cb4ac5ba1fc267cc6b556259 Mon Sep 17 00:00:00 2001 From: Chris Mayo Date: Mon, 13 Nov 2023 19:22:12 +0000 Subject: [PATCH 5/8] Check cookie file has entries --- linkcheck/cookies.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/linkcheck/cookies.py b/linkcheck/cookies.py index 5e17ef29..b97e8018 100644 --- a/linkcheck/cookies.py +++ b/linkcheck/cookies.py @@ -21,6 +21,8 @@ from http.cookiejar import split_header_words import email import requests +from . import LinkCheckerError + def from_file(filename): """Parse cookie data from a text file in HTTP header format. @@ -40,6 +42,8 @@ def from_file(filename): lines.append(line) if lines: entries.extend(from_headers("\r\n".join(lines))) + if not entries: + raise LinkCheckerError(_("No entries found")) return entries From 7a3be9ba932a5e6c1a478a0cf737f73a1bea54fd Mon Sep 17 00:00:00 2001 From: Chris Mayo Date: Mon, 13 Nov 2023 19:22:12 +0000 Subject: [PATCH 6/8] Avoid FileNotFoundError if FILENAME does not exist File ".../linkcheck/fileutil.py", line 110, in is_valid_config_source return os.path.isfile(filename) or stat.S_ISFIFO(os.stat(filename).st_mode) ^^^^^^^^^^^^^^^^^ FileNotFoundError: [Errno 2] No such file or directory --- linkcheck/fileutil.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/linkcheck/fileutil.py b/linkcheck/fileutil.py index db574500..22572ec1 100644 --- a/linkcheck/fileutil.py +++ b/linkcheck/fileutil.py @@ -107,4 +107,5 @@ def is_writable_by_others(filename): def is_valid_config_source(filename): """Check if the file is a valid config file.""" - return os.path.isfile(filename) or stat.S_ISFIFO(os.stat(filename).st_mode) + return os.path.exists(filename) and ( + os.path.isfile(filename) or stat.S_ISFIFO(os.stat(filename).st_mode)) From 73b099ad4c56825f8b05f1768e54d048db286e52 Mon Sep 17 00:00:00 2001 From: Chris Mayo Date: Mon, 13 Nov 2023 19:22:12 +0000 Subject: [PATCH 7/8] Exit if FILENAME does not exist or is not readable --- linkcheck/command/linkchecker.py | 11 +++++++---- linkcheck/command/setup_config.py | 8 ++++---- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/linkcheck/command/linkchecker.py b/linkcheck/command/linkchecker.py index f4c5ff47..139a7cf8 100644 --- a/linkcheck/command/linkchecker.py +++ b/linkcheck/command/linkchecker.py @@ -135,11 +135,14 @@ def linkchecker(): files = [] if options.configfile: path = configuration.normpath(options.configfile) - if fileutil.is_valid_config_source(path): - files.append(path) + if not fileutil.is_valid_config_source(path): + raise LinkCheckerError( + _("Config file %s does not exist.") % options.configfile) + elif not fileutil.is_readable(path): + raise LinkCheckerError( + _("Could not read config file %s.") % options.configfile) else: - log.warn( - LOG_CMDLINE, _("Unreadable config file: %r"), options.configfile) + files.append(path) config.read(files=files) except LinkCheckerError as msg: # config error diff --git a/linkcheck/command/setup_config.py b/linkcheck/command/setup_config.py index 5d7e6a51..da6c36d1 100644 --- a/linkcheck/command/setup_config.py +++ b/linkcheck/command/setup_config.py @@ -203,10 +203,10 @@ def setup_config(config, options): config["useragent"] = options.useragent if options.cookiefile is not None: if not fileutil.is_valid_config_source(options.cookiefile): - log.error( - LOG_CMDLINE, _("Cookie file %s does not exist."), options.cookiefile) + print_usage( + _("Cookie file %s does not exist.") % options.cookiefile) elif not fileutil.is_readable(options.cookiefile): - msg = _("Could not read cookie file %s") % options.cookiefile - log.error(LOG_CMDLINE, msg) + print_usage( + _("Could not read cookie file %s") % options.cookiefile) else: config["cookiefile"] = options.cookiefile From ac8495cb18d64db7702ea1bcf49ef0ab29597935 Mon Sep 17 00:00:00 2001 From: Chris Mayo Date: Mon, 13 Nov 2023 19:22:12 +0000 Subject: [PATCH 8/8] Add tests for missing and empty FILENAMEs --- tests/configuration/data/config.empty | 0 tests/configuration/test_config.py | 10 ++++++++++ tests/test_cookies.py | 8 ++++++++ tests/test_linkchecker.py | 3 +++ 4 files changed, 21 insertions(+) create mode 100644 tests/configuration/data/config.empty diff --git a/tests/configuration/data/config.empty b/tests/configuration/data/config.empty new file mode 100644 index 00000000..e69de29b diff --git a/tests/configuration/test_config.py b/tests/configuration/test_config.py index 0529b71f..b36ef09d 100644 --- a/tests/configuration/test_config.py +++ b/tests/configuration/test_config.py @@ -191,3 +191,13 @@ class TestConfig(TestBase): # blacklist logger section self.assertEqual(config["failures"]["filename"], "blacklist") self.assertEqual(config["failures"]["encoding"], "utf-8") + + def test_confparse_empty(self): + config = linkcheck.configuration.Configuration() + files = [get_file("config.empty")] + self.assertRaises(linkcheck.LinkCheckerError, config.read, files) + + def test_confparse_missing(self): + config = linkcheck.configuration.Configuration() + files = [get_file("no_such_config")] + self.assertRaises(linkcheck.LinkCheckerError, config.read, files) diff --git a/tests/test_cookies.py b/tests/test_cookies.py index 78f29bfa..ebe8fc46 100644 --- a/tests/test_cookies.py +++ b/tests/test_cookies.py @@ -18,6 +18,7 @@ Test cookie routines. """ import os +from pathlib import Path import linkcheck.cookies import linkcheck.configuration @@ -80,3 +81,10 @@ class TestCookies(TestBase): aggregate.add_request_session() session = aggregate.get_request_session() self.assertEqual({c.name for c in session.cookies}, {"om", "multiple", "are"}) + + def test_empty_cookie_file(self): + self.assertRaises( + linkcheck.LinkCheckerError, + linkcheck.cookies.from_file, + Path(__file__).parent / "configuration/data/config.empty", + ) diff --git a/tests/test_linkchecker.py b/tests/test_linkchecker.py index e105a848..5ab06832 100644 --- a/tests/test_linkchecker.py +++ b/tests/test_linkchecker.py @@ -40,3 +40,6 @@ class TestLinkchecker(unittest.TestCase): run_with_options([option]) # unknown option self.assertRaises(OSError, run_with_options, ["--imadoofus"]) + # non-existent FILENAMEs + self.assertRaises(OSError, run_with_options, ["--config", "no_such_file"]) + self.assertRaises(OSError, run_with_options, ["--cookiefile", "no_such_file"])