password protection for web pages
This commit is contained in:
@@ -1,13 +1,18 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python3
|
||||||
# coding=utf-8
|
# coding=utf-8
|
||||||
''' A script that creates an index for a folder.
|
''' A script that creates an index for a folder.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import os,sys,time
|
import os
|
||||||
import urllib
|
import sys
|
||||||
|
import time
|
||||||
|
import string
|
||||||
|
import urllib.parse
|
||||||
from glob import fnmatch
|
from glob import fnmatch
|
||||||
|
import base64
|
||||||
|
import random
|
||||||
|
|
||||||
VERSION = "20200427"
|
VERSION = "20200619"
|
||||||
IMAGE_EXTENSIONS = ['png', 'gif', 'jpg', 'jpeg', 'tif', 'tiff']
|
IMAGE_EXTENSIONS = ['png', 'gif', 'jpg', 'jpeg', 'tif', 'tiff']
|
||||||
|
|
||||||
def setup():
|
def setup():
|
||||||
@@ -26,6 +31,8 @@ def setup():
|
|||||||
help="Output filename (Default: index.html)")
|
help="Output filename (Default: index.html)")
|
||||||
parser.add_argument("-p",action="store_false",dest="parent",default=True,
|
parser.add_argument("-p",action="store_false",dest="parent",default=True,
|
||||||
help="Do no print .. link for parent folder.")
|
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,
|
parser.add_argument("-r",action="store_true",dest="recursive",default=False,
|
||||||
help="Include all files recursively in the list. Do not include any folders.")
|
help="Include all files recursively in the list. Do not include any folders.")
|
||||||
parser.add_argument("--images",action="store_true",dest="images",default=False,
|
parser.add_argument("--images",action="store_true",dest="images",default=False,
|
||||||
@@ -57,7 +64,7 @@ def setup2HTML(opts):
|
|||||||
return '<meta name="SimpleWebPageSetup" content="%s"/>'%";".join([
|
return '<meta name="SimpleWebPageSetup" content="%s"/>'%";".join([
|
||||||
'hidden=%s'%opts.hidden,
|
'hidden=%s'%opts.hidden,
|
||||||
'parent=%s'%opts.parent,
|
'parent=%s'%opts.parent,
|
||||||
'title=%s'%urllib.quote(opts.title),
|
'title=%s'%urllib.parse.quote(opts.title),
|
||||||
'images=%s'%opts.images
|
'images=%s'%opts.images
|
||||||
])
|
])
|
||||||
|
|
||||||
@@ -73,7 +80,7 @@ def HTML2setup(opts):
|
|||||||
(k,v) = s.split('=',1)
|
(k,v) = s.split('=',1)
|
||||||
if k == 'hidden': opts.hidden = v == "True"
|
if k == 'hidden': opts.hidden = v == "True"
|
||||||
if k == 'parent': opts.parent = v == "True"
|
if k == 'parent': opts.parent = v == "True"
|
||||||
if k == 'title': opts.title = urllib.unquote(v)
|
if k == 'title': opts.title = urllib.parse.unquote(v)
|
||||||
if k == 'images': opts.images = v == "True"
|
if k == 'images': opts.images = v == "True"
|
||||||
read_config = True
|
read_config = True
|
||||||
print("Reading options from existing " + opts.filename)
|
print("Reading options from existing " + opts.filename)
|
||||||
@@ -109,7 +116,11 @@ def get_files_and_folders(opts):
|
|||||||
|
|
||||||
|
|
||||||
def generate_index(opts):
|
def generate_index(opts):
|
||||||
|
opts.password_filename = ""
|
||||||
dirs, files, path = get_files_and_folders(opts)
|
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
|
existing_config = False
|
||||||
if opts.filename in files:
|
if opts.filename in files:
|
||||||
opts, existing_config = HTML2setup(opts)
|
opts, existing_config = HTML2setup(opts)
|
||||||
@@ -117,6 +128,7 @@ def generate_index(opts):
|
|||||||
print(opts.filename + " exists, and not generated with SimpleWebPage. Exiting.")
|
print(opts.filename + " exists, and not generated with SimpleWebPage. Exiting.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
files = [ f for f in files if f != opts.filename]
|
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)
|
files = match_files(files, opts.includes)
|
||||||
dirs.sort()
|
dirs.sort()
|
||||||
files.sort()
|
files.sort()
|
||||||
@@ -135,6 +147,50 @@ def generate_index(opts):
|
|||||||
return
|
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 = (p * (int(len(t)/len(p))+1))[:len(t)]
|
||||||
|
t = (t * (int(len(p)/len(t))+1))[:len(p)]
|
||||||
|
s = ''
|
||||||
|
for i in range(len(p)):
|
||||||
|
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=8):
|
||||||
|
letters = string.ascii_lowercase + string.digits
|
||||||
|
return ''.join(random.choice(letters) for i in range(stringLength))
|
||||||
|
|
||||||
|
|
||||||
|
def get_target(filename):
|
||||||
|
return "{0}.{2}{1}".format(
|
||||||
|
*os.path.splitext(filename) + (random_string(),)
|
||||||
|
)
|
||||||
|
|
||||||
|
target_file = get_target(password_file)
|
||||||
|
secret = "{}:{}".format(
|
||||||
|
ha(password),
|
||||||
|
enc(scramble(password, target_file))
|
||||||
|
)
|
||||||
|
with open(os.path.join(path, password_file), 'wt') as f:
|
||||||
|
f.write(get_password_page(secret))
|
||||||
|
return target_file
|
||||||
|
|
||||||
|
|
||||||
def get_filelink(path,fname,images=False):
|
def get_filelink(path,fname,images=False):
|
||||||
if os.path.islink(os.path.join(path, fname)) and not os.path.exists(os.path.join(path, fname)):
|
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")
|
(fsize, fsstr, fsstrb, fdstr)=(0, "NA", "NA", "NA")
|
||||||
@@ -149,7 +205,7 @@ def get_filelink(path,fname,images=False):
|
|||||||
else:
|
else:
|
||||||
fname_str = fname
|
fname_str = fname
|
||||||
return '<tr><td><a class="link_file" href="%s">%s</a><td class="right">%s</td><td class="right bytes">%s</td><td class="right">%s</td></tr>\n'%(
|
return '<tr><td><a class="link_file" href="%s">%s</a><td class="right">%s</td><td class="right bytes">%s</td><td class="right">%s</td></tr>\n'%(
|
||||||
urllib.quote(fname),
|
urllib.parse.quote(fname),
|
||||||
fname_str,
|
fname_str,
|
||||||
fsstr,
|
fsstr,
|
||||||
fsstrb,
|
fsstrb,
|
||||||
@@ -160,7 +216,7 @@ def get_filelink(path,fname,images=False):
|
|||||||
def get_wget_lines(files):
|
def get_wget_lines(files):
|
||||||
wget = "\n<!--\n"
|
wget = "\n<!--\n"
|
||||||
for f in files:
|
for f in files:
|
||||||
wget += "#FILE %s\n"%(urllib.quote(f),)
|
wget += "#FILE %s\n"%(urllib.parse.quote(f),)
|
||||||
wget += "#WGET URL=[insert URL] && curl $URL | grep '^#FILE ' | cut -c7- | sed \"s,^,$URL,\" | xargs -n1 wget\n"
|
wget += "#WGET URL=[insert URL] && curl $URL | grep '^#FILE ' | cut -c7- | sed \"s,^,$URL,\" | xargs -n1 wget\n"
|
||||||
wget += "-->\n"
|
wget += "-->\n"
|
||||||
return wget
|
return wget
|
||||||
@@ -168,7 +224,7 @@ def get_wget_lines(files):
|
|||||||
|
|
||||||
def get_imagestr(fname):
|
def get_imagestr(fname):
|
||||||
return '<img src="%s" title="%s"/>'%(
|
return '<img src="%s" title="%s"/>'%(
|
||||||
urllib.quote(fname),
|
urllib.parse.quote(fname),
|
||||||
fname
|
fname
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -176,11 +232,102 @@ def get_pathlink(path,dname):
|
|||||||
fdate = time.localtime(os.path.getmtime(os.path.join(path, dname)))
|
fdate = time.localtime(os.path.getmtime(os.path.join(path, dname)))
|
||||||
fdstr = time.strftime("%Y/%m/%d %H:%M:%S", fdate)
|
fdstr = time.strftime("%Y/%m/%d %H:%M:%S", fdate)
|
||||||
return '<tr><td><a class="link_dir" href="%s">↳ %s/</a><td class="right">[DIR]</td><td class="right bytes">0</td><td class="right">%s</td></tr>\n'%(
|
return '<tr><td><a class="link_dir" href="%s">↳ %s/</a><td class="right">[DIR]</td><td class="right bytes">0</td><td class="right">%s</td></tr>\n'%(
|
||||||
urllib.quote(dname),
|
urllib.parse.quote(dname),
|
||||||
dname,
|
dname,
|
||||||
fdstr
|
fdstr
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_password_page(secret):
|
||||||
|
return """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
|
<link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon">
|
||||||
|
<title>Password required</title>
|
||||||
|
<script language="JavaScript">
|
||||||
|
Object.defineProperty(String.prototype, 'hashCode', {
|
||||||
|
value: function() {
|
||||||
|
var hash = 0, i, chr;
|
||||||
|
for (i = 0; i < this.length; i++) {
|
||||||
|
chr = this.charCodeAt(i);
|
||||||
|
hash = ((hash << 5) - hash) + chr;
|
||||||
|
hash |= 0; // Convert to 32bit integer
|
||||||
|
}
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
function scramble(p,t) {
|
||||||
|
p = p.padEnd(t.length, p);
|
||||||
|
t = t.padEnd(p.length,' ');
|
||||||
|
var sstr = '';
|
||||||
|
for (var i=0; i < t.length; i++) {
|
||||||
|
sstr += String.fromCharCode(p.charCodeAt(i) + t.charCodeAt(i));
|
||||||
|
}
|
||||||
|
return sstr;
|
||||||
|
}
|
||||||
|
function unscramble(p,t) {
|
||||||
|
p = p.padEnd(t.length, p);
|
||||||
|
t = t.padEnd(p.length, ' ');
|
||||||
|
var sstr = '';
|
||||||
|
for (var i=0; i < t.length; i++) {
|
||||||
|
sstr += String.fromCharCode(t.charCodeAt(i) - p.charCodeAt(i));
|
||||||
|
}
|
||||||
|
return sstr;
|
||||||
|
}
|
||||||
|
function pw_event(element) {
|
||||||
|
if(event.key === 'Enter') {
|
||||||
|
pw(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function pw(element) {
|
||||||
|
var pw = "SECRET";
|
||||||
|
if (element.value) {
|
||||||
|
pw = pw.split(':',2);
|
||||||
|
if (element.value.hashCode() == parseInt(pw[0])) {
|
||||||
|
this.location.href = unscramble(element.value, atob(pw[1]));
|
||||||
|
} else {
|
||||||
|
document.getElementById("message").innerHTML = "No match";
|
||||||
|
setTimeout(clear_message, 5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function clear_message() {
|
||||||
|
document.getElementById("message").innerHTML = '';
|
||||||
|
}
|
||||||
|
function newPass(password, target) {
|
||||||
|
console.log(
|
||||||
|
' var pw = "' + password.hashCode().toString() + ':' +
|
||||||
|
btoa(scramble(password, target)) + '";'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
//console.log("Use newPass('pass','page.html'); to get a new hash");
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
#content {
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 4rem;
|
||||||
|
font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
#message {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
#button {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="content">
|
||||||
|
<input type="password" placeholder="Password" class="password" onkeydown="pw_event(this)" id="password" onFocus="this.select()" autofocus/>
|
||||||
|
<br><button id="button" type="button" onclick="pw(document.getElementById('password'));">Enter</button>
|
||||||
|
<div id="message"></div>
|
||||||
|
<noscript><br>Javascript is required to access this area. Yours seems to be disabled.</noscript>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>""".replace("SECRET",secret)
|
||||||
|
|
||||||
def get_header(opts):
|
def get_header(opts):
|
||||||
header='''<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
header='''<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
||||||
<html>
|
<html>
|
||||||
|
|||||||
Reference in New Issue
Block a user