Skip to content

Instantly share code, notes, and snippets.

@Vizonex
Last active February 16, 2023 23:39
Show Gist options
  • Select an option

  • Save Vizonex/63d3b9676fe43e403a777ca05c4a21f1 to your computer and use it in GitHub Desktop.

Select an option

Save Vizonex/63d3b9676fe43e403a777ca05c4a21f1 to your computer and use it in GitHub Desktop.
An llhttp installer script for those working with python and C

This is An Installer Idea I had for those not cabable of using node-js to get or to build llhttp immediately into a small and portable virual enviornment and it is used to Compile the llhttp library for msvc compiler or for the gcc compiler to create a ".lib" & ".dll" file or a single ".a" file if your using the gcc compiler or anything besides windows. This is my alternative for installing such an amazing library and I'll proabably write one for libuv when I've figured out how to correctly install it because digging though and doing that one correctly is a nightmare.

This tool doesn't use the requests library for the sake of portability and arbitrary speed ups.

This will install the precompiled version of llhttp and get rid of other files that aren't needed it will then only grabbing nessesary files to run llhttp in C and it will attempt to use either the gcc compiler or MSVC compiler depending on your operating system and then make the choice as to what other tools to use accordingly. you can use --help if you would like to use gcc instead on windows as well as other important things...

This code will work with 3.9 or above since I'm using 3.9 , as long as you have either Visual Studio code 2019 or above, it should be fine as I have tested it multiple times with that version

# 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()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment