linkchecker/linkcheck/fileutil.py
Chris Down 85ce9841eb Allow FIFOs to be used as config files
There are some config options which have no equivalent command line
option. Some may want to set these options dynamically or on a one-off
basis where a static config file is not ideal, and one very easy way to
do that is using process substitution:

    linkchecker --config <(printf '%s\n' '[filtering]' 'ignorewarnings=http-redirected') ...

This, however, does not work in the current code because these are
typically implemented as FIFOs, which don't pass the `os.path.isfile`
check:

    WARNING linkcheck.cmdline 2023-10-31 00:12:09,678 MainThread Unreadable config file: '/dev/fd/63'

Allow reading FIFOs as config input so that this is possible.
`fileutil.is_readable` also now doesn't check if the path leads to a
regular file: this is only used as part of cookie and config file input,
and in both cases that's not really relevant.
2023-10-31 00:31:28 +00:00

110 lines
3.2 KiB
Python

# Copyright (C) 2005-2014 Bastian Kleineidam
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""
File and path utilities.
"""
import os
import locale
import stat
import tempfile
import importlib
from functools import lru_cache
def has_module(name, without_error=True):
"""Test if given module can be imported.
@param without_error: True if module must not throw any errors when importing
@return: flag if import is successful
@rtype: bool
"""
try:
importlib.import_module(name)
return True
except ImportError:
return False
except Exception:
# some modules raise errors when intitializing
return not without_error
def get_mtime(filename):
"""Return modification time of filename or zero on errors."""
try:
return os.path.getmtime(filename)
except os.error:
return 0
def get_size(filename):
"""Return file size in Bytes, or -1 on error."""
try:
return os.path.getsize(filename)
except os.error:
return -1
# http://developer.gnome.org/doc/API/2.0/glib/glib-running.html
if "G_FILENAME_ENCODING" in os.environ:
FSCODING = os.environ["G_FILENAME_ENCODING"].split(",")[0]
if FSCODING == "@locale":
FSCODING = locale.getpreferredencoding()
elif "G_BROKEN_FILENAMES" in os.environ:
FSCODING = locale.getpreferredencoding()
else:
FSCODING = "utf-8"
def path_safe(path):
"""Ensure path string is compatible with the platform file system encoding."""
if path and not os.path.supports_unicode_filenames:
path = path.encode(FSCODING, "replace").decode(FSCODING)
return path
def get_temp_file(mode='r', **kwargs):
"""Return tuple (open file object, filename) pointing to a temporary
file."""
fd, filename = tempfile.mkstemp(**kwargs)
return os.fdopen(fd, mode), filename
def is_tty(fp):
"""Check if is a file object pointing to a TTY."""
return hasattr(fp, "isatty") and fp.isatty()
@lru_cache(128)
def is_readable(filename):
"""Check if file is readable."""
return os.access(filename, os.R_OK)
def is_accessable_by_others(filename):
"""Check if file is group or world accessible."""
mode = os.stat(filename)[stat.ST_MODE]
return mode & (stat.S_IRWXG | stat.S_IRWXO)
def is_writable_by_others(filename):
"""Check if file or directory is world writable."""
mode = os.stat(filename)[stat.ST_MODE]
return mode & stat.S_IWOTH
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)