Source code for passpy.util

# passpy --  ZX2C4's pass compatible library and cli
# Copyright (C) 2016 Benedikt Rascher-Friesenhausen <>

# 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 3 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
# 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, see <>.

import os
import random
import shutil
import string

from functools import wraps

from passpy.exceptions import (

[docs]def trap(path_index): """Prevent accessing files and directories outside the password store. `path_index` is necessary as the functions that need to be trapped have different argument lists. This way we can indicate which argument contains the paths that are to be checked. :param path_index: The index for the path variable in either `args` or `kwargs`. :type path_index: int or str :rtype: func :returns: The trapped function. """ def trap_decorator(func): @wraps(func) def trap_wrapper(*args, **kwargs): try: path_list = args[path_index] except TypeError: try: path_list = kwargs[path_index] except KeyError: path_list = None except IndexError: path_list = None if path_list is not None and not isinstance(path_list, list): path_list = [path_list] if path_list is not None: for path in path_list: path = os.path.normpath(path) if path.startswith('..' + os.sep) or path == '..': raise PermissionError('Sneaky!') return func(*args, **kwargs) return trap_wrapper return trap_decorator
[docs]def initialised(func): """Check that the store is initialised before running. Used as a decorator in methods for :class:``. :param func: A method of :class:``. :type store: function :rtype: function :returns: The method if the store is initialised. :raises passpy.exceptions.StoreNotInitialisedError: if the store is not initialised. """ @wraps(func) def initialised_wrapper(*args, **kwargs): store = args[0] if not store.is_init(): raise StoreNotInitialisedError( 'You need to initialise the store first.') return func(*args, **kwargs) return initialised_wrapper
[docs]def gen_password(length, symbols=True): """Generates a random string. Uses :class:`random.SystemRandom` if available and :class:`random.Random` otherwise. :param int length: The length of the random string. :param bool symbols: (optional) If ``True`` :const:`string.punctuation` will also be used to generate the output. :rtype: str :returns: A random string of length `length`. """ try: rand = random.SystemRandom() except NotImplementedError: rand = random.Random() chars = string.ascii_letters + string.digits if symbols: chars += string.punctuation return ''.join(rand.choice(chars) for _ in range(length))
[docs]def copy_move(src, dst, force=False, move=False, interactive=False, verbose=False): """Copies/moves a file or directory recursively. This function is partially based on the `cp` function from the `pycoreutils`_ package written by Hans van Leeuwen and licensed under the MIT license. .. _pycoreutils: :param str src: The file or directory to be copied. :param str dst: The file or directory to be copied to. :param bool force: If ``True`` existing files at the destination will be silently overwritten. :param bool interactive: If ``True`` the user will be prompted for every file to be overwritten. Has no effect if `force` is also ``True``. :param bool verbose: If ``True`` print the old and new filename for every copied/moved file. :raises FileNotFoundError: if there exists no key or directory for `src`. :raises FileExistsError: if a key at `dst` already exists and `force` is set to ``False``. """ if move: operation = shutil.move else: operation = shutil.copy if os.path.isfile(src): if dst.endswith(os.sep) and not os.path.exists(dst): os.makedirs(dst, exist_ok=True) else: os.makedirs(os.path.dirname(dst), exist_ok=True) if os.path.isdir(dst): dst = os.path.join(dst, os.path.basename(src)) if os.path.exists(dst) and not force: if interactive: answer = input('Really overwrite {0}? [y/N] '.format(dst)) if answer.lower() != 'y': return None else: raise FileExistsError('{0} already exists.'.format(dst)) operation(src, dst) if verbose: print('{0} -> {1}'.format(src, dst)) elif os.path.isdir(src): if os.path.commonpath([src, dst]) == src: raise RecursiveCopyMoveError('Can\'t copy or move a ' 'directory into itself.') if os.path.exists(dst): dst = os.path.join(dst, os.path.basename(src)) if not os.path.exists(dst): os.makedirs(dst, exist_ok=True) for root, dirs, files in os.walk(src): mid = os.path.relpath(root, src) for d in dirs: dstdir = os.path.normpath(os.path.join(dst, mid, d)) if not os.path.exists(dstdir): os.mkdir(dstdir) if verbose: print('created directory {0}'.format(dstdir)) for f in files: srcfile = os.path.join(root, f) dstfile = os.path.normpath(os.path.join(dst, mid, f)) if os.path.exists(dstfile) and not force: if interactive: answer = input('Really overwrite {0}? [y/N] ' .format(dstfile)) if answer.lower() != 'y': continue else: raise FileExistsError('{0} already exists.' .format(dstfile)) operation(srcfile, dstfile) if verbose: print('{0} -> {1}'.format(srcfile, dstfile)) else: raise FileNotFoundError('{0} does not exist.'.format(src)) return dst