diff --git a/README.md b/README.md index f3dd6cc..7063ba3 100644 --- a/README.md +++ b/README.md @@ -82,18 +82,23 @@ make the specified vault's directory the current one in the pycryptomator internal shell ``` -decrypt [-m] [-f] +decrypt [-fmF] decrypt - ``` decrypt one or more files and/or directories to the specified destination in the -real file system. Option `-f` forces to overwrite existing files, `-m` moves -(i.e. deletes) the source files after decryption. With `-` as destination, a -file is decrypted and printed to standard output. - -`encrypt [-m] ` -encrypt one or more files and/or directories to the specified destination. If -the destination is a directory, it must exist. `-m` moves (i.e. deletes) the -source files after encryption. +real file system. +`-f` forces to overwrite existing files, `-m` moves (i.e. deletes) the source +files after decryption, `-F` replicates the full command line path of source +in destination (by default only filenames are copied). +With `-` as destination, a file is decrypted and printed to standard output. + +`encrypt [-fmF] ` +encrypt one or more files and/or directories to the specified destination. +If multiple sources are specified, the destination directory will be created +if not existent. +`-f` forces to overwrite existing files, `-m` moves (i.e. deletes) the source +files after encryption, `-F` replicates the full command line path of source +in destination (by default only filenames are copied). `ln ` make a symbolic link to a target file or directory in the vault. @@ -109,9 +114,9 @@ write time, name, symbolic link target). `-s` sorts results by one or more criteria: `N`ame, `S`ize, `D`ate, `E`xtension (a.k.a. file type), `-` sorts in reverse order and `!` puts directories first. -`mkdir [...]` +`mkdir [-R] [...]` make one or more directories or directory trees (i.e. intermediate directories -get created) in the vault. +get created) in the vault or in the real file system if `-R` is specified. `mv [...] ` rename or move files and directories. If more files or directories are specified, diff --git a/pycryptomator/__init__.py b/pycryptomator/__init__.py index bee37a3..d62fd80 100644 --- a/pycryptomator/__init__.py +++ b/pycryptomator/__init__.py @@ -1,4 +1,4 @@ COPYRIGHT = '''Copyright (C)2024, by maxpat78.''' -__version__ = '1.10' +__version__ = '1.11' __all__ = ["Vault", "init_vault", "backupDirIds"] from .cryptomator import * diff --git a/pycryptomator/cmshell.py b/pycryptomator/cmshell.py index 7e0325d..a413da4 100644 --- a/pycryptomator/cmshell.py +++ b/pycryptomator/cmshell.py @@ -98,8 +98,10 @@ def do_decrypt(p, arg): if move: argl.remove('-m') force = '-f' in argl if force: argl.remove('-f') + fulltree = '-F' in argl + if fulltree: argl.remove('-F') if not argl or argl[0] == '-h' or len(argl) < 2: - print('use: decrypt [-m] [-f] ') + print('use: decrypt [-fmF] ') print('use: decrypt -') return try: @@ -110,40 +112,57 @@ def do_decrypt(p, arg): else: dest = argl[-1] if len(argl) > 2: - if not os.path.isdir(dest): - print('Destination directory %s does not exist!' % dest) + if os.path.exists(dest) and not os.path.isdir(dest): + print('Destination %s exists and is not a directory!' % dest) return - dest = CMShell._join(dest, it) - print(dest) + # else it will be created + if fulltree: + dest = CMShell._join(dest, it) + else: + dest = CMShell._join(dest, basename(it)) + print(dest) p.vault.decryptFile(p._prep_cd(it), dest, force, move) if argl[-1] == '-': print() except: print(sys.exception()) + #~ import traceback; print(traceback.format_exc()) def do_encrypt(p, arg): 'Encrypt files or directories into the vault, eventually moving them' argl = split(arg) move = '-m' in argl if move: argl.remove('-m') + force = '-f' in argl + if force: argl.remove('-f') + fulltree = '-F' in argl + if fulltree: argl.remove('-F') if not argl or argl[0] == '-h' or len(argl) < 2: - print('use: encrypt [-m] ') + print('use: encrypt [-Ffm] ') return try: for it in argl[:-1]: + dest = p._prep_cd(argl[-1]) if isdir(it): - p.vault.encryptDir(it, p._prep_cd(argl[-1]), move=move) + p.vault.encryptDir(it, dest, force, move) else: - dest = p._prep_cd(argl[-1]) + x = p.vault.getInfo(dest) + # In many-to-one, one must exist as dir, or not exist if len(argl) > 2: - x = p.vault.getInfo(dest) - if not x.isDir: - print('Destination directory %s does not exist!' % dest) + if x.exists and not x.isDir: + print('Destination %s exists and is not a directory!' % dest) return - dest = CMShell._join(dest, it) - print(dest) - p.vault.encryptFile(it, dest, move=move) + if not x.exists: # dir will be created + x.isDir = 1 + if x.isDir: + if fulltree: + dest = CMShell._join(dest, it) + else: + dest = CMShell._join(dest, basename(it)) + print(dest) + p.vault.encryptFile(it, dest, force, move) except: print(sys.exception()) + #~ import traceback; print(traceback.format_exc()) def do_ls(p, arg): 'List files and directories' @@ -190,12 +209,17 @@ def do_ln(p, arg): def do_mkdir(p, arg): 'Make a directory or directory tree' argl = split(arg) + realfs = '-R' in argl + if realfs: argl.remove('-R') if not argl or argl[0] == '-h': - print('use: mkdir [...]') + print('use: mkdir [-R] [...]') return for it in argl: try: - p.vault.mkdir(p._prep_cd(it)) + if realfs: + os.makedirs(it) + else: + p.vault.mkdir(p._prep_cd(it)) except: print(sys.exception()) @@ -206,7 +230,10 @@ def do_mv(p, arg): print('please use: mv [...] ') return for it in argl[:-1]: - p.vault.mv(p._prep_cd(it), p._prep_cd(argl[-1])) + try: + p.vault.mv(p._prep_cd(it), p._prep_cd(argl[-1])) + except: + print(sys.exception()) def do_rm(p, arg): 'Remove files and directories' diff --git a/pycryptomator/cryptomator.py b/pycryptomator/cryptomator.py index 64a702a..86a5549 100644 --- a/pycryptomator/cryptomator.py +++ b/pycryptomator/cryptomator.py @@ -149,6 +149,8 @@ def hashDirId(p, dirId): def encryptName(p, dirId, name): "Encrypt a name contained in a given directory" + i = check_name(name) + if i: raise BaseException('''Illegal character '%s' in "%s"''' % (chr(name[i-1]), name.decode())) dirIdE = aes_siv_encrypt(p.pk, p.hk, name, dirId) # concatenated 128-bit digest and encrypted name return base64.urlsafe_b64encode(dirIdE) + b'.c9r' @@ -275,6 +277,9 @@ def encryptFile(p, src, virtualpath, force=False, move=False): if not exists(src): raise BaseException('Source file does not exist: '+src) f = open(src, 'rb') + x = p.getInfo(virtualpath) + if x.exists and not force: + raise BaseException('destination file "%s" exists and won\'t get overwritten!'%virtualpath) if not basename(virtualpath).endswith('dirid.c9r'): rp = p.create(virtualpath) else: @@ -351,7 +356,7 @@ def decryptFile(p, virtualpath, dest, force=False, move=False): info = p.getInfo(virtualpath) while info.pointsTo: info = p.getInfo(info.pointsTo) - rp = info.realPathName + rp = info.contentsC9 f = open(rp, 'rb') if hasattr(dest, 'write'): # if it's file out = dest @@ -952,3 +957,16 @@ def calc_rel_path(base, child): n -= 1 relpath += '/'.join(base_parts[i:]) return relpath + +def check_name(name): + if os.name == 'nt': + illegal_chars = b'\x00<>:"/\\|?*' + else: + illegal_chars = b'\x00/' + i = 0 + while i < len(name): + c = name[i] + i += 1 + if c in illegal_chars: return i + if os.name == 'nt' and c in b' .' and i+1 == len(name): return i + return 0 \ No newline at end of file