Compare commits

...

10 commits

11 changed files with 556 additions and 62 deletions

1
.gitignore vendored
View file

@ -3,3 +3,4 @@
*.pyc
*.pid
*.log
.unison*

491
brain.py
View file

@ -1,28 +1,191 @@
import os, sys
from git import *
import bottle
from bottle import default_app, route, run, request, template, static_file, redirect
from bottle import default_app, get, post, route, run, request, template, static_file, redirect
from os.path import isdir
from string import lower, split
from urllib import unquote
import string
from urllib import unquote, urlopen
from urlparse import urlparse
import shutil
from datetime import datetime
import re
import Image
from markdown2 import markdown
import MySQLdb
from contextlib import closing
import random
try:
conn = MySQLdb.connect( host = "localhost",
user = "brain",
passwd = "horsebatteries",
db = "brain")
cursor = conn.cursor()
except:
print 'Cannot connect to database.'
conf = {}
conf['repo'] = '/srv/git/documents'
conf['ext_bundles'] = ['.pages', '.sparsebundle']
conf['ext_render'] = ['.md','.txt', '.jpg']
conf['ext_render'] = ['.md','.txt','.jpg','.gif','.png']
conf['ext_edit'] = ['.md','.txt','.rb','.py','.pl','.sh']
def get_secrets(url = False):
def generate_query():
if url:
query = 'SELECT base_url,username,password,id FROM `secrets` WHERE base_url LIKE "%' + url + '";'
else:
query = 'SELECT base_url,username,password,id FROM `secrets`;'
return query
cursor.execute(generate_query())
secrets = cursor.fetchall()
if not len(secrets) > 0:
print 'No secrets for ' + url + ' expanding search.'
url = get_domain(url)
print 'Trying ' + url + '.'
cursor.execute(generate_query())
secrets = cursor.fetchall()
return secrets
def get_domain(base_url):
with closing(urlopen('https://mxr.mozilla.org/mozilla/source/netwerk/dns/src/effective_tld_names.dat?raw=1')) as tldFile:
tlds = [line.strip() for line in tldFile if line[0] not in "/\n"]
urlElements = base_url.split('.')
for i in range(-len(urlElements),0):
lastIElements = urlElements[i:]
candidate = ".".join(lastIElements)
wildcardCandidate = ".".join(["*"]+lastIElements[1:])
exceptionCandidate = "!"+candidate
if (exceptionCandidate in tlds):
return ".".join(urlElements[i:])
if (candidate in tlds or wildcardCandidate in tlds):
return ".".join(urlElements[i-1:])
return base_url
def mkpass(size=10):
validChars = string.ascii_letters + string.digits
validChars = validChars.strip("oO01l")
return string.join([random.choice(validChars) for x in range(size)],"")
def mkshort(size=4):
validChars = string.ascii_letters + string.digits
validChars = validChars.strip("oO01l")
return string.join([random.choice(validChars) for x in range(size)],"")
def get_generated(domain):
get = 'SELECT password FROM secrets_gen WHERE base_url LIKE "%'+get_domain(domain)+'%" LIMIT 1'
create = 'INSERT INTO secrets_gen (base_url, password) VALUES ("'+get_domain(domain)+'","'+mkpass()+'")'
cursor.execute(get)
gen = cursor.fetchone()
if not gen:
cursor.execute(create)
cursor.execute(get)
gen = cursor.fetchone()
return str(gen[0])
def sanitize_path(path):
return path.lstrip('./')
def render_file(name,public=False):
path = os.path.join(conf['repo'], name)
filename, extension = os.path.splitext(name)
filename = os.path.basename(name)
if isdir(path) and lower(extension) in conf['ext_bundles']:
d = datetime.now()
basedir = re.sub(filename+extension, '', path)
rootdir = filename+extension
z = shutil.make_archive('/tmp/' + unquote(filename) + '_' + \
d.strftime("%Y%M%d%H%M%S"), "zip", basedir, rootdir )
return static_file(os.path.basename(z), root='/tmp')
if lower(extension) in conf['ext_render']:
try:
img = Image.open(path)
width, height = img.size
if width > 1024 or height > 1024:
if height > width:
h = 1024
w = round(1024 * (float(width)/float(height)))
else:
if height < width:
h = round(1024 * (float(height)/float(width)))
w = 1024
else:
h = 1024
w = 1024
else:
h = height
w = width
content = '<img height="'+str(h)+'" width="'+str(w)+'" src="/raw/'+sanitize_path(name)+'" />'
except:
try:
o = open(path,'r')
content = markdown(o.read())
o.close()
content += '<p><a href="/edit/'+sanitize_path(name)+'">Edit</a> - '
content += '<a href="/raw/'+sanitize_path(name)+'">Raw</a></p>'
except:
content = "<p>This cannot be rendered.</p>"
if sanitize_path(os.path.dirname(name)):
content += '<p>Back to <a href="/repo/'+sanitize_path(os.path.dirname(name))\
+'">'+os.path.dirname(name)+'</a></p>'
return template('templates/repo', content = content, \
title=filename)
else:
return static_file(unquote(name), root=conf['repo'])
# Return the static assets.
@route('/static/<name:path>')
def static(name=''):
return static_file(name, root='./static/')
# Traverse the repository
@route('/repo')
@route('/repo/')
@route('/repo/<name:path>')
@ -84,58 +247,18 @@ def public(name=''):
tree.append('<a href="/new/'+sanitize_path(name)+'">New File</a> ')
tree.append('<a href="/mkdir/'+sanitize_path(name)+'">New Dir</a></p>')
return template('templates/yield', content="\n".join(tree), title="Files in "+sanitize_path(name))
return template('templates/repo', content="\n".join(tree), title="Files in "+sanitize_path(name))
else:
if isdir(path) and lower(extension) in conf['ext_bundles']:
print "This is a bundle: " + sanitize_path(name)
d = datetime.now()
basedir = re.sub(filename+extension, '', path)
rootdir = filename+extension
z = shutil.make_archive('/tmp/' + unquote(filename) + '_' + \
d.strftime("%Y%M%d%H%M%S"), "zip", basedir, rootdir )
return static_file(os.path.basename(z), root='/tmp')
if lower(extension) in conf['ext_render']:
print "This is a render: " + sanitize_path(name)
try:
Image.open(path)
content = '<img src="/raw/'+sanitize_path(name)+'"/>'
except:
try:
o = open(path,'r')
content = markdown(o.read())
o.close()
content += '<p><a href="/edit/'+sanitize_path(name)+'">Edit</a> - \
<a href="/raw/'+sanitize_path(name)+'">Raw</a></p>'
except:
content = "<p>This cannot be rendered.</p>"
return template('templates/yield', content = content, \
title=filename+extension)
else:
print "This is a file: " + unquote(name)
return static_file(unquote(name), root=conf['repo'])
return render_file(name)
else:
return 'File does not exist.'
# Raw access to files.
@route('/raw/<path:path>')
def raw(path=False):
try:
@ -144,7 +267,10 @@ def raw(path=False):
return 'No such file'
@route('/upload/:path', method="GET")
# Uploading files.
@route('/upload/<path:path>', method="GET")
@route('/upload/', method="GET")
@route('/upload', method="GET")
def upload(path=''):
@ -176,7 +302,7 @@ def upload(path=''):
body.append('</form>')
return template('templates/yield', content='\n'.join(body), \
return template('templates/repo', content='\n'.join(body), \
title='Add')
@ -228,9 +354,12 @@ def do_upload():
content.append('<p><a href="/upload">Upload another.</a></p>')
return template('templates/yield', content='\n'.join(content), title='"'+filename+'" uploaded.')
return template('templates/repo', content='\n'.join(content), title='"'+filename+'" uploaded.')
# Deleting files.
@route('/delete', method="GET")
@route('/delete/<path:path>', method="GET")
def delete(path = ''):
@ -260,7 +389,7 @@ def delete(path = ''):
body.append('</table>')
body.append('</form>')
return template('templates/yield', content='\n'.join(body), title='Delete')
return template('templates/repo', content='\n'.join(body), title='Delete')
@route('/delete', method='POST')
@ -293,9 +422,12 @@ def delete():
body.append('<p>'+path+' does not exist.</p>')
return template('templates/yield', content='\n'.join(body), title=path+' deleted.')
return template('templates/repo', content='\n'.join(body), title=path+' deleted.')
# And if we want to edit a file?
@route('/edit/<path:path>', method='GET')
@route('/edit', method="GET")
def edit(path=''):
@ -344,7 +476,7 @@ def edit(path=''):
body.append('</table>')
body.append('</form>')
return template('templates/yield', content='\n'.join(body), \
return template('templates/repo', content='\n'.join(body), \
title='New')
@ -378,9 +510,14 @@ def edit():
else:
body.append('<p>' + path + ' not changed.</p>')
return template('templates/yield', content='\n'.join(body),\
return template('templates/repo', content='\n'.join(body),\
title=path+' edited.')
# Let's add some functionality to make new files.
@route('/new', method='GET')
@route('/new/<path:path>', method='GET')
def new(path = ''):
@ -413,7 +550,7 @@ def new(path = ''):
body.append('</form>')
return template('templates/yield', content='\n'.join(body), \
return template('templates/repo', content='\n'.join(body), \
title='New')
@route('/new', method="POST")
@ -450,10 +587,14 @@ def new():
if commit:
body.append('<p>Git: '+commit+'</p>')
return template('templates/yield', content='\n'.join(body), \
return template('templates/repo', content='\n'.join(body), \
title=filename + ' created')
# Let's add in some functionality to move files.
@route('/move/<path:path>', method="GET")
@route('/move', method="GET")
def rename(path = ''):
@ -483,7 +624,7 @@ def rename(path = ''):
body.append('</form>')
return template('templates/yield', content='\n'.join(body),\
return template('templates/repo', content='\n'.join(body),\
title='move')
@route('/move', method="POST")
@ -518,9 +659,15 @@ def rename():
else:
body.append('<p>Must have both a current and new name.</p>')
return template('templates/yield', content='\n'.join(body),\
return template('templates/repo', content='\n'.join(body),\
title=request.forms.current+' moved to '+request.forms.destination)
# What if we want to make a new directory?
@route('/mkdir/<path:path>', method="GET")
@route('/mkdir', method="GET")
def mkdir(path=''):
@ -545,7 +692,7 @@ def mkdir(path=''):
body.append('</form>')
return template('templates/yield', content='\n'.join(body),\
return template('templates/repo', content='\n'.join(body),\
title='Create Directory')
@route('/mkdir', method="POST")
@ -565,15 +712,236 @@ def mkdir():
body.append('<p>Path exists.</p>')
return template('templates/yield', content='\n'.join(body),\
return template('templates/repo', content='\n'.join(body),\
title='Create Directory')
# Let's build in the url shortening functionality.
@get('/short/<short>')
def short(short = ''):
body = []
if short:
query = 'SELECT url,public FROM short WHERE `key` = "'+short+'" LIMIT 1;'
cursor.execute(query)
url = cursor.fetchone()
if url:
redirect(url[0])
@get('/short/')
@get('/short')
def mkshort(short = ''):
body = []
body.append("<h3>Create Shortened URL.</h3>")
body.append('<form method="POST" action="/short">')
body.append('<input type="submit">')
body.append('</form>')
return template('templates/public', content='\n'.join(body),\
title='Create Shortened URL' + short)
# Let's make a place to store my secrets
# A big list of secrets, need a special password'
@post('/secrets/list')
@get('/secrets/list')
@get('/secrets/list/')
def secret_list(path = ''):
body = []
if request.forms.password == "allyourbasearebelongtous":
secrets = get_secrets()
for s in secrets:
body.append(s[0]+': '+s[1]+' | '+s[2])
body.append('<a href="/secrets/trash/'+str(s[3])+'">(x)</a>')
body.append('<br/>')
else:
body.append('<form method="post" action="/secrets/list" />')
body.append('<p>Are you sure?</p>')
body.append('<table>')
body.append('<tr><td>Password:</td>')
body.append('<td><input type="password" name="password" /></td></tr>')
body.append('<td><td><input type="submit" /></td></tr>')
body.append('</table>')
body.append('</form>')
return template('templates/secret', content = '\n'.join(body), title = 'Secrets List')
# Show the secrets associated with a specific site.
@get('/secrets/show/<url>')
@get('/secrets/show/')
@get('/secrets/show')
def secret_lookup(url = False):
body = []
if url:
secrets = get_secrets(url)
for s in secrets:
body.append(s[0]+': '+s[1]+' | '+s[2])
body.append('<a href="/secrets/trash/'+str(s[3])+'">(x)</a>')
body.append('<br/>')
else:
body.append('Must provide a url.')
return template('templates/secret', content = '\n'.join(body), title = 'Secrets for ' + url)
# A form for trashing secrets.
@get('/secrets/trash/<key>')
@get('/secrets/trash/')
@get('/secrets/trash')
def secret_trash(key=False):
body = []
body.append('<form method="post" action="/secrets/trash/" >')
body.append('<p>Trash a secret</p>')
body.append('<table>')
body.append('<tr><td><label for="id">Secret id</label></td>')
body.append('<td><input name="id" ')
if key: body.append('value="'+key+'" ')
body.append('/></td></tr>')
body.append('<tr><td><input type="submit" /></td></tr>')
body.append('</table>')
return template('templates/secret', content = '\n'.join(body), title = 'Trash Secret')
# Actually trashing secrets.
@post('/secrets/trash')
@post('/secrets/trash/')
def secret_trash():
#query = 'SELECT base_url,username,password,id FROM `secrets` WHERE base_url LIKE "%' + url + '";'
#cursor.execute(query)
#secrets = cursor.fetchall()
original = 'SELECT id FROM `secrets` WHERE id = "' + str(request.forms.id) + '" LIMIT 1'
copy = 'INSERT INTO `secrets_trash` SELECT * FROM `secrets` WHERE id = "' + str(request.forms.id) + '"'
check = 'SELECT * FROM `secrets_trash` INNER JOIN `secrets` ON (secrets_trash.base_url = secrets.base_url AND secrets_trash.username = secrets.username AND secrets_trash.password = secrets.password)'
delete = 'DELETE FROM `secrets` WHERE id = "' + str(request.forms.id) + '" LIMIT 1'
body = []
if cursor.execute(original):
body.append('<p>Record exists.</p>')
cursor.execute(copy)
body.append('<p>Record copied.</p>')
if cursor.execute(check):
body.append('<p>Duplicate records exist.</p>')
cursor.execute(delete)
body.append('<p>Original record deleted.</p>')
if not cursor.execute(original):
body.append('<p>Original record does not exist.</p>')
body.append('<p>Record ' + str(request.forms.id) + ' trashed.</p>')
else:
body.append('<p>No such record.</p>')
return template('templates/secret', content = '\n'.join(body), title = 'Secret ' + str(request.forms.id) + ' trashed.')
# Create a new secret.
@get('/secrets/create/<url>')
@get('/secrets/create/')
@get('/secrets/create')
def secrets_create(url = False):
body = []
body.append('<h3>Create Secret</h3>')
body.append('<form method="POST" action="/secrets/create">')
body.append('<table>')
body.append('<tr><td><label for="url">URL:</label></td>')
body.append('<td><input name="url" ')
if url: body.append('value="'+url+'" ')
body.append('/></td></tr>')
body.append('<tr><td><label for="username">Username:</label></td>')
body.append('<td><input name="username" /></td></tr>')
body.append('<tr><td><label for="password">Password:</label></td>')
body.append('<td><input type="password" name="password" ')
if url: body.append('value="'+get_generated(url)+'" ')
body.append('/></td></tr>')
body.append('<tr><td><input type="submit" /></td></tr>')
body.append('</table>')
body.append('</form>')
return template('templates/secret', content='\n'.join(body), title = 'Create Secret')
# Create a new secret
@post('/secrets/create')
def create_secret():
body = []
query = 'INSERT INTO secrets (base_url, username, password) VALUES (' +\
'"'+request.forms.url+'", '+\
'"'+request.forms.username+'", '+\
'"'+request.forms.password+'")'
print query
if cursor.execute(query):
body.append('<p>Secret created.</p>')
body.append('<p>View secrets for <a href="/secrets/show/'+\
request.forms.url+'">'+request.forms.url+'</a>.</p>')
else:
body.append('Secret could not be created.')
return template('templates/secret', content='\n'.join(body), title = 'Create Secret')
@get('/secrets/overlay/<url>')
def secret_overlay(url):
body = []
body.append('<p>Secrets for: '+url+'</p>')
secrets = get_secrets(url)
for s in secrets:
body.append(s[0] + ': ' + s[1] + ' | ' + s[2] + '<br/>')
gen = get_generated(url)
body.append('<p>Generated: ' + gen + '</p>')
body.append('<form method="POST" action="/secrets/create" target="_blank">')
body.append('<table>')
body.append('<tr><td>URL:</td><td><input name="base_url" value="'+url+'" /></td></tr>')
body.append('<tr><td>U:</td><td><input name="username" /></td></tr>')
body.append('<tr><td>P:</td><td><input name="password" type="password" value="'+gen+'" /></td></tr>')
body.append('<tr><td><input type="submit" /></td></tr>')
body.append('</table>')
body.append('</form>')
return template('templates/overlay', content='\n'.join(body), title = url + ' overlay')
# An API will be needed for bookmarklets and whatnot.
@route('/api')
def api():
return 'Someday there will be an API here.'
# Do something with the home page.
@route('/')
def index():
redirect('/repo')
@ -583,5 +951,6 @@ def index():
application = bottle.default_app()
if __name__ == "__main__":
bottle.debug(True)
conf['repo'] = os.path.expanduser('~/dev/repo')
run(host="127.0.0.1", port=8000, reloader=True)
run(host='0.0.0.0', port=8000, reloader=True)

9
serve.sh Executable file
View file

@ -0,0 +1,9 @@
#!/bin/sh
while [ 1 ]
do
echo "Starting server."
python brain.py
echo "Server died.\nSleeping for 5 seconds."
sleep 5
done

15
static/brain.css Normal file
View file

@ -0,0 +1,15 @@
body, table {
background-color: #CCC;
color: #3c3c3c;
font-family: Helvetica;
font-size: 12px;
}
a {
color: #3c3c3c;
}
a:hover {
color: #800;
}

BIN
static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

77
static/overlay.js Normal file
View file

@ -0,0 +1,77 @@
(function() {
function cleanHouse() {
elements = document.querySelectorAll('.myS');
for (i=0; i<elements.length; i++) {
elements[i].parentNode.removeChild(elements[i]);
}
}
cleanHouse();
s=document.createElement('style');
s.id='myS-style';
s.type='text/css';
s.className+='myS';
s.innerHTML='\
.myS{\
font-family:Georgia;\
color:#3C3C3C;\
text-align:center;\
text-size:14px;\
}\
.myS iframe {\
border: none;\
}\
.myS p {\
color:#3C3C3C;\
padding:10px;\
}\
.myS a {\
text-decoration:none;\
color:#3C3C3C;\
}\
.myS a:hover{\
text-decoration:underline;\
}\
.myS a.close:hover {\
border:1px solid #F00;\
text-decoration:none;\
color:#F00;\
}\
';
document.body.appendChild(s);
o=document.createElement('div');
o.id='myS-overlay';
o.className+='myS';
o.style.position='fixed';
o.style.left=o.style.right=o.style.top=o.style.bottom='0%';
o.style.zIndex='1337';
o.style.backgroundColor='rgba(0,0,0,0.7)';
document.body.appendChild(o);
i=document.createElement('div');
i.id='myS-inner';
i.className+='myS';
i.style.position='relative';
i.style.margin='0em auto';
i.style.marginTop='20px';
i.style.backgroundColor='rgba(255,255,255,1)';
o.appendChild(i);
f=document.createElement('iframe');
f.id='myS-iframe';
f.className+='myS';
f.width=320;
f.height=320;
f.style.overflow='auto';
f.src='https://amdavidson.net/secrets/overlay/'+document.domain;
i.appendChild(f);
e=document.createElement('p');
e.className+='myS';
e.onclick=function(){cleanHouse();};
e.innerHTML='<a style="font-size:10pt;" class="myS close">Close</a>';
i.appendChild(e);
})();

View file

@ -1,6 +1,8 @@
<html>
<head>
<title>{{title or 'myStuff'}}</title>
<link rel="shortcut icon" href="/static/favicon.ico" />
<link rel="stylesheet" type="text/css" href="/static/brain.css" />
</head>
<body>
%include

15
templates/overlay.tpl Normal file
View file

@ -0,0 +1,15 @@
<html>
<head>
<title>{{title or 'myStuff'}}</title>
<link rel="shortcut icon" href="/static/favicon.ico" />
<link rel="stylesheet" type="text/css" href="/static/brain.css" />
<style type="text/css">
body, table {
background: #FFF !important;
}
</style>
</head>
<body>
{{!content}}
</body>
</html>

3
templates/public.tpl Normal file
View file

@ -0,0 +1,3 @@
{{!content}}
%rebase templates/layout title=title

View file

@ -1,5 +1,5 @@
{{!content}}
<p><a href="/repo">Back to Repo</a></p>
<p>Back to <a href="/repo">Repo</a></p>
%rebase templates/layout title=title

3
templates/secret.tpl Normal file
View file

@ -0,0 +1,3 @@
{{!content}}
%rebase templates/layout title=title