#!/usr/bin/env python import argparse,json,sys,os from shutil import copyfile from tabulate import tabulate from datetime import datetime from utils.utils import * from utils.crypt import * def get_root_path(opts): root_folder = os.path.dirname( os.path.dirname( os.path.abspath( opts.config ) ) ) return root_folder def list_shares(shares,opts): table = [] table.append(('Name', 'Path','Public','Password','PassHash','Upload','Overwrite','Direct','Expire','Recipient','Description')) for share in shares: public = get_or_none('public',share, False) passhash = '-' password = 'pass_hash' in share if opts.show_password: if 'pass_plain' in share: password = share['pass_plain'] else: password = "" if 'pass_hash' in share: passhash = share['pass_hash'] else: passhash = "-" upload = get_or_none('upload',share, False) overwrite = get_or_none('overwrite',share, True) direct = get_or_none('direct_links',share, False) if password else False expire = get_or_none('expire',share, "-") description = get_or_none('description',share, "")[0:20] table.append(( share['name'], share['path']+"/", public, password, passhash, upload, overwrite, direct, expire, get_or_none('recipient', share, "")[0:20], description )) print(tabulate(table, headers = "firstrow")) def list_folders(shares,config): if 'data_folder' not in config: print("data_folder not defined in config") sys.exit(1) data_folder = os.path.join(config['__root_path__'], config['data_folder']) folders = sorted(os.listdir(data_folder)) table = [] table.append( ('Path','Share','Size','Unit') ) for folder in folders: full_path = os.path.join(data_folder, folder) if not os.path.isdir(full_path): continue share_name = "[unused by any share]" for share in shares: share_path = os.path.join(data_folder, share['path']) if not os.path.exists(share_path): break if os.path.samefile(full_path, share_path): share_name = share['name'] break (size_num, size_unit) = file_size_human(get_folder_size( full_path )).split(" ") table.append(( folder, share_name, size_num, size_unit )) print(tabulate(table, headers = "firstrow")) def add_share(shares, config, opts): share = { 'name': opts.name, 'path': opts.path, 'public': opts.public, 'upload': opts.upload, 'overwrite': opts.overwrite, 'direct_links': opts.direct, 'description': opts.description, 'recipient': opts.recipient } if opts.password: if opts.plain: share['pass_plain'] = opts.password share['pass_hash'] = password_hash(opts.password) if opts.expire: try: date_object = datetime.strptime(opts.expire,"%Y-%m-%d %H:%M") except ValueError as e: print(e) print("Date format error") sys.exit(1) share.update({ 'expire': opts.expire }) if opts.write: shares.append(share) shares_file = os.path.join(config['__root_path__'], opts.shares_file) if os.path.exists(shares_file): print("creating backup %s"%(shares_file+".bkp",)) copyfile( shares_file, shares_file+".bkp" ) with open(shares_file,'wt') as fp: json.dump(shares, fp, indent = 2, sort_keys = True) print("Wrote file %s"%(shares_file,)) print("Add share: %s"%( opts.name, )) else: print("Share not saved anywhere. Save with -w") print(json.dumps(share, indent = 2, sort_keys = True)) def modify_share(shares, config, opts): print("Modifying share: %s"%( opts.name, )) found = False for i,share in enumerate(shares): if share['name'] != opts.name: continue orig_share = share.copy() print(json.dumps(share, indent = 2, sort_keys = True)) found = True break if not found: print('no such share') sys.exit(1) if opts.path != None: share['path'] = opts.path for attr in ('public','upload','direct_links','overwrite'): if getattr(opts,attr) != None: share[attr] = getattr(opts,attr) == 'true' if opts.description != None: share['description'] = opts.description if opts.recipient != None: share['recipient'] = opts.recipient # REMOVE password if opts.password == "": if 'pass_plain' in share: del share['pass_plain'] if 'pass_hash' in share: del share['pass_hash'] if opts.password: # ADD/Change a password if opts.plain: share['pass_plain'] = opts.password share['pass_hash'] = password_hash(opts.password) if opts.expire: if opts.expire == "": # REMOVE EXPIRATION if 'expire' in share: del share['expire'] else: # ADD/CHANGE EXPIRATION try: date_object = datetime.strptime(opts.expire,"%Y-%m-%d %H:%M") except ValueError as e: print(e) print("Date format error") sys.exit(1) share['expire'] = opts.expire if opts.write: shares[i] = share shares_file = os.path.join(config['__root_path__'], opts.shares_file) if os.path.exists(shares_file): print("creating backup %s"%(shares_file+".bkp",)) copyfile( shares_file, shares_file+".bkp" ) with open(shares_file,'wt') as fp: json.dump(shares, fp, indent = 2, sort_keys = True) print("Wrote file %s"%(shares_file,)) else: print("Share not saved anywhere. Save with -w") modified = [] for key in share: if not key in orig_share: modified.append(key) continue if orig_share[key] != share[key]: modified.append(key) continue for key in orig_share: if not key in share: modified.append(key) print("Modified values: %s"%(", ".join(modified))) print(json.dumps(share, indent = 2, sort_keys = True)) def remove_share(shares,config,opts): name = opts.name share = [share for share in shares if share['name'] == name] for share_ in share: print("Removing share: %s"%( name, )) print(json.dumps(share_, indent = 2, sort_keys = True)) if opts.write: shares = [share for share in shares if share['name'] != name] shares_file = os.path.join(config['__root_path__'], opts.shares_file) print("creating backup %s"%(shares_file+".bkp",)) copyfile(shares_file, shares_file+".bkp") with open(shares_file,'wt') as fp: json.dump(shares, fp, indent = 2, sort_keys = True) print("Removed %s from %s"%(name, shares_file)) else: print("Share was not actually removed. Use -w to rewrite shares file.") def print_rest_api(shares, config, opts): if 'public_url' not in config: print("Set public_url variable in your config.json") sys.exit(1) shares = [share for share in shares if share['name'] == opts.name] if len(shares) == 0: print("No such share %s"%( opts.name, )) sys.exit(1) share = shares[0] if opts.type == "list": print("Link to enter the share:") print("%s/list/%s"%( config['public_url'], share['name'] )) return if not 'pass_hash' in share: print("REST API enabled only if pass_hash is set for share") sys.exit(1) crypter = Crypto(config['app_secret_key']) crypted = crypter.encrypt(share['pass_hash']) if opts.type == "login": print("Link to automatically login in the share:") print("%s/list/%s/%s"%( config['public_url'], share['name'], crypted )) elif opts.type == "upload": if 'upload' not in share or not share['upload']: print("Uploading not allowed to this share") sys.exit(0) print("Link to upload file to the share:") print("\n# curl -F file=@'the_file_name.ext' %s/upload/%s/%s"%( config['public_url'], share['name'], crypted )) print("\nLink to upload multiple files to the share:") print("\n# curl -s %s/script/upload/%s/%s | bash /dev/stdin file_to_upload.ext [second.file.ext]"%( config['public_url'], share['name'], crypted )) print("\nLink to upload multiple files to the share, splitting large files (default 512Mb):") print("\n# curl -s %s/script/upload_split/%s/%s | bash /dev/stdin [-s split_size_in_Mb] file_to_upload.ext [second.file.ext]"%( config['public_url'], share['name'], crypted )) elif opts.type == "download": print("Links to download files:") share_path = os.path.join( config['__root_path__'], config['data_folder'], share['path'] ) if not os.path.exists(share_path): print("no files") sys.exit(0) for filename in sorted(os.listdir(share_path)): if os.path.isdir(os.path.join(share_path,filename)): continue if filename.startswith("."): continue print("%s/download/%s/%s/%s"%( config['public_url'], share['name'], crypted, filename )) print("or \n\n# curl -s %s/script/download/%s/%s | bash /dev/stdin [-f]"%( config['public_url'], share['name'], crypted )) elif opts.type == "direct": if 'direct_links' not in share or not share['direct_links']: print("Direct downloading not allowed in this share") sys.exit(0) print("Links to direct download files:") share_path = os.path.join( config['__root_path__'], config['data_folder'], share['path'] ) if not os.path.exists(share_path): print("no files") sys.exit(0) for filename in sorted(os.listdir(share_path)): if os.path.isdir(os.path.join(share_path,filename)): continue if filename.startswith("."): continue print("%s/direct/%s/%s/%s"%( config['public_url'], share['name'], get_direct_token(share,filename), filename )) print("or \n\n# curl -s %s/script/direct/%s/%s | bash /dev/stdin [-f]"%( config['public_url'], share['name'], crypted )) elif opts.type == "zip": print("ZIP download:") print("%s/zip/%s/%s"%( config['public_url'], share['name'], crypted )) def parse_options(): config_default = os.path.realpath( os.path.join( os.path.dirname( os.path.realpath(__file__) ), "..", "data", "config.json" ) ) parser = argparse.ArgumentParser(description='Flees share manager') parser.add_argument('-c','--config', action="store", dest="config", default = config_default, help = "Your current config.json file [%(default)s]") parser.add_argument('-s','--shares', action="store", dest="shares_file", default = None, help = "shares.json you want to use. Defaults to what config.json defines") subparsers = parser.add_subparsers(help='sub-command help', dest='subparser_name') ## list shares parser_list = subparsers.add_parser('list', help = "List shares") parser_list.add_argument('-P', action="store_true", dest="show_password", default = False, help = "Display passwords") ## list folders parser_folders = subparsers.add_parser('folders', help = "List folders and share names") ## Remove parser_remove = subparsers.add_parser('remove', help = "Remove a share") parser_remove.add_argument(dest="name") parser_remove.add_argument('-w','--write', action="store_true", dest="write", default = False, help = "Write changes to the shares.json file" ) ## Add parser_add = subparsers.add_parser('add', help = "Add a share") parser_add.add_argument('-n','--name', action="store", dest="name", required = True) parser_add.add_argument('-p','--path', action="store", dest="path", required = True, help= "path relative to data folder" ) parser_add.add_argument('-D','--description', action="store", dest="description", default = "", help= "Describe the contents" ) parser_add.add_argument('-P','--public', action="store_true", dest="public", default = False) parser_add.add_argument('-u','--upload', action="store_true", dest="upload", default = False) parser_add.add_argument('-o','--overwrite', action="store_false", dest="overwrite", default = True, help = "Disable file overwrites. If disabled, old files are versioned with modification date.") parser_add.add_argument('-d','--direct', action="store_true", dest="direct", default = False, help = "Allow direct file sharing (password hash included in URL)") parser_add.add_argument('--pass-plain', action="store_true", dest="plain", default = False, help = "Save the password as plain text") parser_add.add_argument('--password', action="store", dest="password", default = False, help = "Setting a password enables use of login links and direct downloads") parser_add.add_argument('-e','--expire', action="store", dest="expire", default = False, help = "expire date in format '%%Y-%%m-%%d %%H:%%M' ex. '2018-12-24 21:00'" ) parser_add.add_argument('-r','--recipient', action="store", dest="recipient", default = "", help= "Recipient for notifications (if enabled)" ) parser_add.add_argument('-w','--write', action="store_true", dest="write", default = False, help = "Write changes to the shares.json file" ) ## Modify parser_modify = subparsers.add_parser('modify', help = "Modify share") parser_modify.add_argument('-n','--name', action="store", dest="name", required = True) parser_modify.add_argument('-p','--path', action="store", dest="path", default = None, help= "path relative to data folder" ) parser_modify.add_argument('-D','--description', action="store", dest="description", default = None, help= "Describe the contents" ) parser_modify.add_argument('-P','--public', action="store", dest="public", default = None, choices = ['true','false']) parser_modify.add_argument('-u','--upload', action="store", dest="upload", default = None, choices = ['true','false']) parser_modify.add_argument('-o','--overwrite', action="store", dest="overwrite", default = None, choices = ['true','false'], help = "Disable file overwrites. If disabled, old files are versioned with modification date.") parser_modify.add_argument('-d','--direct', action="store", dest="direct_links", default = None, choices = ['true','false'], help = "Allow direct file sharing (password hash included in URL)") parser_modify.add_argument('--pass-plain', action="store_true", dest="plain", default = False, help = "Save the password as plain text") parser_modify.add_argument('--password', action="store", dest="password", default = False, help = "Setting a password enables use of login links and direct downloads. Set as empty string to remove password protection.") parser_modify.add_argument('-e','--expire', action="store", dest="expire", default = False, help = "expire date in format '%%Y-%%m-%%d %%H:%%M' ex. '2018-12-24 21:00'. Set as empty string to remove expiration." ) parser_modify.add_argument('-r','--recipient', action="store", dest="recipient", default = None, help= "Recipient for notifications (if enabled)" ) parser_modify.add_argument('-w','--write', action="store_true", dest="write", default = False, help = "Write changes to the shares.json file" ) ## REST parser_rest = subparsers.add_parser('rest', help = "Display REST API links") parser_rest.add_argument(dest="name", help = "Name of the share") parser_rest.add_argument(dest="type", help = "Type of command", choices = ['list','login','upload','download','direct','zip'] ) return parser.parse_args() if __name__ == "__main__": opts = parse_options() config = {} if os.path.exists(opts.config): config = json.load(open(opts.config,'rt')) config['__root_path__'] = get_root_path(opts) else: print("config file %s does not exist!"%(opts.config,)) sys.exit(1) if opts.shares_file: config['shares_file'] = opts.shares_file if 'shares_file' in config: # if not from command line, read from config opts.shares_file = config['shares_file'] if os.path.exists(os.path.join(config['__root_path__'],config['shares_file'])): shares = json.load(open(os.path.join(config['__root_path__'],config['shares_file']),'rt')) else: print("shares_file %s does not exist!"%(os.path.join(config['__root_path__'],config['shares_file']))) shares = [] if opts.subparser_name == 'list': list_shares(shares,opts) elif opts.subparser_name == 'folders': list_folders(shares,config) elif opts.subparser_name == 'remove': remove_share(shares,config,opts) elif opts.subparser_name == 'add': add_share(shares,config,opts) elif opts.subparser_name == 'modify': modify_share(shares,config,opts) elif opts.subparser_name == 'rest': print_rest_api(shares,config,opts)