Initial commit, just gotta fix README

This commit is contained in:
Robert Miles 2018-06-18 19:16:12 -04:00
commit 89a5fe9824
16 changed files with 2873 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*.pyc
__pycache__/*

3
Makefile Normal file
View File

@ -0,0 +1,3 @@
.PHONY: test
test:
sudo ncat -l 80 -k --sh-exec "./serve"

3
README.md Normal file
View File

@ -0,0 +1,3 @@
# PyServ - Python server (use with `ncat` or similar)
A Python framework for HTTP servers. Requires ncat or similar.

5
blog/README.md Normal file
View File

@ -0,0 +1,5 @@
# MineRobber's blog generator
Made with Python, love, care, and [markdown2](https://github.com/trentm/python-markdown2)!
##Configuration
It's all configured already.

66
blog/blog.py Normal file
View File

@ -0,0 +1,66 @@
import cPickle, subprocess, tempfile, markdown2, config, time, argparse, tempfile, os;
#print("MineRobber's blog generator")
#print("------------------------")
try:
blog_posts = cPickle.load(open("posts.pickled","rb"))
except:
blog_posts = []
parser = argparse.ArgumentParser(description="A blog generator.")
parser.add_argument("--workflow",help="Run the basic workflow (write a new post, generate the page, and quit)",action="store_true")
parser.add_argument("--generate",help="Generate the page and quit",action="store_true")
args = parser.parse_args()
if not args.workflow and not args.generate:
print("MineRobber's blog generator")
print("------------------------")
print("'n' for new post, 'g' to generate, 'q' to quit.")
running = True;
def tweetlink():
return config.tweetlink_do and config.tweetlink_location and config.tweetlink_url
def loop(choice):
if choice.lower() == "n":
title = raw_input("Title: ")
bodyf = tempfile.NamedTemporaryFile()
os.system("nano {}".format(bodyf.name))
body = markdown2.markdown(bodyf.read().rstrip())
bodyf.close()
blog_posts.append([title, body])
elif choice.lower() == "g":
rss = "<?xml version='1.0' encoding='UTF-8'?><rss version='2.0'><channel><title>"+config.title+"</title><link>"+config.url+"</link><description>"+config.desc+"</description>"
contents = "<html><head><title>{0}</title><link rel='stylesheet' href='{1}'></head><body><h1>{0}</h1>".format(config.title,config.css_location)
blog_posts.reverse()
i = len(blog_posts)
for post in blog_posts:
contents += "<h2 id='post"+str(i)+"'>"+post[0]+"</h2>"+post[1]
if tweetlink():
contents += "<p><a href='{}'>Tweet this</a></p>".format(config.tweetlink_url+"#post"+str(i))
rss += "<item><title>{0}</title><link>{3}#post{1!s}</link><description>{2}</description></item>".format(post[0],i,post[1][3:-5].replace("<","&lt;").replace(">","&gt;"),config.url)
i -= 1
blog_posts.reverse()
# remove footer
# contents += "<hr><p>Generated by <a href='https://github.com/MineRobber9000/blog-gen'>MineRobber's Python blog generator.</a><br><a href='{}'>RSS Feed</a></p>".format(config.rss_url)
contents += "</body></html>"
rss += "</channel></rss>"
with open(config.location,"wb") as f:
f.write(contents);
with open(config.rss_location,"wb") as f2:
f2.write(rss)
if tweetlink():
with open(config.tweetlink_location,"wb") as f3:
f3.write("<script>document.location='https://twitter.com/home?status={}'+document.location.hash.split('').slice(1).join('')</script>".format(config.tweetlink_format).format(config.title,config.url))
elif choice.lower() == "q":
return False
else:
print("Unknown option: "+choice)
cPickle.dump(blog_posts,open("posts.pickled","wb"))
return True
if not args.workflow and not args.generate:
while running:
running = loop(raw_input("> "))
print("bye")
elif not args.workflow and args.generate:
loop("g")
loop("q")
else:
loop("n")
loop("g")
loop("q")

12
blog/config.py Normal file
View File

@ -0,0 +1,12 @@
title = "Minecraft News"
url = "http://mcupdate.tumblr.com"
location = "/home/minerobber/mcuserv/mcus/index.html"
rss_location = "/home/minerobber/mcuserv/mcus/feed.xml"
rss_url = "http://mcupdate.tumblr.com/feed.xml"
desc = "Minecraft News replacement with static files"
css_location = "http://mcupdate.tumblr.com/index.css"
tweetlink_do = False
tweetlink_location = ""
tweetlink_url = ""
tweetlink_format = "I just read an article from \"{}\":"
tweetlink_format += " {}%23" # don't change this!

2467
blog/markdown2.py Normal file

File diff suppressed because it is too large Load Diff

16
blog/posts.pickled Normal file
View File

@ -0,0 +1,16 @@
(lp1
(lp2
S'Test'
p3
accopy_reg
_reconstructor
p4
(cmarkdown2
UnicodeWithAttrs
p5
c__builtin__
unicode
p6
V<p>This is a test, stupid.</p>\u000a
tRp7
aa.

68
mcus.py Normal file
View File

@ -0,0 +1,68 @@
import pyserv, sys
# Adding response headers to this list will put them in any response that doesn't need specific headers.
DEFAULT_RESPONSE_HEADERS = dict(Server=pyserv.VERSION)
ending_mimes = {}
ending_mimes["html"] = "text/html"
ending_mimes["css"] = "text/css"
ending_mimes["png"] = "image/png"
class TestServ(pyserv.HTTPServer):
def __init__(self):
# Initialize base class.
super().__init__()
# Register GET and POST methods.
self.register("GET",self.do_GET)
self.register("POST",self.do_POST)
# Add registries for other reasons
self.contents = dict()
self.mime = dict()
self.cgi = dict()
self.post = dict()
# Add index.html content to be statically served.
# with open("mcus.html") as f:
# self.registerPage("/",[l.rstrip() for l in f],"text/html")
# self.registerCGI("/",lambda x,y: [m.replace("PATH",x) for m in self.contents["/"]])
def registerPage(self,path,content,mime="text/plain"):
self.contents[path] = content
self.mime[path] = mime
def registerCGI(self,path,func):
self.cgi[path]=func
def registerPost(self,path,func):
self.post[path] = func
# def greetingform(self,path,headers,data):
# with open("greetings.html") as f:
# return 200, DEFAULT_RESPONSE_HEADERS, [l.rstrip().format(pyserv.lazySan(data['name'][0] if data.get('name') else "stranger")) for l in f]
def do_GET(self,method,path,headers):
resp_headers = dict(Server=pyserv.VERSION)
# with open("mcus.html") as f:
# contents = [l.rstrip() for l in f]
print("GET {}".format(path),file=sys.stderr)
path = path if path!="/" else "index.html"
try:
with open("mcus/{}".format(path)) as f:
contents = [f.read()]
if path.split(".")[-1] in ending_mimes:
resp_headers["Content-Type"] = ending_mimes[path.split(".")[-1]]
except FileNotFoundError:
return pyserv.abort(404)
return self.formatResponse(200,resp_headers,contents) # Send contents along.
def do_POST(self,m,path,headers,data):
if path in self.post:
try:
# CGI functions return response code, response headers, and contents.
respcode, resp_headers, contents = self.post[path](path,headers,pyserv.unquote(data))
# This data is directly passed into formatResponse to get a formatted response.
return self.formatResponse(respcode,resp_headers,contents)
except pyserv.Abort404: # Abort404 error is a signal
return pyserv.abort(404)
except: # If something else goes wrong, it's an internal error.
return pyserv.abort(500)
return pyserv.abort(405) # If path isn't in self.post, we aren't going to bother asking GET for it.

BIN
mcus/bg_main.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 B

1
mcus/feed.xml Normal file
View File

@ -0,0 +1 @@
<?xml version='1.0' encoding='UTF-8'?><rss version='2.0'><channel><title>Minecraft News</title><link>http://mcupdate.tumblr.com</link><description>Minecraft News replacement with static files</description><item><title>Test</title><link>http://mcupdate.tumblr.com#post1</link><description>This is a test, stupid.</description></item></channel></rss>

85
mcus/i.html Normal file

File diff suppressed because one or more lines are too long

35
mcus/index.css Normal file
View File

@ -0,0 +1,35 @@
body {
font-family: sans-serif;
background-color: #222222;
background-image: url('https://static.tumblr.com/qfn3sex/ezom7y7iq/bg_main.png');
color: #e0d0d0;
}
a {
color: #aaaaff;
}
hr {
border: 0;
color: #111111;
background-color: #111111;
height: 2px;
}
h3 {
color: #ffffff;
font-size: 16px;
}
img {
border:0;
margin:0;
}
.sidebar {
padding:10px;
vertical-align: top;
}
.alert {
background-color:#aa0000;
color:#ffffff;
font-weight:bold;
padding: 6px 10px;
width:500px;
}

2
mcus/index.html Normal file
View File

@ -0,0 +1,2 @@
<html><head><title>Minecraft News</title><link rel='stylesheet' href='http://mcupdate.tumblr.com/index.css'></head><body><h1>Minecraft News</h1><h2 id='post1'>Test</h2><p>This is a test, stupid.</p>
</body></html>

66
pyserv.py Normal file
View File

@ -0,0 +1,66 @@
from urllib.parse import parse_qs as upunquote
VERSION = "PyServ Beta 0.1"
class Abort404(Exception):
"""Signals for a 404 error, if listened for"""
def __init__(self,*args):
super().__init__(*args)
# Unquotes query strings.
def unquote(*args):
return upunquote(*args)
# Lazy text sanitizer.
def lazySan(s):
return s.replace("<","&lt;").replace("&","&amp;")
class HTTPServer:
"""HTTP Server base class"""
def __init__(self):
self.methods = dict()
"""Registers function to handle requests to a method"""
def register(self,method,f):
self.methods[method]=f
"""Returns whether a function is set to handle the given method"""
def hasMethod(self,method):
return method in self.methods.keys()
"""Returns a list of supported methods."""
def getMethods(self):
ret = list(self.methods.keys())
ret.sort()
return ret
"""Formats a response."""
def formatResponse(self,respcode,headers,content):
lines = []
lines.append("HTTP/1.1 {!s} {}".format(respcode,DEFAULT_MSGS[respcode]))
for h in headers:
lines.append("{}: {}".format(h,headers[h]))
lines.append("") # of course
for line in content:
lines.append(line)
return "\n".join(lines)+"\n"
# This function creates a stock error page.
# TODO: Replace DEFAULT_MSGS with maintained database
DEFAULT_MSGS = {100: "Continue",101: "Switching Protocols",102: "Processing",200: "Ok",201: "Created",202: "Accepted",203: "Non Authoritative Information",204: "No Content",205: "Reset Content",206: "Partial Content",207: "Multi Status",208: "Already Reported",226: "Im Used",300: "Multiple Choices",301: "Moved Permanently",302: "Found",303: "See Other",304: "Not Modified",305: "Use Proxy",307: "Temporary Redirect",308: "Permanent Redirect",400: "Bad Request",401: "Unauthorized",402: "Payment Required",403: "Forbidden",404: "Not Found",405: "Method Not Allowed",406: "Not Acceptable",407: "Proxy Authentication Required",408: "Request Timeout",409: "Conflict",410: "Gone",411: "Length Required",412: "Precondition Failed",413: "Request Entity Too Large",414: "Request Uri Too Long",415: "Unsupported Media Type",416: "Requested Range Not Satisfiable",417: "Expectation Failed",418: "I'm A Teapot",422: "Unprocessable Entity",423: "Locked",424: "Failed Dependency",426: "Upgrade Required",428: "Precondition Required",429: "Too Many Requests",431: "Request Header Fields Too Large",500: "Internal Server Error",501: "Not Implemented",502: "Bad Gateway",503: "Service Unavailable",504: "Gateway Timeout",505: "Http Version Not Supported",506: "Variant Also Negotiates",507: "Insufficient Storage",508: "Loop Detected",510: "Not Extended",511: "Network Authentication Required"}
def abort(errorcode,message=None,headers=dict()):
lines = []
if message is None:
message = DEFAULT_MSGS.get(errorcode,"Unknown response code")
lines.append("HTTP/1.1 {!s} {}".format(errorcode,message))
headers["Server"] = headers.get("Server",VERSION)
if errorcode!=204 and "Content-Type" not in headers:
headers["Content-Type"] = "text/html"
for h in headers:
lines.append("{}: {}".format(h,headers[h]))
if errorcode==204:
return "\n".join(lines)+"\n" # No content. Duh.
lines.append("")
lines.append("<h1>{!s} {}</h1>".format(errorcode, message))
lines.append("<hr>")
lines.append("<p>{}</p>".format(headers["Server"]))
return "\n".join(lines)+"\n"

42
serve Executable file
View File

@ -0,0 +1,42 @@
#!/usr/bin/python
import sys, pyserv, mcus
# Change this to change which server class gets used. Should be a subclass of pyserv.HTTPServer.
server = mcus.TestServ()
# Example registration (in server __init__ method)
# self.register("GET",lambda m,p,h: pyserv.abort(418))
# The above will make any GET request return the I'm a Teapot code.
# Distance from end of string to literal "HTTP". Should be 8 in all requests.
def httpDistance(l):
return (l[::-1].find("PTTH"))+len("PTTH")
# Read all lines of data sent to us by browser/client
inlines = []
a = sys.stdin.readline().rstrip()
while a:
inlines.append(a)
a = sys.stdin.readline().rstrip()
# Validate HTTP request
# In all valid HTTP requests, "HTTP/X.X" is 8 characters from the end.
if httpDistance(inlines[0])!=8:
print(pyserv.abort(400))
# Parse headers
headers = dict()
for l in inlines[1:]:
p = l.split(": ",1)
if p[0] in headers:
continue
headers[p[0]] = p[1]
# Parse request
method, path, version = inlines[0].split(" ")
# If the server doesn't have the method, it isn't implemented.
if not server.hasMethod(method):
print(pyserv.abort(501))
if method=="POST":
# Read query-string and call function
print(server.methods[method](method, path, headers, sys.stdin.read(int(headers["Content-Length"]))))
else:
# Just call the function
print(server.methods[method](method, path, headers))