#!/usr/bin/env python3 # coding=utf-8 """ A script that creates an index for a folder. """ import os import sys import time import string import urllib.parse from glob import fnmatch import base64 import random VERSION = "20210128" IMAGE_EXTENSIONS = ["png", "gif", "jpg", "jpeg", "tif", "tiff"] AUDIO_EXTENSIONS = ["wav", "mp3", "ogg"] VIDEO_EXTENSIONS = ["mp4", "ogg", "webm"] def setup(): """ Setup the command line options """ from argparse import ArgumentParser parser = ArgumentParser( epilog="Recursively generate indexes: \n# find . -type d -not -path '*/.*' -exec SimpleWebPage -f \{\} \;" ) parser.add_argument( "-f", action="store_true", dest="overwrite", default=False, help="Overwrite existing index file, even if it's not generated with SimpleWebPage. By default, if file is generated with SimpleWebPage it will be overwritten!", ) parser.add_argument( "-H", action="store_true", dest="hidden", default=False, help="Show hidden files", ) parser.add_argument( "-t", type=str, dest="title", default=None, help="Name for the title (Default: Folder name)", ) parser.add_argument( "-o", type=str, dest="filename", default="index.html", help="Output filename (Default: index.html)", ) parser.add_argument( "-p", action="store_false", dest="parent", default=True, help="Do no print .. link for parent folder.", ) parser.add_argument( "--password", type=str, dest="password", default=None, help="Set a password to view page. The file list will be written to a randomly generated filename. Note: this is not secure, as the target link will still be unprotected if known.", ) parser.add_argument( "-r", action="store_true", dest="recursive", default=False, help="Include all files recursively in the list. Do not include any folders.", ) parser.add_argument( "--images", action="store_true", dest="images", default=False, help="Show images with tags", ) parser.add_argument( "--media", action="store_true", dest="media", default=False, help="Make media playable", ) parser.add_argument( "--include", "-i", action="store", dest="includes", default=["*"], help="Glob match for files to be included in the table. ex. *.jpg. You can pass several includes.", nargs="*", ) parser.add_argument("--version", action="version", version=VERSION) parser.add_argument( "path", type=str, action="store", default=os.path.abspath("."), nargs="?", help="Root path of the index", ) options = parser.parse_args() options.path = os.path.abspath(options.path) if options.title == None: options.title = os.path.basename(options.path) return options def setup2HTML(opts): return '' % ";".join( [ "hidden=%s" % opts.hidden, "parent=%s" % opts.parent, "title=%s" % urllib.parse.quote(opts.title), "images=%s" % opts.images, "media=%s" % opts.media, ] ) def HTML2setup(opts): """ returns new opts and was it able to read HTML """ try: read_config = False with open(os.path.join(opts.path, opts.filename), "rt") as f: for l in f.readlines(): if l.find(' -1: content = l[l.find('name="SimpleWebPageSetup"') :] for s in content.split('"')[3].split(";"): (k, v) = s.split("=", 1) if k == "hidden": opts.hidden = v == "True" if k == "parent": opts.parent = v == "True" if k == "title": opts.title = urllib.parse.unquote(v) if k == "images": opts.images = v == "True" if k == "media": opts.media = v == "True" read_config = True print("Reading options from existing " + opts.filename) break return (opts, read_config) except: return (opts, False) def get_files_and_folders(opts): if opts.recursive: rdirs = [] rfiles = [] rpath = None for path, dirs, files in os.walk(opts.path): if rpath == None: rpath = path if not opts.hidden: files = [f for f in files if not f.startswith(".")] dirs[:] = [d for d in dirs if not d.startswith(".")] files = [os.path.join(os.path.relpath(path, opts.path), f) for f in files] files = [f[2:] if f.startswith("./") else f for f in files] rfiles.extend(files) return rdirs, rfiles, rpath else: for path, dirs, files in os.walk(opts.path): if not opts.hidden: files = [f for f in files if not f.startswith(".")] dirs = [d for d in dirs if not d.startswith(".")] return dirs, files, path def generate_index(opts): opts.password_filename = "" dirs, files, path = get_files_and_folders(opts) if opts.password != None: opts.password_filename = opts.filename opts.filename = generate_password_page(path, opts.filename, opts.password) existing_config = False if opts.filename in files: opts, existing_config = HTML2setup(opts) if not existing_config and not opts.overwrite: print( opts.filename + " exists, and not generated with SimpleWebPage. Exiting." ) sys.exit(1) files = [f for f in files if f != opts.filename] files = [f for f in files if f != opts.password_filename] files = match_files(files, opts.includes) dirs.sort() files.sort() files.sort(key=lambda x: x.find("/") > 0) with open(os.path.join(path, opts.filename), "wt") as f: f.write(get_header(opts)) if opts.parent: f.write(get_pathlink(path, "..")) for di in dirs: f.write(get_pathlink(path, di)) for fi in files: f.write(get_filelink(path, fi, opts.images, opts.media)) f.write(get_footer()) f.write(get_wget_lines(files)) f.close() return def generate_password_page(path, password_file, password): def ha(p): h = 0 for c in p: h = ((h << 5) - h) + ord(c) h = h & 0xFFFFFFFF if h > (1 << 31) - 1: h -= 1 << 32 return h def scramble(p, t): p += t s = "" for i in range(len(t)): s += chr(ord(p[i]) + ord(t[i])) return s def enc(s): return base64.b64encode(s.encode("latin1")).decode("ascii") def random_string(stringLength=16): letters = string.ascii_lowercase + string.digits return "".join(random.choice(letters) for i in range(stringLength)) def get_target(filename): splitted = os.path.splitext(filename) return (splitted[0], random_string(), splitted[1]) target_base, target_middle, target_ext = get_target(password_file) secret = "{}:{}".format(ha(password), enc(scramble(password, target_middle))) with open(os.path.join(path, password_file), "wt") as f: f.write(get_password_page(secret, target_base, target_ext)) return "{}.{}{}".format(target_base, target_middle, target_ext) def get_filelink(path, fname, images=False, media=False): if os.path.islink(os.path.join(path, fname)) and not os.path.exists( os.path.join(path, fname) ): (fsize, fsstr, fsstrb, fdstr) = (0, "NA", "NA", "NA") else: fsize = os.path.getsize(os.path.join(path, fname)) fsstr = sizeof(fsize) fsstrb = str(fsize) fdate = time.localtime(os.path.getmtime(os.path.join(path, fname))) fdstr = time.strftime("%Y/%m/%d %H:%M:%S", fdate) fname_str = fname if images and is_imagefile(fname): fname_str = get_imagestr(fname) if media and is_audiofile(fname): fname_str = get_audiostr(fname) if media and is_videofile(fname): fname_str = get_videostr(fname) return ( '%s%s%s%s\n' % (urllib.parse.quote(fname), fname_str, fsstr, fsstrb, fdstr) ) def get_wget_lines(files): wget = "\n\n" return wget def get_imagestr(fname): return '' % (urllib.parse.quote(fname), fname) def get_audiostr(fname): return '%s
' % ( fname, urllib.parse.quote(fname), ) def get_videostr(fname): return '%s
' % ( fname, urllib.parse.quote(fname), ) def get_pathlink(path, dname): fdate = time.localtime(os.path.getmtime(os.path.join(path, dname))) fdstr = time.strftime("%Y/%m/%d %H:%M:%S", fdate) return ( '↳ %s/[DIR]0%s\n' % (urllib.parse.quote(dname), dname, fdstr) ) def get_password_page(secret, target_base, target_ext): return ( """ Password required

""".replace( "TARGET_EXT", target_ext, 1 ) .replace("TARGET_BASE", target_base, 1) .replace("SECRET", secret, 1) ) def get_header(opts): header = ( """ """ + setup2HTML(opts) + """ """ + opts.title + """

""" + opts.title + """

""" ) return header def get_footer(): return """
NameSizeSize BModified
""" def is_imagefile(fname): for ext in IMAGE_EXTENSIONS: if fname.lower().endswith(ext): return True return False def is_audiofile(fname): for ext in AUDIO_EXTENSIONS: if fname.lower().endswith(ext): return True return False def is_videofile(fname): for ext in VIDEO_EXTENSIONS: if fname.lower().endswith(ext): return True return False def match_files(files, glob_list): matched = [] for f in files: for g in glob_list: if fnmatch.fnmatch(f, g): matched.append(f) break return matched def sizeof(num): for x in [" B", "KB", "MB", "GB", "TB"]: if num < 1024.0: if x == " B": return "%d %s" % (num, x) return "%3.1f %s" % (num, x) num /= 1024.0 if __name__ == "__main__": opts = setup() generate_index(opts)