First commit
This commit is contained in:
parent
6fef4d3fc6
commit
cdcc03d0bf
|
@ -0,0 +1,90 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
p = os.path.join
|
||||||
|
pUp = os.path.dirname
|
||||||
|
s = False
|
||||||
|
if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
|
||||||
|
s = os.path.realpath(sys.executable)
|
||||||
|
else:
|
||||||
|
s = os.path.realpath(__file__)
|
||||||
|
sp = pUp(s)
|
||||||
|
|
||||||
|
# script start
|
||||||
|
import socket
|
||||||
|
import threading
|
||||||
|
import queue
|
||||||
|
|
||||||
|
# IMPORTANT! Obtain locks in this order, if you need multiple at once:
|
||||||
|
# - clientDataLock
|
||||||
|
# - clientsLock
|
||||||
|
# - serverThreadsLock
|
||||||
|
# - fileLock
|
||||||
|
modulePath = p(sp,"modules")
|
||||||
|
|
||||||
|
mainQueue = queue.Queue()
|
||||||
|
clientsLock = threading.Lock()
|
||||||
|
clientID = 0
|
||||||
|
clients = {}
|
||||||
|
clientDataLock = threading.Lock()
|
||||||
|
clientData = {}
|
||||||
|
serverThreadsLock = threading.Lock()
|
||||||
|
serverThreads = []
|
||||||
|
fileLock = threading.Lock()
|
||||||
|
|
||||||
|
def runCode(str, lcs = False, description = "loose-code"):
|
||||||
|
if lcs == False: lcs = {}
|
||||||
|
code = compile(str,description,"exec")
|
||||||
|
exec(code,globals(),lcs)
|
||||||
|
return lcs
|
||||||
|
|
||||||
|
def runScript(sf, lcs = False):
|
||||||
|
if lcs == False: lcs = {}
|
||||||
|
|
||||||
|
code = False
|
||||||
|
with fileLock:
|
||||||
|
with open(sf,"r",encoding="utf-8") as script:
|
||||||
|
code = script.read()
|
||||||
|
|
||||||
|
runCode(code,lcs,sf)
|
||||||
|
return lcs
|
||||||
|
|
||||||
|
def readModuleFile(path):
|
||||||
|
with open(path,"r",encoding="utf-8") as modulesFile:
|
||||||
|
for line in modulesFile:
|
||||||
|
line = line.split("#",1)[0].strip(" \t\r\n")
|
||||||
|
if line == "": continue
|
||||||
|
modType = line.rsplit(".",1)[-1].lower()
|
||||||
|
line = line.replace("\\","/")
|
||||||
|
|
||||||
|
if modType == "mods":
|
||||||
|
print(">> " +line+ " <<")
|
||||||
|
else:
|
||||||
|
print("> " +line+ " ...")
|
||||||
|
|
||||||
|
line = line.replace("/",os.path.sep)
|
||||||
|
if line.startswith("." +os.path.sep):
|
||||||
|
line = pUp(path) + line[1:]
|
||||||
|
else:
|
||||||
|
line = p(modulePath,line)
|
||||||
|
|
||||||
|
if modType == "py":
|
||||||
|
runScript(line,locals())
|
||||||
|
|
||||||
|
if modType == "mods":
|
||||||
|
readModuleFile(line)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if os.path.isfile(p(modulePath,"main.mods")):
|
||||||
|
print("Loading modules...")
|
||||||
|
print(">> main.mods <<")
|
||||||
|
readModuleFile(p(modulePath,"main.mods"))
|
||||||
|
print("OK.\n")
|
||||||
|
|
||||||
|
with serverThreadsLock:
|
||||||
|
for server in servers:
|
||||||
|
makeServer(*server)
|
||||||
|
|
||||||
|
print("Serving!\n")
|
||||||
|
|
||||||
|
main()
|
|
@ -0,0 +1,6 @@
|
||||||
|
<html>
|
||||||
|
<head></head>
|
||||||
|
<body>
|
||||||
|
<h1>Hello world!</h1>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,2 @@
|
||||||
|
env["handler"] = handle404 # Always pretend as if the file wasn't found
|
||||||
|
env["htaccessPropagate"] = False # Do not read .fhtpyaccess files in sub-folders
|
|
@ -0,0 +1,6 @@
|
||||||
|
<html>
|
||||||
|
<head></head>
|
||||||
|
<body>
|
||||||
|
<h1>You shouldn't be able to access this page.</h1>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,33 @@
|
||||||
|
global json
|
||||||
|
import json
|
||||||
|
|
||||||
|
response = '''<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background-color: #000000;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
background-color: #222222;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>'''
|
||||||
|
|
||||||
|
for key in env:
|
||||||
|
value = str(env[key])
|
||||||
|
if value == "": value = " "
|
||||||
|
response += "<b>" +html.escape(key)+ ":</b><br>\n<code>" +html.escape(value)+ "</code><br>\n"
|
||||||
|
response += "</html>"
|
||||||
|
|
||||||
|
simpleResponse(
|
||||||
|
env["self"].connection,"200 OK",
|
||||||
|
{
|
||||||
|
"Content-Type": "text/html; charset=UTF-8",
|
||||||
|
},(response).encode("utf-8")
|
||||||
|
)
|
|
@ -0,0 +1,95 @@
|
||||||
|
global clientThreadIn
|
||||||
|
class clientThreadIn(threading.Thread):
|
||||||
|
def __init__(self,cID,connection,address):
|
||||||
|
threading.Thread.__init__(self)
|
||||||
|
self.cID = cID
|
||||||
|
self.connection = connection
|
||||||
|
self.address = address
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
try:
|
||||||
|
clientLoopIn(self)
|
||||||
|
except Exception as e:
|
||||||
|
handleException(e)
|
||||||
|
|
||||||
|
with clientDataLock:
|
||||||
|
with clientsLock:
|
||||||
|
closeClient(self.cID,0)
|
||||||
|
|
||||||
|
global clientThreadOut
|
||||||
|
class clientThreadOut(threading.Thread):
|
||||||
|
def __init__(self,cID,connection,address):
|
||||||
|
threading.Thread.__init__(self)
|
||||||
|
self.cID = cID
|
||||||
|
self.connection = connection
|
||||||
|
self.address = address
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
try:
|
||||||
|
clientLoopOut(self)
|
||||||
|
except Exception as e:
|
||||||
|
handleException(e)
|
||||||
|
|
||||||
|
with clientDataLock:
|
||||||
|
with clientsLock:
|
||||||
|
closeClient(self.cID,1)
|
||||||
|
|
||||||
|
global closeClient
|
||||||
|
def closeClient(cID,threadType = None):
|
||||||
|
try: # Close connection
|
||||||
|
clients[cID][0].close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try: # Set reference of connection to false, to denote the client is to not be served
|
||||||
|
clients[cID][0] = False
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try: # Set reference of the thread to false, to denote that it is closed
|
||||||
|
if threadType != None:
|
||||||
|
clients[cID][1 + threadType] = False
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try: # Get rid of leftover data to free memory
|
||||||
|
if clients[cID] == [False,False,False]:
|
||||||
|
del clients[cID]
|
||||||
|
del clientData[cID]
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
global setClientData
|
||||||
|
def setClientData(cID,key,data):
|
||||||
|
clientData[cID][key] = data
|
||||||
|
|
||||||
|
global getClientData
|
||||||
|
def getClientData(cID,key):
|
||||||
|
if not key in clientData[cID]: return None
|
||||||
|
return clientData[cID][key]
|
||||||
|
|
||||||
|
def main():
|
||||||
|
def onConnectionEvent(event,eEnv,connection,address):
|
||||||
|
with clientDataLock:
|
||||||
|
with clientsLock:
|
||||||
|
global clientID
|
||||||
|
clientID += 1
|
||||||
|
cID = str(clientID)
|
||||||
|
threadIn = clientThreadIn(cID,connection,address)
|
||||||
|
threadOut = False
|
||||||
|
if enableOutThread:
|
||||||
|
threadOut = clientThreadOut(cID,connection,address)
|
||||||
|
clients[cID] = [connection,threadIn,threadOut]
|
||||||
|
clientData[cID] = {"address":address}
|
||||||
|
threadIn.start()
|
||||||
|
if enableOutThread:
|
||||||
|
threadOut.start()
|
||||||
|
|
||||||
|
if clientDebug:
|
||||||
|
print("---")
|
||||||
|
print("Clients: " +str(len(clients)))
|
||||||
|
print("Threads: " +str(threading.active_count()))
|
||||||
|
return True
|
||||||
|
addEventHandler("onConnection",onConnectionEvent)
|
||||||
|
|
||||||
|
main()
|
|
@ -0,0 +1,11 @@
|
||||||
|
def main():
|
||||||
|
def onConnectionEvent(event,eEnv,connection,address):
|
||||||
|
count = 0
|
||||||
|
with clientDataLock:
|
||||||
|
for cID in clientData:
|
||||||
|
if getClientData(cID,"address") == address:
|
||||||
|
count += 1
|
||||||
|
if count >= maxConnections: return False
|
||||||
|
return True
|
||||||
|
addEventHandler("onConnection",onConnectionEvent)
|
||||||
|
main()
|
|
@ -0,0 +1,30 @@
|
||||||
|
global eventHandlers
|
||||||
|
eventHandlers = {}
|
||||||
|
|
||||||
|
global addEventHandler
|
||||||
|
def addEventHandler(event,handler):
|
||||||
|
if not event in eventHandlers: eventHandlers[event] = []
|
||||||
|
try:
|
||||||
|
eventHandlers[event].remove(handler)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
eventHandlers[event].append(handler)
|
||||||
|
|
||||||
|
global removeEventHandler
|
||||||
|
def removeEventHandler(event,handler):
|
||||||
|
if not event in eventHandlers: return
|
||||||
|
try:
|
||||||
|
eventHandlers[event].remove(handler)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
if len(eventHandlers[event]) == 0:
|
||||||
|
del eventHandlers[event]
|
||||||
|
|
||||||
|
global triggerEvent
|
||||||
|
def triggerEvent(event,*args,eEnv=False,**kwargs):
|
||||||
|
if not eEnv: eEnv = {}
|
||||||
|
if not event in eventHandlers: return
|
||||||
|
for func in eventHandlers[event]:
|
||||||
|
result = func(event,eEnv,*args,**kwargs)
|
||||||
|
if result == False: return False
|
||||||
|
return True
|
|
@ -0,0 +1,19 @@
|
||||||
|
global traceback
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
global excConnectionClosed
|
||||||
|
class excConnectionClosed(Exception): pass
|
||||||
|
|
||||||
|
global handleException
|
||||||
|
def handleException(e):
|
||||||
|
try:
|
||||||
|
if printExceptions:
|
||||||
|
print(traceback.format_exc())
|
||||||
|
except:
|
||||||
|
try:
|
||||||
|
print(e)
|
||||||
|
except:
|
||||||
|
try:
|
||||||
|
print("Printing exception failed!")
|
||||||
|
except:
|
||||||
|
pass
|
|
@ -0,0 +1,15 @@
|
||||||
|
global time
|
||||||
|
import time
|
||||||
|
|
||||||
|
global recv
|
||||||
|
def recv(conn,l):
|
||||||
|
start = time.process_time()
|
||||||
|
timeo = conn.gettimeout()
|
||||||
|
bytes = b""
|
||||||
|
while l > 0:
|
||||||
|
b = conn.recv(l)
|
||||||
|
if b == b"": raise ConnectionResetError
|
||||||
|
if time.process_time() - start > timeo: raise TimeoutError
|
||||||
|
bytes += b
|
||||||
|
l -= len(b)
|
||||||
|
return bytes
|
|
@ -0,0 +1,8 @@
|
||||||
|
global handle404
|
||||||
|
def handle404(env):
|
||||||
|
newPath = "/" + pathToURL(env["pathFixed"])
|
||||||
|
rawArgs = env["protocolHeaderList"][1].split("?",1)
|
||||||
|
|
||||||
|
if len(rawArgs) > 1:
|
||||||
|
newPath += "?" +rawArgs[-1]
|
||||||
|
notFound(env["self"].connection,newPath)
|
|
@ -0,0 +1,55 @@
|
||||||
|
global handleBinary
|
||||||
|
def handleBinary(env):
|
||||||
|
filePath = env["fPath"]
|
||||||
|
fileExt = env["fileExt"]
|
||||||
|
connection = env["self"].connection
|
||||||
|
length = 0
|
||||||
|
with fileLock:
|
||||||
|
length = os.path.getsize(filePath)
|
||||||
|
|
||||||
|
rangeDefined = False
|
||||||
|
rangeStart = 0
|
||||||
|
rangeEnd = None
|
||||||
|
if "range" in env["headerList"]:
|
||||||
|
rangeDefined = True
|
||||||
|
rangeStart,rangeEnd = getRange(env["headerList"]["range"])
|
||||||
|
|
||||||
|
rangeStart,rangeEnd = convertRanges(rangeStart,rangeEnd,length)
|
||||||
|
if rangeStart == None:
|
||||||
|
raise # tell the client the request is invalid
|
||||||
|
|
||||||
|
mimetype = "application/octet-stream"
|
||||||
|
if fileExt in mimetypesBinary:
|
||||||
|
mimetype = mimetypesBinary[fileExt]
|
||||||
|
|
||||||
|
if not rangeDefined:
|
||||||
|
simpleResponse(connection,"200 OK",{
|
||||||
|
"Content-Length": str(length),
|
||||||
|
"Content-Type": mimetype,
|
||||||
|
"Accept-Ranges": "bytes"
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
simpleResponse(connection,"206 Partial Content",{
|
||||||
|
"Content-Range": "bytes " +str(rangeStart)+ "-" +str(rangeEnd - 1)+ "/" +str(length),
|
||||||
|
"Content-Length": str(rangeEnd - rangeStart),
|
||||||
|
"Content-Type": mimetype,
|
||||||
|
"Accept-Ranges": "bytes"
|
||||||
|
})
|
||||||
|
|
||||||
|
print(rangeStart,rangeEnd)
|
||||||
|
cByte = rangeStart
|
||||||
|
while cByte < rangeEnd:
|
||||||
|
bytes = b""
|
||||||
|
rSize = readBufferSize
|
||||||
|
if cByte + rSize > rangeEnd:
|
||||||
|
rSize = rangeEnd - cByte
|
||||||
|
with fileLock:
|
||||||
|
with open(filePath,"rb") as file:
|
||||||
|
file.seek(cByte)
|
||||||
|
bytes = file.read(rSize)
|
||||||
|
connection.sendall(bytes)
|
||||||
|
cByte += rSize
|
||||||
|
|
||||||
|
fileHandlers[".*"] = handleBinary
|
||||||
|
for t in mimetypesBinary:
|
||||||
|
fileHandlers[t] = handleBinary
|
|
@ -0,0 +1,18 @@
|
||||||
|
def main():
|
||||||
|
def handleHTTP(event,eenv,env):
|
||||||
|
env["htaccessPropagate"] = True
|
||||||
|
paths = [indexPath] + env["lPath"].split(os.path.sep)[:-1]
|
||||||
|
pathl = []
|
||||||
|
for pathbit in paths:
|
||||||
|
pathl.append(pathbit)
|
||||||
|
path = p(*pathl,".fhtpyaccess")
|
||||||
|
if not os.path.isfile(path): continue
|
||||||
|
handlePYP(env,path)
|
||||||
|
if env["htaccessPropagate"] == False:
|
||||||
|
return True
|
||||||
|
|
||||||
|
addEventHandler("handleHTTP",handleHTTP)
|
||||||
|
|
||||||
|
main()
|
||||||
|
fileHandlers["fhtpyaccess"] = handle404
|
||||||
|
fileHandlers["htaccess"] = handle404
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,17 @@
|
||||||
|
global handlePYP
|
||||||
|
def handlePYP(env,cpath = False,getlock = True):
|
||||||
|
code = False
|
||||||
|
if not cpath: cpath = env["fPath"]
|
||||||
|
|
||||||
|
if getlock:
|
||||||
|
with fileLock:
|
||||||
|
with open(cpath,encoding="utf-8") as cfile:
|
||||||
|
code = cfile.read()
|
||||||
|
else:
|
||||||
|
with open(cpath,encoding="utf-8") as cfile:
|
||||||
|
code = cfile.read()
|
||||||
|
env = runCode(code,{"env":env},cpath)["env"]
|
||||||
|
fileHandlers["pyp"] = handlePYP
|
||||||
|
|
||||||
|
global indexFiles
|
||||||
|
indexFiles = ["index.pyp"] + indexFiles
|
|
@ -0,0 +1,19 @@
|
||||||
|
global handleText
|
||||||
|
def handleText(env):
|
||||||
|
data = b""
|
||||||
|
with fileLock:
|
||||||
|
with open(env["fPath"],"rb") as textFile:
|
||||||
|
data = textFile.read()
|
||||||
|
|
||||||
|
simpleResponse(
|
||||||
|
env["self"].connection,"200 OK",
|
||||||
|
{
|
||||||
|
"Content-Type": mimetypesText[env["fileExt"]]+ "; charset=UTF-8",
|
||||||
|
"Accept-Ranges": "bytes"
|
||||||
|
},data
|
||||||
|
)
|
||||||
|
|
||||||
|
for t in mimetypesText:
|
||||||
|
fileHandlers[t] = handleText
|
||||||
|
|
||||||
|
indexFiles.append("index.html")
|
|
@ -0,0 +1,211 @@
|
||||||
|
global time
|
||||||
|
import time
|
||||||
|
global urllib
|
||||||
|
import urllib.parse
|
||||||
|
global html
|
||||||
|
import html
|
||||||
|
|
||||||
|
global getHeaderFromConnection
|
||||||
|
def getHeaderFromConnection(connection):
|
||||||
|
start = time.process_time()
|
||||||
|
timeo = connection.gettimeout()
|
||||||
|
l = 0
|
||||||
|
nl = 0
|
||||||
|
header = ""
|
||||||
|
while True:
|
||||||
|
b = connection.recv(1)
|
||||||
|
if b == b"": raise ConnectionResetError
|
||||||
|
if time.process_time() - start > timeo: raise TimeoutError
|
||||||
|
l += 1
|
||||||
|
if l > maxHeaderLength:
|
||||||
|
connection.sendall("""\
|
||||||
|
HTTP 1.1 413 Payload Too Large\r
|
||||||
|
\r
|
||||||
|
""".encode("ascii"))
|
||||||
|
raise excConnectionClosed
|
||||||
|
|
||||||
|
bd = None
|
||||||
|
try:
|
||||||
|
bd = b.decode("ascii")
|
||||||
|
except:
|
||||||
|
connection.sendall("""\
|
||||||
|
HTTP 1.1 400 Bad Request\r
|
||||||
|
\r
|
||||||
|
""".encode("ASCII"))
|
||||||
|
raise excConnectionClosed
|
||||||
|
|
||||||
|
if bd == "\n":
|
||||||
|
nl += 1
|
||||||
|
if nl == 2:
|
||||||
|
return header
|
||||||
|
else:
|
||||||
|
if bd != "\r":
|
||||||
|
nl = 0
|
||||||
|
header += bd
|
||||||
|
|
||||||
|
global parseHeader
|
||||||
|
def parseHeader(headers):
|
||||||
|
headers = headers.replace("\r","").split("\n")
|
||||||
|
del headers[-1]
|
||||||
|
for i in range(len(headers)):
|
||||||
|
headers[i] = headers[i].strip(" \t")
|
||||||
|
|
||||||
|
mainHeader = headers.pop(0).split(" ")
|
||||||
|
index = 0
|
||||||
|
length = len(mainHeader)
|
||||||
|
while index < length:
|
||||||
|
val = mainHeader[index]
|
||||||
|
val = val.strip(" \t")
|
||||||
|
if val == "":
|
||||||
|
del mainHeader[index]
|
||||||
|
length -= 1
|
||||||
|
continue
|
||||||
|
index += 1
|
||||||
|
mainHeader[0] = mainHeader[0].lower()
|
||||||
|
mainHeader[-1] = mainHeader[-1].lower()
|
||||||
|
|
||||||
|
headerList = {}
|
||||||
|
for header in headers:
|
||||||
|
header = header.split(":",1)
|
||||||
|
if len(header) != 2: continue
|
||||||
|
headerKey = header[0].strip(" \t").lower()
|
||||||
|
headerValue = header[1].strip(" \t")
|
||||||
|
if headerKey in headerList:
|
||||||
|
headers[headerKey] += ", " +headerValue
|
||||||
|
else:
|
||||||
|
headerList[headerKey] = headerValue
|
||||||
|
|
||||||
|
return mainHeader,headerList
|
||||||
|
|
||||||
|
global parseHeaderPath
|
||||||
|
def parseHeaderPath(path):
|
||||||
|
path = path.split("?",1)
|
||||||
|
if len(path) < 2: path.append("")
|
||||||
|
args = {}
|
||||||
|
for arg in path[1].split("&"):
|
||||||
|
arg = arg.split("=",1)
|
||||||
|
if len(arg) < 2: arg.append("")
|
||||||
|
args[urllib.parse.unquote(arg[0]).lower()] = urllib.parse.unquote(arg[1])
|
||||||
|
return urllib.parse.unquote(path[0]),args
|
||||||
|
|
||||||
|
global fixUserPath
|
||||||
|
def fixUserPath(path):
|
||||||
|
path = path.replace("\\","/") # Replace backslash with forward slash
|
||||||
|
path = path.lstrip("/")
|
||||||
|
npath = ""
|
||||||
|
for pathbit in path.split("/"):
|
||||||
|
pathbit = pathbit.strip(" \t\r\n") # Remove spaces, tabs, line return, and new line
|
||||||
|
if pathbit in [".",".."]: # Remove . and ..
|
||||||
|
continue
|
||||||
|
npath += pathbit + "/"
|
||||||
|
npath = npath[:-1]
|
||||||
|
|
||||||
|
while "//" in npath: npath = npath.replace("//","/") # Remove double slashes
|
||||||
|
return npath
|
||||||
|
|
||||||
|
global simpleResponse
|
||||||
|
def simpleResponse(connection,status,headers = None,content = None,autolength = True):
|
||||||
|
if headers == None:
|
||||||
|
headers = {}
|
||||||
|
|
||||||
|
if not "Accept-Ranges" in headers:
|
||||||
|
headers["Accept-Ranges"] = "none"
|
||||||
|
|
||||||
|
if content != None and autolength == True:
|
||||||
|
headers["Content-Length"] = str(len(content))
|
||||||
|
|
||||||
|
response = 'HTTP/1.1 ' +status+ '\r\n'
|
||||||
|
for header in headers:
|
||||||
|
response += header + ": " +headers[header] + "\r\n"
|
||||||
|
response += "\r\n"
|
||||||
|
|
||||||
|
connection.sendall(response.encode("ascii"))
|
||||||
|
if content != None:
|
||||||
|
connection.sendall(content)
|
||||||
|
|
||||||
|
global refer
|
||||||
|
def refer(connection,path):
|
||||||
|
simpleResponse(
|
||||||
|
connection,"302 Found",
|
||||||
|
{
|
||||||
|
"Content-Type": "text/html; charset=ASCII",
|
||||||
|
"Location": path
|
||||||
|
},('''\
|
||||||
|
<html>
|
||||||
|
<head></head>
|
||||||
|
<body>
|
||||||
|
Referring you to <a href="''' +html.escape(path)+ '''">''' +html.escape(path)+ '''</a>...
|
||||||
|
</body>
|
||||||
|
</html>''').encode("ascii")
|
||||||
|
)
|
||||||
|
|
||||||
|
global notFound
|
||||||
|
def notFound(connection,path):
|
||||||
|
simpleResponse(
|
||||||
|
connection,"404 Not Found",
|
||||||
|
{
|
||||||
|
"Content-Type": "text/html; charset=ASCII"
|
||||||
|
},('''\
|
||||||
|
<html>
|
||||||
|
<head></head>
|
||||||
|
<body>
|
||||||
|
Not found: <a href="''' +html.escape(path)+ '''">''' +html.escape(path)+ '''</a>
|
||||||
|
</body>
|
||||||
|
</html>''').encode("ascii")
|
||||||
|
)
|
||||||
|
|
||||||
|
global pathToURL
|
||||||
|
def pathToURL(path):
|
||||||
|
path = path.split("/")
|
||||||
|
length = len(path)
|
||||||
|
index = 0
|
||||||
|
while index < length:
|
||||||
|
path[index] = urllib.parse.quote(path[index])
|
||||||
|
index += 1
|
||||||
|
path = "/".join(path)
|
||||||
|
return path
|
||||||
|
|
||||||
|
# Can return the following:
|
||||||
|
# positive integer, None: Send entire file content starting at arg 1
|
||||||
|
# negative integer, None: Send entire file content starting at file end + arg 1
|
||||||
|
# positive integer, positive integer: Send entire file, from arg 1 to arg 2, not including arg 2
|
||||||
|
global getRange
|
||||||
|
def getRange(range):
|
||||||
|
try:
|
||||||
|
range = range.split("=",1)
|
||||||
|
if range[0].strip("\t ") != "bytes": return None,None
|
||||||
|
range = range[1].split(",")[0].split("-")
|
||||||
|
range[0] = range[0].strip("\t ")
|
||||||
|
range[1] = range[1].strip("\t ")
|
||||||
|
if range[0] == "":
|
||||||
|
return 0 - int(range[1]),None
|
||||||
|
|
||||||
|
if range[1] == "":
|
||||||
|
return int(range[0]),None
|
||||||
|
|
||||||
|
return int(range[0]),int(range[1]) + 1
|
||||||
|
except:
|
||||||
|
return 0,None
|
||||||
|
|
||||||
|
global convertRanges
|
||||||
|
def convertRanges(rangeStart,rangeEnd,length):
|
||||||
|
# Convert given ranges into complete ranges
|
||||||
|
if rangeStart < 0:
|
||||||
|
rangeStart = length - rangeStart
|
||||||
|
rangeEnd = length
|
||||||
|
else:
|
||||||
|
if rangeEnd == None:
|
||||||
|
rangeEnd = length
|
||||||
|
|
||||||
|
# Check if the ranges make sense
|
||||||
|
if rangeStart < 0:
|
||||||
|
return None,None
|
||||||
|
|
||||||
|
if rangeEnd > length:
|
||||||
|
return None,None
|
||||||
|
|
||||||
|
if rangeStart > rangeEnd:
|
||||||
|
return None,None
|
||||||
|
|
||||||
|
# OK
|
||||||
|
return rangeStart,rangeEnd
|
|
@ -0,0 +1,11 @@
|
||||||
|
./settings.py # Settings
|
||||||
|
./helpers.py # Helper functions
|
||||||
|
./main.py # Main loop and functions
|
||||||
|
./404.py # 404 page
|
||||||
|
|
||||||
|
# File handlers:
|
||||||
|
./file-handlers/mimetypes.py # List of file endings and the mimetypes they belong to
|
||||||
|
./file-handlers/binary.py # Images, video, audio, executables, etc...
|
||||||
|
./file-handlers/text.py # HTML, XML, TXT, etc...
|
||||||
|
./file-handlers/pyp.py # pyp, fhttpy's script format
|
||||||
|
./file-handlers/htaccess.py # .fhtpyaccess - can be used to override handlers on an entire folder
|
|
@ -0,0 +1,85 @@
|
||||||
|
global email
|
||||||
|
import email.utils
|
||||||
|
|
||||||
|
global fileHandlers
|
||||||
|
fileHandlers = {}
|
||||||
|
|
||||||
|
global indexFiles
|
||||||
|
indexFiles = []
|
||||||
|
|
||||||
|
global pathHandlers
|
||||||
|
pathHandlers = {}
|
||||||
|
|
||||||
|
global clientLoopIn
|
||||||
|
def clientLoopIn(self):
|
||||||
|
env = {}
|
||||||
|
env["self"] = self
|
||||||
|
env["requestTime"] = time.time()
|
||||||
|
env["header"] = getHeaderFromConnection(self.connection)
|
||||||
|
env["protocolHeaderList"],env["headerList"] = parseHeader(env["header"])
|
||||||
|
env["cmd"] = env["protocolHeaderList"][0]
|
||||||
|
env["path"],env["args"] = parseHeaderPath(env["protocolHeaderList"][1])
|
||||||
|
env["pathFixed"] = fixUserPath(env["path"])
|
||||||
|
|
||||||
|
env["lPath"] = env["pathFixed"].replace("/",os.path.sep)
|
||||||
|
env["fPath"] = p(indexPath,env["lPath"])
|
||||||
|
env["fileExt"] = "."
|
||||||
|
|
||||||
|
if not env["pathFixed"] == "" and not os.path.isfile(env["fPath"]) and os.path.isdir(env["fPath"]) and env["pathFixed"][-1] != "/": env["pathFixed"] += "/" # This is dirty, since it possibly circumvents .fhtpyaccess (You can see if a folder exists or not by probing)
|
||||||
|
|
||||||
|
if "/" + env["pathFixed"] != env["path"]:
|
||||||
|
newPath = "/" + pathToURL(env["pathFixed"])
|
||||||
|
rawArgs = env["protocolHeaderList"][1].split("?",1)
|
||||||
|
|
||||||
|
if len(rawArgs) > 1:
|
||||||
|
newPath += "?" +rawArgs[-1]
|
||||||
|
|
||||||
|
refer(self.connection,newPath)
|
||||||
|
return
|
||||||
|
|
||||||
|
if env["pathFixed"] in pathHandlers:
|
||||||
|
pathHandlers[env["pathFixed"]](env)
|
||||||
|
return
|
||||||
|
|
||||||
|
if not os.path.isfile(env["fPath"]):
|
||||||
|
if not os.path.isdir(env["fPath"]):
|
||||||
|
handle404(env)
|
||||||
|
return
|
||||||
|
|
||||||
|
found = False
|
||||||
|
for file in indexFiles:
|
||||||
|
if os.path.isfile(p(env["fPath"],file)):
|
||||||
|
found = file
|
||||||
|
break
|
||||||
|
|
||||||
|
if found == False:
|
||||||
|
env["fileExt"] = ".d"
|
||||||
|
env["lPath"] = p(env["lPath"],".")
|
||||||
|
env["fPath"] = p(indexPath,env["lPath"])
|
||||||
|
else:
|
||||||
|
env["lPath"] = p(env["lPath"],found)
|
||||||
|
env["fPath"] = p(indexPath,env["lPath"])
|
||||||
|
lPathSplit = env["lPath"].rsplit(os.path.sep,1)[-1].rsplit(".",1)
|
||||||
|
if len(lPathSplit) > 1:
|
||||||
|
env["fileExt"] = lPathSplit[-1].lower()
|
||||||
|
else:
|
||||||
|
lPathSplit = env["lPath"].rsplit(os.path.sep,1)[-1].rsplit(".",1)
|
||||||
|
if len(lPathSplit) > 1:
|
||||||
|
env["fileExt"] = lPathSplit[-1].lower()
|
||||||
|
|
||||||
|
env["fPathDir"] = pUp(env["fPath"])
|
||||||
|
env["requestTimeFormatted"] = email.utils.formatdate(int(env["requestTime"])).replace("-0000","GMT")
|
||||||
|
|
||||||
|
env["handler"] = False
|
||||||
|
if env["fileExt"] in fileHandlers:
|
||||||
|
env["handler"] = fileHandlers[env["fileExt"]]
|
||||||
|
elif ".*" in fileHandlers:
|
||||||
|
env["handler"] = fileHandlers[".*"]
|
||||||
|
|
||||||
|
if triggerEvent("handleHTTP",env) == False: return
|
||||||
|
|
||||||
|
if env["handler"]:
|
||||||
|
env["handler"](env)
|
||||||
|
else:
|
||||||
|
handle404(env)
|
||||||
|
return
|
|
@ -0,0 +1,6 @@
|
||||||
|
global maxHeaderLength
|
||||||
|
maxHeaderLength = 4096
|
||||||
|
global indexPath
|
||||||
|
indexPath = p(sp,"index")
|
||||||
|
global readBufferSize
|
||||||
|
readBufferSize = 32768
|
|
@ -0,0 +1,8 @@
|
||||||
|
settings.py # User settings
|
||||||
|
helpers.py # Helper functions
|
||||||
|
events.py # Event/event handler implementation
|
||||||
|
exceptions.py # Handle exceptions, close connections
|
||||||
|
servers.py # Create sockets, optionally with SSL/TLS
|
||||||
|
connlimit.py # Optional: Limit the amount of connections made by one IP
|
||||||
|
clients.py # Create and remove client sessions and connections
|
||||||
|
http/main.mods # HTTP server
|
|
@ -0,0 +1,45 @@
|
||||||
|
global ssl
|
||||||
|
import ssl
|
||||||
|
|
||||||
|
global serverThread
|
||||||
|
class serverThread(threading.Thread):
|
||||||
|
def __init__(self,socket):
|
||||||
|
threading.Thread.__init__(self)
|
||||||
|
self.socket = socket
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
connection = False
|
||||||
|
address = False
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
connection,address = self.socket.accept()
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
connection.settimeout(timeout)
|
||||||
|
if not triggerEvent("onConnection",connection,address): raise excConnectionClosed
|
||||||
|
except Exception as e:
|
||||||
|
handleException(e)
|
||||||
|
try:
|
||||||
|
connection.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
global makeServer
|
||||||
|
def makeServer(host,port,https):
|
||||||
|
print("Opening " +str(host)+ ":" +str(port)+ " (" +str(https)+ ") ...")
|
||||||
|
serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
serverSocket.bind((host,port))
|
||||||
|
serverSocket.settimeout(5)
|
||||||
|
if https:
|
||||||
|
serverSocket = ssl.wrap_socket(
|
||||||
|
serverSocket,
|
||||||
|
server_side = True,
|
||||||
|
certfile = https,
|
||||||
|
ssl_version = ssl.PROTOCOL_TLS
|
||||||
|
)
|
||||||
|
serverSocket.listen(65535)
|
||||||
|
thread = serverThread(serverSocket)
|
||||||
|
serverThreads.append(thread)
|
||||||
|
thread.start()
|
|
@ -0,0 +1,17 @@
|
||||||
|
global servers
|
||||||
|
servers = [
|
||||||
|
# Host Port SSL Certificate
|
||||||
|
("127.0.0.1", 80, False),
|
||||||
|
# ("127.0.0.1", 443, "localhost.pem")
|
||||||
|
]
|
||||||
|
|
||||||
|
global timeout
|
||||||
|
timeout = 15 # Seconds until the connection should be timed out
|
||||||
|
global maxConnections
|
||||||
|
maxConnections = 50 # Maximum connections per IP, needs connlimit.py to be activated
|
||||||
|
global enableOutThread
|
||||||
|
enableOutThread = False # Use a seperate thread for data output?
|
||||||
|
global printExceptions
|
||||||
|
printExceptions = False # Print exceptions as they happen, enable if you're developing
|
||||||
|
global clientDebug
|
||||||
|
clientDebug = False # Print how many clients and threads there are
|
Loading…
Reference in New Issue