|
# The MIT License (MIT) |
|
# |
|
# Copyright © 2023 Vizonex |
|
# |
|
# Permission is hereby granted, free of charge, to any person |
|
# obtaining a copy of this software and associated documentation |
|
# files (the “Software”), to deal in the Software without restriction, |
|
# including without limitation the rights to use, copy, modify, merge, |
|
# publish, distribute, sublicense, and/or sell copies of the Software, |
|
# and to permit persons to whom the Software is furnished to do so, |
|
# subject to the following conditions: |
|
# The above copyright notice and this permission notice shall |
|
# be included in all copies or substantial portions of the Software. |
|
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, |
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES |
|
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
|
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, |
|
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, |
|
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR |
|
# OTHER DEALINGS IN THE SOFTWARE. |
|
|
|
|
|
from urllib.request import urlopen, Request |
|
from urllib import error |
|
|
|
from http.client import HTTPSConnection |
|
|
|
import zipfile |
|
import shutil |
|
import re |
|
import random |
|
import os |
|
import subprocess |
|
import pathlib |
|
import sys |
|
|
|
|
|
is_64bit :bool = (sys.maxsize > 2**32) |
|
|
|
# NOTE I ripped these from the same site that the Harvester Pentesting tool |
|
# uses you can find more useragents at the site: https://techblog.willshouse.com/2012/01/03/most-common-user-agents/ |
|
# if you wish to update or alter the useragents used... |
|
|
|
|
|
UA = ['Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36', |
|
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:107.0) Gecko/20100101 Firefox/107.0', |
|
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36', |
|
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36', |
|
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:108.0) Gecko/20100101 Firefox/108.0', |
|
'Mozilla/5.0 (X11; Linux x86_64; rv:107.0) Gecko/20100101 Firefox/107.0', |
|
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36', |
|
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.1 Safari/605.1.15', |
|
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:107.0) Gecko/20100101 Firefox/107.0'] |
|
|
|
|
|
RELEASE = re.compile(r"https://github.com/nodejs/llhttp/releases/expanded_assets/release/v([\d+\.]+)") |
|
|
|
# The reason to have a the same user-agent thorought is to make the |
|
# request look like it's coming from a legitimate user visiting the |
|
# page to install the latest release of llhttp and not a bot |
|
user_agent = random.choice(UA) |
|
|
|
url = "https://github.com/nodejs/llhttp/releases" |
|
|
|
|
|
|
|
def get_page() -> str: |
|
"""Gets the first page of releases for when the user is unsure about what to do...""" |
|
# NOTE The goal here is not to be sneaky which is why http/2 is not being used... |
|
# The only goal is to get the request being made to pass... |
|
|
|
headers = {"User-Agent":user_agent,"Accept":"text/html"} |
|
req = Request("https://github.com/nodejs/llhttp/releases",headers=headers,method="GET") |
|
|
|
try: |
|
r = urlopen(req) |
|
data = r.read() |
|
r.close() |
|
return data.decode("latin-1") |
|
|
|
except error.HTTPError as e: |
|
print("Error : BAD RESPONSE %i : Shutting down!" % e.code) |
|
r.close() |
|
sys.exit(1) |
|
|
|
|
|
def grab_latest_release(): |
|
# Find the lastest release... |
|
latest:str = max(RELEASE.findall(get_page())) |
|
# TODO VIZONEX Make a Release checker to see if were already up to date or not... |
|
return latest |
|
|
|
|
|
def install_llhttp(): |
|
"""Downlaods llhttp from its github releases into a zipfile, |
|
Returns filename and the version information""" |
|
download_path = "/nodejs/llhttp/zip/refs/tags/release/v" |
|
release = grab_latest_release() |
|
print(f"installing llhttp version : {release}") |
|
|
|
filename = f"llhttp_ver_{release}.zip" |
|
|
|
# The reason why I'm not using urllib here is that we don't have to |
|
# do anything extreme now that we have direct access to the download |
|
# that we want and we need to have control over how its downloaded |
|
# just incase were using a lower end device that runs as bad as a potato... |
|
"https://codeload.github.com/nodejs/llhttp/zip/refs/tags/release/v8.1.0" |
|
client = HTTPSConnection("codeload.github.com") |
|
|
|
link = download_path + release |
|
client.request("GET",link,headers={"User-Agent":user_agent}) |
|
|
|
resp = client.getresponse() |
|
if resp.status != 200: |
|
print(f"ERROR: [BAD STATUS] {resp.status}") |
|
print("shutting down...") |
|
client.close() |
|
sys.exit(1) |
|
|
|
print("Downloading...") |
|
with open(filename,"wb") as download: |
|
while b := resp.read(1024): |
|
download.write(b) |
|
print("Download Complete!") |
|
|
|
return filename , release |
|
|
|
|
|
# TODO Vizonex Create a TEST File to test the code on to then verify that llhttp can be |
|
# compiled correctly and then delete/abandon the test and temporary files created... |
|
# while moving around our copies into an organized folder... |
|
|
|
|
|
def unpack_file(filename:str,ver:str): |
|
|
|
with zipfile.ZipFile(filename) as z: |
|
# The Folder will be used again to extract the roots... |
|
folder = f"llhttp-release-v{ver}" |
|
z.extract(folder + "/LICENSE-MIT","tmp") |
|
z.extract(folder + "/src/api.c","tmp") |
|
z.extract(folder + "/src/http.c","tmp") |
|
z.extract(folder + "/src/llhttp.c","tmp") |
|
z.extract(folder + "/include/llhttp.h","tmp") |
|
# don't forget to allow the readme to be extracted for those who are clueless as to what they just did... |
|
z.extract(folder + "/README.md","tmp") |
|
|
|
def move_files(ver:str,remove_zip:bool=False): |
|
"""This will create an organized foler for the following |
|
|
|
- llhttp.h |
|
- llhttp.c |
|
- http.c |
|
- api.c |
|
- README.md |
|
- LICENSE-MIT |
|
|
|
returns the llhttp path for later use in the final parts which are compiling the library for you |
|
and running a test to ensure that the library was installed correctly...""" |
|
|
|
# -- Installed Path -- |
|
tmp = pathlib.Path("tmp",f"llhttp-release-v{ver}") |
|
|
|
# -- NEW PATH -- |
|
llhttp = pathlib.Path("llhttp") |
|
|
|
files = ["LICENSE-MIT","src/api.c","src/http.c","src/llhttp.c","include/llhttp.h","README.md"] |
|
|
|
# Move all files over from temporary into something easier to reach... |
|
for f in files: |
|
last = f.split("/")[-1] |
|
file = tmp.joinpath(f) |
|
shutil.move(file,llhttp.joinpath(last)) |
|
|
|
|
|
|
|
if remove_zip: |
|
# delete Zip when done |
|
os.remove(f"llhttp_ver_{ver}.zip") |
|
|
|
return llhttp |
|
|
|
|
|
|
|
|
|
def compile_llhttp(gcc:bool=False,year:int=2019): |
|
# "vcvarsall.bat" x86&&"lib" |
|
if sys.platform == "win32" and gcc == False: |
|
|
|
# build dll or lib files.... |
|
compiler = "x86" if not is_64bit else "x64" |
|
# |
|
if not os.path.exists(f"C:\\Program Files (x86)\\Microsoft Visual Studio\\{year}\\Community\\VC\\Auxiliary\\Build\\vcvarsall.bat"): |
|
print("Visual Studio must be installed before use, Shutting down...") |
|
sys.exit(1) |
|
|
|
# It is faster to also create a .dll and then a .lib file so that only two commands inside vcvarsall are ran instead of possibly 4 commands to create all the objects in order... |
|
cmd = f"\"C:\\Program Files (x86)\\Microsoft Visual Studio\\{year}\\Community\\VC\\Auxiliary\\Build\\vcvarsall.bat\" " \ |
|
+ f" {compiler}&& cd llhttp & CL /LD /OUT:llhttp.dll llhttp.c api.c http.c & LIB /OUT:llhttp.lib llhttp.obj http.obj api.obj" |
|
|
|
|
|
subprocess.run(cmd,shell=True) |
|
|
|
# delete and destory objects after use.... |
|
os.remove("llhttp\\llhttp.obj") |
|
os.remove("llhttp\\http.obj") |
|
os.remove("llhttp\\api.obj") |
|
return # Were done now... |
|
|
|
else: |
|
|
|
# Make O files... |
|
for f in ["api","http","llhttp"]: |
|
subprocess.run(f"gcc -c llhttp\\{f}.c -o llhttp\\{f}.o") |
|
|
|
# Compile library for internal use to make complations easier to accomplish... |
|
subprocess.run("ar rcs llhttp\\llhttp.a llhttp\\api.o llhttp\\http.o llhttp\\llhttp.o") |
|
# Were Done So delete all .o files and return... |
|
os.remove("llhttp/api.o") |
|
os.remove("llhttp/http.o") |
|
os.remove("llhttp/llhttp.o") |
|
return |
|
|
|
|
|
|
|
def main(): |
|
import argparse |
|
|
|
p = argparse.ArgumentParser(description="Used to help/assist in compiling and working with llhttp's C library into a mobile enviornment without Cmake or a package manager") |
|
|
|
p.add_argument("-gcc", "--gcc-compiler", default=False,action="store_true",help="Use GCC Compiler to create an llhttp.a file for convience when compiling llhttp this will be the default if your on a system besides windows...") |
|
|
|
p.add_argument("-y","--year",default=2019,help="Pick the year of your Visual Studio version if your on windows default is 2019 if your not on windows you can ignore these arguments all together...") |
|
|
|
p.add_argument("-d","--delete-zip",default=False,action="store_true",help="Deletes Zip file after transporting data from it to the llhttp folder it creates...") |
|
p.add_argument("-nc","--no-compile",default=False,action="store_true",help="Skips Compiling the extension , a good option if you don't have gcc or an msvc compiler, default is false...") |
|
args = p.parse_args() |
|
|
|
if not os.path.exists("llhttp"): |
|
os.mkdir("llhttp") |
|
|
|
gcc:bool = args.gcc_compiler |
|
|
|
d :bool = args.delete_zip |
|
|
|
file, ver = install_llhttp() |
|
print("Unpacking instalation...") |
|
unpack_file(file,ver) |
|
print("Moving files...") |
|
|
|
move_files(ver,remove_zip=d) |
|
|
|
if not args.no_compile: |
|
print("Compiling library...") |
|
compile_llhttp(gcc,args.year) |
|
print("Done!") |
|
print("note that tmp will need to be deleted manually...") |
|
|
|
|
|
if __name__ == "__main__": |
|
main() |
|
|
|
|