Skip to content

Instantly share code, notes, and snippets.

@gvanem
Last active October 6, 2023 09:05
Show Gist options
  • Save gvanem/1c68a417c8dcc249c5562d46a3d9fcb9 to your computer and use it in GitHub Desktop.
Save gvanem/1c68a417c8dcc249c5562d46a3d9fcb9 to your computer and use it in GitHub Desktop.
Makefile for curl-impersonate-chrome.exe
#
# Makefile for curl-impersonate-chrome.exe supporting MSVC or clang-cl.
# by Gisle Vanem <gvanem@yahoo.no>
#
# Ref: https://github.com/lwthiker/curl-impersonate.git
#
# Options and roots. Change to suite.
#
CURL_ROOT ?= ..
CARES_ROOT ?= f:/MinGW32/src/inet/DNS/C-ares
HYPER_ROOT ?= f:/MinGW32/src/inet/Web/Hyper
BORINGSSL_ROOT ?= f:/MinGW32/src/inet/Crypto/BoringSSL
BROTLI_ROOT ?= f:/MinGW32/src/Compression/Brotli
ZLIB_ROOT ?= f:/MinGW32/src/Compression/zlib
ZSTD_ROOT ?= f:/MinGW32/src/Compression/zstd
INSTALL_ROOT ?= $(realpath $(VSINSTALLDIR))
#
# A 'getopt.h' is needed for 'tests/minicurl.c' only.
# Do 'vcpkg install getopt:%CPU%-windows-static' to get it.
#
GETOPT_ROOT ?= $(realpath $(VCPKG_ROOT))/installed/$(CPU)-windows-static
USE_ASAN ?= 0
USE_ASTYLE ?= 1
USE_CARES ?= 1
USE_CURL_DEBUG ?= 1
USE_MP_COMPILE ?= 1
USE_WSOCK_TRACE ?= 1
PERL ?= perl
PYTHON ?= py -3
MDEPEND = # $(THIS_FILE)
THIS_FILE := Makefile.Windows
TODAY := $(shell date +%d-%B-%Y)
COPY := cp --update --preserve=timestamps
OBJ_DIR := objects
MAKEFLAGS += --warn-undefined-variables
#
# Undefine all these
#
export CL=
export WSOCK_TRACE_LEVEL=
export P11_KIT_DEBUG=
define Usage
Usage: make -f $(THIS_FILE) CC=[cl | clang-cl] <all | clean | vclean | install>
endef
c_to_obj = $(addprefix $(OBJ_DIR)/$(strip $(2)), $(notdir $(1:.c=.obj)))
c_to_i = $(addprefix $(strip $(2)), $(notdir $(1:.c=.i)))
ifneq ($(CC),clang-cl)
ifneq ($(CC),cl)
$(error $(Usage))
endif
endif
CFLAGS = -nologo -MD -Zi -GF -GS- -Gy -O2 -W3 \
-D_CRT_SECURE_NO_WARNINGS \
-D_CRT_NONSTDC_NO_WARNINGS \
-DCURL_CA_FALLBACK \
-DCURL_DEFAULT_SSL_BACKEND=\"openssl\" \
-DCURL_DISABLE_OPENSSL_AUTO_LOAD_CONFIG \
-DCURL_STATICLIB=1 \
-DCURL_WITH_MULTI_SSL \
-DHAVE_BORINGSSL=1 \
-DHAVE_BROTLI=1 \
-DHAVE_CONFIG_H=1 \
-DHAVE_GETSOCKNAME=1 \
-DHAVE_LDAP_SSL=1 \
-DHAVE_LIBZ=1 \
-DHAVE_LONGLONG=1 \
-DHAVE_SSLV2_CLIENT_METHOD=1 \
-DHAVE_ZSTD=1 \
-DUSE_HYPER=1 \
-DUSE_IPV6=1 \
-DUSE_MANUAL=1 \
-DUSE_OPENSSL=1 \
-DUSE_STATIC=1 \
-DUSE_TLS_SRP=1 \
-DUSE_WIN32_IDN=1 \
-DUSE_WIN32_LDAP=1 \
-DOPENSSL_SUPPRESS_DEPRECATED \
-DWIN32 \
-DWIN32_LEAN_AND_MEAN \
-DWINVER=0x0602 \
-D_WIN32_WINNT=0x0602 \
-I. \
-I./$(OBJ_DIR)/include \
-I./$(OBJ_DIR)/lib \
-I./$(OBJ_DIR)/src \
-I$(BORINGSSL_ROOT)/include \
-I$(BROTLI_ROOT)/c/include \
-I$(HYPER_ROOT)/capi/include \
-I$(ZLIB_ROOT) \
-I$(ZSTD_ROOT)/lib
LDFLAGS = -nologo -debug -incremental:no \
-map -verbose \
-subsystem:console \
-machine:$(CPU) \
-nodefaultlib:iphlpapi.lib \
-nodefaultlib:uuid.lib
RCFLAGS = -nologo \
-I$(OBJ_DIR)/include \
-I$(OBJ_DIR)/src \
-I$(CURL_ROOT)/src
#
# For '$(OBJ_DIR)/lib/random.c':
#
ifneq ($(RANDFILE),)
CFLAGS += -DRANDOM_FILE=\"$(realpath $(RANDFILE))\"
endif
OS_LIBS = advapi32.lib \
crypt32.lib \
normaliz.lib \
ntdll.lib \
userenv.lib \
wldap32.lib
ifeq ($(USE_WSOCK_TRACE),1)
OS_LIBS += wsock_trace-$(CPU).lib
LDFLAGS += -nodefaultlib:ws2_32.lib
else
OS_LIBS += ws2_32.lib
endif
ifeq ($(USE_ASAN),1)
#
# With ASAN + '-DCURLDEBUG', 'chrome/curl-impersonate-chrome.exe' hangs
# for a long time at 'exit()'. Hence turn this off:
#
USE_CURL_DEBUG := 0
CFLAGS += -fsanitize=address
LDFLAGS += -inferasanlibs
ifeq ($(CC),clang-cl)
CFLAGS += -fsanitize-recover=address
endif
endif
#
# For the Rust built Hyper:
#
ifeq ($(CPU),x86)
EX_LIBS = $(HYPER_ROOT)/target/i686-pc-windows-msvc/release/hyper.lib
else
EX_LIBS = $(HYPER_ROOT)/target/x86_64-pc-windows-msvc/release/hyper.lib
endif
EX_LIBS += $(BORINGSSL_ROOT)/lib/libssl_imp-$(CPU).lib \
$(BORINGSSL_ROOT)/lib/libcrypto_imp-$(CPU).lib \
$(BROTLI_ROOT)/lib/brotli_imp-$(CPU).lib \
$(ZLIB_ROOT)/lib/zlib-$(CPU).lib \
$(ZSTD_ROOT)/lib/zstd-$(CPU).lib
ifeq ($(USE_CARES),1)
CFLAGS += -DUSE_ARES=1 -I$(CARES_ROOT)/include
EX_LIBS += $(CARES_ROOT)/lib/cares_imp-$(CPU).lib
else
CFLAGS += -DUSE_THREADS_WIN32=1
endif
ifeq ($(USE_CURL_DEBUG),1)
CFLAGS += -DDEBUG=1 \
-DCURLDEBUG=1 \
-DDEBUG_ADDRINFO=1 \
-DDEBUG_LDAP \
-DDEBUGBUILD=1 \
-DCURL_MT_FREE_FILL=0 \
-DCURL_MT_MALLOC_FILL=0xEE \
-DMEMDEBUG_LOG_SYNC
RCFLAGS += -DDEBUGBUILD=1
endif
ifeq ($(CC),clang-cl)
RCFLAGS += -D__clang__
CFLAGS += -ferror-limit=5 \
-fms-compatibility \
-Wno-macro-redefined \
-Wno-unused-variable \
-Wno-pointer-sign \
-Wno-void-pointer-to-enum-cast \
-Wno-missing-variable-declarations
else
RCFLAGS += -D_MSC_VER
CFLAGS += -wd4005 -wd4101 -wd4267
endif
include $(CURL_ROOT)/lib/Makefile.inc
#
# These patches are generated first:
#
PATCHES = $(addprefix $(OBJ_DIR)/, \
include/curl/curl.h.patch \
include/curl/easy.h.patch \
lib/dynhds.c.patch \
lib/dynhds.h.patch \
lib/easy.c.patch \
lib/http.c.patch \
lib/multi.c.patch \
lib/setopt.c.patch \
lib/transfer.c.patch \
lib/url.c.patch \
lib/urldata.h.patch \
lib/vtls/openssl.c.patch \
lib/vtls/vtls.c.patch \
lib/vtls/vtls.h.patch \
lib/vtls/vtls_int.h.patch \
src/tool_cfgable.c.patch \
src/tool_cfgable.h.patch \
src/tool_getparam.c.patch \
src/tool_listhelp.c.patch \
src/tool_operate.c.patch \
src/tool_setopt.c.patch)
#
# Generated manually:
#
GENERATED = $(addprefix $(OBJ_DIR)/lib/, \
curl_config.h \
easyoptions.c \
impersonate.c \
impersonate.h)
#
# Copied:
#
GENERATED += $(addprefix $(OBJ_DIR)/lib/, \
$(CSOURCES) \
$(HHEADERS) \
config-win32.h \
curl_setup.h \
setup-win32.h)
#
# Copied and possibly patched:
#
GENERATED += $(addprefix $(OBJ_DIR)/include/curl/, \
curl.h \
curlver.h \
easy.h \
header.h \
mprintf.h \
multi.h \
options.h \
stdcheaders.h \
system.h \
typecheck-gcc.h \
urlapi.h \
websockets.h)
#
# Generated manually for 'chrome/minicurl.exe':
#
GENERATED += $(OBJ_DIR)/include/unistd.h
#
# Copied, only '$(OBJ_DIR)/src/tool_cfgable.h' gets patched:
#
GENERATED += $(addprefix $(OBJ_DIR)/src/, \
tool_binmode.h \
tool_bname.h \
tool_cb_dbg.h \
tool_cb_hdr.h \
tool_cb_prg.h \
tool_cb_rea.h \
tool_cb_see.h \
tool_cb_wrt.h \
tool_cfgable.h \
tool_dirhie.h \
tool_doswin.h \
tool_easysrc.h \
tool_filetime.h \
tool_findfile.h \
tool_formparse.h \
tool_getparam.h \
tool_getpass.h \
tool_help.h \
tool_helpers.h \
tool_hugehelp.h \
tool_libinfo.h \
tool_main.h \
tool_msgs.h \
tool_operate.h \
tool_operhlp.h \
tool_paramhlp.h \
tool_parsecfg.h \
tool_progress.h \
tool_sdecls.h \
tool_setopt.h \
tool_setup.h \
tool_sleep.h \
tool_stderr.h \
tool_strdup.h \
tool_urlglob.h \
tool_util.h \
tool_version.h \
tool_vms.h \
tool_writeout.h \
tool_writeout_json.h \
tool_xattr.h \
slist_wc.h \
var.h)
#
# Copied and 5 gets patched:
#
CURL_CFILES = $(addprefix $(OBJ_DIR)/src/, \
slist_wc.c \
tool_binmode.c \
tool_bname.c \
tool_cb_dbg.c \
tool_cb_hdr.c \
tool_cb_prg.c \
tool_cb_rea.c \
tool_cb_see.c \
tool_cb_wrt.c \
tool_cfgable.c \
tool_dirhie.c \
tool_doswin.c \
tool_easysrc.c \
tool_filetime.c \
tool_findfile.c \
tool_formparse.c \
tool_getparam.c \
tool_getpass.c \
tool_help.c \
tool_helpers.c \
tool_libinfo.c \
tool_listhelp.c \
tool_main.c \
tool_msgs.c \
tool_operate.c \
tool_operhlp.c \
tool_paramhlp.c \
tool_parsecfg.c \
tool_progress.c \
tool_setopt.c \
tool_sleep.c \
tool_stderr.c \
tool_strdup.c \
tool_urlglob.c \
tool_util.c \
tool_vms.c \
tool_writeout.c \
tool_writeout_json.c \
tool_xattr.c \
var.c)
GENERATED += $(CURL_CFILES)
#
# Not copied but generated below:
#
GENERATED += $(OBJ_DIR)/src/tool_hugehelp.c
#
# Generated from '$(PATCHES)':
#
PATCHED_SRC = $(PATCHES:.patch=)
LIB_SRC = $(addprefix $(OBJ_DIR)/lib/, $(CSOURCES)) \
$(OBJ_DIR)/lib/impersonate.c
LIB_OBJ = $(call c_to_obj, $(LIB_SRC), )
TOOL_SRC = $(CURL_CFILES) \
$(OBJ_DIR)/src/tool_hugehelp.c
TOOL_OBJ = $(call c_to_obj, $(TOOL_SRC), )
TOOL_SRC_x = $(OBJ_DIR)/lib/base64.c \
$(OBJ_DIR)/lib/dynbuf.c
#
# Prefix with 'x_' since these are in '$(OBJ_DIR)/lib' too.
#
TOOL_OBJ_x = $(call c_to_obj, $(TOOL_SRC_x), x_)
all: $(GENERATED) \
chrome/curl-impersonate-chrome.exe \
chrome/minicurl.exe
create_patches: $(PATCHES)
apply_patches: $(PATCHED_SRC)
count_patched = $(shell grep --count --files-with-matches \
"Patched file" $(OBJ_DIR)/lib/*.[ch] $(OBJ_DIR)/lib/vtls/*.[ch] \
$(OBJ_DIR)/src/*.[ch] $(OBJ_DIR)/include/curl/*.h)
check_patches: apply_patches
$(call green_msg0,Files in PATCHES: '$(words $(PATCHES))')
$(call green_msg0,Actual patched files: '$(words $(count_patched))')
chrome/curl-impersonate-chrome.exe: $(LIB_OBJ) \
$(TOOL_OBJ) \
$(TOOL_OBJ_x) \
$(OBJ_DIR)/curl.res \
$(EX_LIBS) | check-for-unused-libs.py
$(call link_EXE, $@, $^ $(OS_LIBS))
chrome/minicurl.exe: $(OBJ_DIR)/minicurl.obj $(LIB_OBJ) $(EX_LIBS) $(GETOPT_ROOT)/lib/getopt.lib | check-for-unused-libs.py
$(call link_EXE, $@, $^ $(OS_LIBS))
$(OBJ_DIR)/curl.rc: $(THIS_FILE) | $(OBJ_DIR)
$(call generate_c,$@,$(curl.rc))
$(OBJ_DIR)/include \
$(OBJ_DIR)/include/curl \
$(OBJ_DIR)/lib \
$(OBJ_DIR)/lib/vauth \
$(OBJ_DIR)/lib/vtls \
$(OBJ_DIR)/lib/vquic \
$(OBJ_DIR)/lib/vssh \
$(OBJ_DIR)/src:
mkdir --parents $@
$(OBJ_DIR)/include/curl/curl.h: $(CURL_ROOT)/include/curl/curl.h $(OBJ_DIR)/include/curl/curl.h.patch $(MDEPEND) | $(OBJ_DIR)/include/curl
$(call copy_and_patch, $@, $<)
$(OBJ_DIR)/include/curl/easy.h: $(CURL_ROOT)/include/curl/easy.h $(OBJ_DIR)/include/curl/easy.h.patch $(MDEPEND) | $(OBJ_DIR)/include/curl
$(call copy_and_patch, $@, $<)
$(OBJ_DIR)/include/curl/%.h: $(CURL_ROOT)/include/curl/%.h | $(OBJ_DIR)/include/curl
$(call copy_file, $@, $<)
$(OBJ_DIR)/lib/easyoptions.c: $(OBJ_DIR)/include/curl/curl.h $(CURL_ROOT)/lib/optiontable.pl $(MDEPEND) | $(OBJ_DIR)/lib
$(call generate_c, $@, )
dos2unix --quiet --keepdate $<
$(PERL) $(CURL_ROOT)/lib/optiontable.pl < $< >> $@
@echo
$(OBJ_DIR)/lib/%.h: $(CURL_ROOT)/lib/%.h $(OBJ_DIR)/lib/%.h.patch $(MDEPEND) | $(OBJ_DIR)/lib
$(call copy_and_patch, $@, $<)
$(OBJ_DIR)/lib/%.c: $(CURL_ROOT)/lib/%.c $(OBJ_DIR)/lib/%.c.patch $(MDEPEND) | $(OBJ_DIR)/lib
$(call copy_and_patch, $@, $<)
$(OBJ_DIR)/lib/vtls/%.c: $(OBJ_DIR)/lib/vtls/%.c $(OBJ_DIR)/lib/vtls/%.c.patch $(MDEPEND) | $(OBJ_DIR)/lib/vtls
$(call copy_and_patch, $@, $<)
$(OBJ_DIR)/lib/%: $(CURL_ROOT)/lib/% | $(OBJ_DIR)/lib $(OBJ_DIR)/lib/vauth $(OBJ_DIR)/lib/vquic $(OBJ_DIR)/lib/vssh $(OBJ_DIR)/lib/vtls
$(call copy_file, $@, $<)
$(OBJ_DIR)/src/tool_cfgable.h: $(CURL_ROOT)/src/tool_cfgable.h $(OBJ_DIR)/src/tool_cfgable.h.patch $(MDEPEND) | $(OBJ_DIR)/src
$(call copy_and_patch, $@, $<)
$(OBJ_DIR)/src/tool_cfgable.c: $(CURL_ROOT)/src/tool_cfgable.c $(OBJ_DIR)/src/tool_cfgable.c.patch $(MDEPEND) | $(OBJ_DIR)/src
$(call copy_and_patch, $@, $<)
$(OBJ_DIR)/src/tool_getparam.c: $(CURL_ROOT)/src/tool_getparam.c $(OBJ_DIR)/src/tool_getparam.c.patch $(MDEPEND) | $(OBJ_DIR)/src
$(call copy_and_patch, $@, $<)
$(OBJ_DIR)/src/tool_operate.c: $(CURL_ROOT)/src/tool_operate.c $(OBJ_DIR)/src/tool_operate.c.patch $(MDEPEND) | $(OBJ_DIR)/src
$(call copy_and_patch, $@, $<)
$(OBJ_DIR)/src/tool_setopt.c: $(CURL_ROOT)/src/tool_setopt.c $(OBJ_DIR)/src/tool_setopt.c.patch $(MDEPEND) | $(OBJ_DIR)/src
$(call copy_and_patch, $@, $<)
$(OBJ_DIR)/src/%: $(CURL_ROOT)/src/% | $(OBJ_DIR)/src
$(call copy_file, $@, $<)
$(OBJ_DIR)/lib/curl_config.h: $(MDEPEND) | $(OBJ_DIR)/lib
$(call generate_c,$@,$(curl_config.h))
$(OBJ_DIR)/lib/impersonate.c: $(MDEPEND) | $(OBJ_DIR)/lib
$(call generate_c,$@,$(impersonate.c))
$(OBJ_DIR)/lib/impersonate.h: $(MDEPEND) | $(OBJ_DIR)/lib
$(call generate_c,$@,$(impersonate.h))
$(OBJ_DIR)/include/unistd.h: $(MDEPEND) | $(OBJ_DIR)/include
$(call generate_c,$@,$(unistd.h))
$(OBJ_DIR)/include/curl/curl.h.patch: $(MDEPEND) | $(OBJ_DIR)/include/curl
$(call generate_patch,$@,$(curl.h.patch))
$(OBJ_DIR)/include/curl/easy.h.patch: $(MDEPEND) | $(OBJ_DIR)/include/curl
$(call generate_patch,$@,$(easy.h.patch))
$(OBJ_DIR)/lib/dynhds.c.patch: $(MDEPEND) | $(OBJ_DIR)/lib
$(call generate_patch,$@,$(dynhds.c.patch))
$(OBJ_DIR)/lib/dynhds.h.patch: $(MDEPEND) | $(OBJ_DIR)/lib
$(call generate_patch,$@,$(dynhds.h.patch))
$(OBJ_DIR)/lib/easy.c.patch: $(MDEPEND) | $(OBJ_DIR)/lib
$(call generate_patch,$@,$(easy.c.patch))
$(OBJ_DIR)/lib/http.c.patch: $(MDEPEND) | $(OBJ_DIR)/lib
$(call generate_patch,$@,$(http.c.patch))
$(OBJ_DIR)/lib/http2.c.patch: $(MDEPEND) | $(OBJ_DIR)/lib
$(call generate_patch,$@,$(http2.c.patch))
$(OBJ_DIR)/lib/http2.h.patch: $(MDEPEND) | $(OBJ_DIR)/lib
$(call generate_patch,$@,$(http2.h.patch))
$(OBJ_DIR)/lib/multi.c.patch: $(MDEPEND) | $(OBJ_DIR)/lib
$(call generate_patch,$@,$(multi.c.patch))
$(OBJ_DIR)/lib/setopt.c.patch: $(MDEPEND) | $(OBJ_DIR)/lib
$(call generate_patch,$@,$(setopt.c.patch))
$(OBJ_DIR)/lib/transfer.c.patch: $(MDEPEND) | $(OBJ_DIR)/lib
$(call generate_patch,$@,$(transfer.c.patch))
$(OBJ_DIR)/lib/url.c.patch: $(MDEPEND) | $(OBJ_DIR)/lib
$(call generate_patch,$@,$(url.c.patch))
$(OBJ_DIR)/lib/urldata.h.patch: $(MDEPEND) | $(OBJ_DIR)/lib
$(call generate_patch,$@,$(urldata.h.patch))
$(OBJ_DIR)/lib/vtls/openssl.c.patch: $(MDEPEND) | $(OBJ_DIR)/lib/vtls
$(call generate_patch,$@,$(openssl.c.patch))
$(OBJ_DIR)/lib/vtls/vtls.c.patch: $(MDEPEND) | $(OBJ_DIR)/lib/vtls
$(call generate_patch,$@,$(vtls.c.patch))
$(OBJ_DIR)/lib/vtls/vtls.h.patch: $(MDEPEND) | $(OBJ_DIR)/lib/vtls
$(call generate_patch,$@,$(vtls.h.patch))
$(OBJ_DIR)/lib/vtls/vtls_int.h.patch: $(MDEPEND) | $(OBJ_DIR)/lib/vtls
$(call generate_patch,$@,$(vtls_int.h.patch))
$(OBJ_DIR)/src/tool_cfgable.c.patch: $(MDEPEND) | $(OBJ_DIR)/src
$(call generate_patch,$@,$(tool_cfgable.c.patch))
$(OBJ_DIR)/src/tool_cfgable.h.patch: $(MDEPEND) | $(OBJ_DIR)/src
$(call generate_patch,$@,$(tool_cfgable.h.patch))
$(OBJ_DIR)/src/tool_getparam.c.patch: $(MDEPEND) | $(OBJ_DIR)/src
$(call generate_patch,$@,$(tool_getparam.c.patch))
$(OBJ_DIR)/src/tool_listhelp.c.patch: $(MDEPEND) | $(OBJ_DIR)/src
$(call generate_patch,$@,$(tool_listhelp.c.patch))
$(OBJ_DIR)/src/tool_operate.c.patch: $(MDEPEND) | $(OBJ_DIR)/src
$(call generate_patch,$@,$(tool_operate.c.patch))
$(OBJ_DIR)/src/tool_setopt.c.patch: $(MDEPEND) | $(OBJ_DIR)/src
$(call generate_patch,$@,$(tool_setopt.c.patch))
#
# Compile rules:
#
LIB_OBJ = $(call c_to_obj, $(CSOURCES), )
LIB_I = $(call c_to_i, $(CSOURCES), )
$(LIB_OBJ) $(LIB_I): EXTRA_CFLAGS = -DBUILDING_LIBCURL
$(OBJ_DIR)/minicurl.obj: EXTRA_CFLAGS = -I$(GETOPT_ROOT)/include
vpath %.c $(OBJ_DIR)/lib \
$(OBJ_DIR)/lib/vauth \
$(OBJ_DIR)/lib/vtls \
$(OBJ_DIR)/lib/vquic \
$(OBJ_DIR)/lib/vssh \
$(OBJ_DIR)/src \
tests
ifeq ($(CC)-$(USE_MP_COMPILE),cl-1)
$(LIB_OBJ): $(LIB_SRC) | $(CC).args
$(call C_compile_MP, $(LIB_SRC), LIB_SRC)
$(TOOL_OBJ): $(TOOL_SRC) | $(CC).args
$(call C_compile_MP, $(TOOL_SRC), TOOL_SRC)
else
$(OBJ_DIR)/tool_hugehelp.obj: $(OBJ_DIR)/src/tool_hugehelp.c | $(CC).args
$(call C_compile, $@, $<)
endif
$(OBJ_DIR)/%.obj: %.c | $(CC).args
$(call C_compile, $@, $<)
$(OBJ_DIR)/x_%.obj: $(OBJ_DIR)/lib/%.c | $(CC).args
$(call C_compile, $@, $<)
%.i: %.c $(GENERATED) cpp-filter.py FORCE | $(CC).args
$(call C_preprocess, $@, $<)
x_%.i: $(OBJ_DIR)/lib/%.c $(GENERATED) cpp-filter.py FORCE | $(CC).args
$(call C_preprocess, $@, $<)
$(OBJ_DIR)/%.res: $(OBJ_DIR)/%.rc
$(call create_res_file, $@, $<)
cpp-filter.py: $(THIS_FILE)
$(call generate_py, $@,$(cpp-filter.py))
check-for-unused-libs.py: $(THIS_FILE)
$(call generate_py, $@,$(check-for-unused-lib.py))
$(CC).args: FORCE $(THIS_FILE)
$(call green_msg, All common CFLAGS are in $@)
$(call create_resp_file, $@, -c $(CFLAGS))
FORCE:
install: chrome/curl-impersonate-chrome.exe
$(call copy_file, $(INSTALL_ROOT)/bin, $(<:.exe=.{exe,pdb}))
clean:
rm -f vc1*.pdb cl.args clang-cl.args link.args link.tmp lib.args \
cpp-filter.py check-for-unused-libs.py
- rm -fr $(OBJ_DIR)
clean_pytest:
cd tests
rm -fr .pytests_cache
realclean vclean: clean clean_pytest
rm -f chrome/curl-impersonate-chrome.{exe,pdb,map} chrome/minicurl.{exe,pdb,map} .depend.Windows
$(OBJ_DIR)/src/tool_hugehelp.c: $(CURL_ROOT)/docs/curl.1 $(CURL_ROOT)/src/mkhelp.pl | $(OBJ_DIR)
$(call generate_hugehelp, $@, $<)
# $(OBJ_DIR)/src/tool_listhelp.c: $(OBJ_DIR)/src/tool_listhelp.c.patch $(CURL_ROOT)/docs/cmdline-opts/gen.pl $(MDEPEND) | $(OBJ_DIR)
# $(call green_msg, Generating $@)
# $(PERL) $< listhelp $(CURL_ROOT)/docs/cmdline-opts/*.d > $@
# $(call apply_patch, $<)
$(OBJ_DIR)/src/tool_listhelp.c: $(OBJ_DIR)/src/tool_listhelp.c.patch $(CURL_ROOT)/docs/cmdline-opts/gen.pl $(MDEPEND) | $(OBJ_DIR)
$(call generate_listhelp, $@, $<)
$(CURL_ROOT)/docs/curl.1: $(CURL_ROOT)/docs/cmdline-opts/gen.pl
$(call green_msg, Generating $@)
cd $(CURL_ROOT)/docs/cmdline-opts ; \
$(PERL) ./gen.pl mainpage *.d > ../curl.1
@echo
test_1: chrome/curl-impersonate-chrome.exe | $(OBJ_DIR)
$(call run_test, $(test_1_args), https://www.google.com, $(OBJ_DIR)/test_1.html)
test_2: chrome/curl-impersonate-chrome.exe | $(OBJ_DIR)
$(call run_test, $(test_2_args), https://www.google.com, $(OBJ_DIR)/test_2.html)
#
# Try to emulate 'def tcpdump()'.
# But does not work. Some shell issue.
#
WINDUMP_ARGS = "--interface=$(PCAP_DEVICE)" -F $(OBJ_DIR)/windump.filter -c 10
WINDUMP_ARGS += --packet-buffered -w $(OBJ_DIR)/windump.pcap
# WINDUMP_ARGS += --snapshot-length=0
test_3: chrome/minicurl.exe check_windump $(OBJ_DIR)/windump.filter
$(call green_msg, Running 'windump' and 'minicurl' in parallell...)
start /win windump.exe $(WINDUMP_ARGS)
chrome/minicurl.exe -o NUL https://www.kernel.org
#
# But this works with 4NT/TCC only.
#
_COMSPEC = $(subst \,\\,$(COMSPEC))
.SHELL: $(_COMSPEC)
test_4: chrome/minicurl.exe check_windump $(OBJ_DIR)/windump.filter
windump.exe $(WINDUMP_ARGS) & $< -o NUL https://www.kernel.org
#
# TODO:
# Dissect the TLS layer:
# tshark -VO eth:ethertype:ip:tcp:tls $(OBJ_DIR)/windump.pcap
#
# Create a modified 'def _extract_client_hello()' function
# that parses '$(OBJ_DIR)/windump.pcap'
#
#
# Some checks for running WinDump
#
file_exist = $(wildcard $(1))
path_find = $(call file_exist, $(addsuffix /$(1),$(subst ;, ,$(subst \,/,$(PATH)))))
.PHONY: check_windump
check_windump:
ifeq ($(call path_find,windump.exe),)
$(error "windump.exe" not found on path. Edit $(THIS_FILE) manually.)
endif
ifeq ($(PCAP_DEVICE),)
$(error Define 'PCAP_DEVICE' first)
endif
$(OBJ_DIR)/windump.filter: $(THIS_FILE) | $(OBJ_DIR)
$(file > $@, (tcp src portrange 1024-60000 and tcp dst port 443))
$(file >> $@, or (tcp dst portrange 1024-60000 and tcp src port 443))
ifeq ($(USE_CURL_DEBUG),1)
define leak_test_start
export CURL_MEMDEBUG=$(realpath $(TEMP))/memdebug.curl
rm -f $(CURL_MEMDEBUG)
endef
define leak_test_stop
$(PERL) $(CURL_ROOT)/tests/memanalyze.pl -v $(CURL_MEMDEBUG)
@echo
endef
else
leak_test_start =
leak_test_stop = @echo
endif
define run_test
$(leak_test_start)
$(call green_msg, Running test against $(BRIGHT_WHITE)$(strip $(2)))
$(file > $(OBJ_DIR)/test.args,$(1))
$(file >> $(OBJ_DIR)/test.args,$(test_common_args))
chrome/curl-impersonate-chrome.exe --libcurl - --config $(OBJ_DIR)/test.args $(2) > $(3)
$(leak_test_stop)
endef
#
# Adapted from 'chrome/curl_chrome100'
#
define test_1_args
-H "sec-ch-ua: \" Not A;Brand\";v=\"99\", \"Chromium\";v=\"100\", \"Google Chrome\";v=\"100\""
-H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36"
--http2-pseudo-headers-order "mspa"
--signature-hashes ecdsa_secp256r1_sha256,rsa_pss_rsae_sha256,rsa_pkcs1_sha256,ecdsa_secp384r1_sha384
endef
#
# Adapted from 'chrome/curl_edge101'
#
define test_2_args
-H "sec-ch-ua: \" Not A;Brand\";v=\"99\", \"Chromium\";v=\"101\", \"Microsoft Edge\";v=\"101\""
-H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36 Edg/101.0.1210.47"
endef
define test_common_args
--ciphers TLS_AES_128_GCM_SHA256,TLS_AES_256_GCM_SHA384,TLS_CHACHA20_POLY1305_SHA256,ECDHE-ECDSA-AES128-GCM-SHA256,ECDHE-RSA-AES128-GCM-SHA256,ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384,ECDHE-ECDSA-CHACHA20-POLY1305,ECDHE-RSA-CHACHA20-POLY1305,ECDHE-RSA-AES128-SHA,ECDHE-RSA-AES256-SHA,AES128-GCM-SHA256,AES256-GCM-SHA384,AES128-SHA,AES256-SHA
--http2
--compressed
--tlsv1.2
--alps
--cert-compression brotli
-H "sec-ch-ua-mobile: ?0"
-H "sec-ch-ua-platform: \"Windows\""
-H "Upgrade-Insecure-Requests: 1"
-H "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
-H "Accept-Encoding: gzip, deflate, br"
-H "Accept-Language: en-US,en;q=0.9"
-H "Sec-Fetch-Site: none"
-H "Sec-Fetch-Mode: navigate"
-H "Sec-Fetch-User: ?1"
-H "Sec-Fetch-Dest: document"
endef
#
# GNU-make macros:
#
# This assumes you have MSys/Cygwin's echo with colour support.
#
BRIGHT_GREEN = \e[1;32m
BRIGHT_WHITE = \e[1;37m
green_msg = @echo -e "$(BRIGHT_GREEN)$(strip $(1))\e[0m"
green_msg0 = @echo -e "$(BRIGHT_GREEN)$(1)\e[0m"
define copy_file
$(call green_msg, Copying $(BRIGHT_WHITE)$(strip $(2))$(BRIGHT_GREEN) to $(BRIGHT_WHITE)$(strip $(1)))
@$(COPY) $(2) $(1)
endef
define copy_and_patch
$(call copy_file, $(1), $(2))
$(call apply_patch, $(1).patch)
@echo
endef
#
# Since we cannot create a patched file with the same name
# as '$(realpath $(1))', use a .tmp-file first.
#
patch_output = $(subst .patch,,$(realpath $(1))).tmp
#
# And mark it as a '/* Patched file */'
#
define mark_as_patched
@echo '/* Patched file */' > $(subst .patch,,$(realpath $(1)))
@cat $(subst .patch,,$(realpath $(1))).tmp >> $(subst .patch,,$(realpath $(1)))
endef
#
# Apply a .patch-file in '$(1)' to '$(1:.patch=).tmp'
# and mark the result $(1:.patch=.c) as a '/* Patched file */'
#
define apply_patch
$(call green_msg, Applying patch $(BRIGHT_WHITE)$(strip $(1)))
patch --silent --unified --ignore-whitespace --strip=1 \
--directory=$(realpath $(OBJ_DIR)) --input=$(realpath $(1)) \
--output=$(call patch_output, $(1))
$(call mark_as_patched, $(1))
endef
define create_resp_file
$(file > $(1))
$(foreach f, $(2), $(file >> $(1),$(strip $(f))) )
endef
define Warning
$(1)
$(1) DO NOT EDIT! This file was automatically generated
$(1) from $(realpath $(THIS_FILE)) at $(TODAY).
$(1) Edit those files instead.
$(1)
endef
define generate
$(call green_msg, Generating $(1).)
$(file > $(1),$(call Warning,$(2)))
@echo
endef
define generate_c
$(call generate, $(1),//)
$(file >> $(1),$(2))
@echo
endef
define generate_py
$(call generate, $(1),#)
$(file >> $(1),if 1:)
$(file >> $(1),$(2))
@echo
endef
define generate_patch
$(call green_msg, Generating $(1).)
$(file > $(1),$(2))
@echo
endef
define generate_hugehelp
$(call generate_c,$(1),#include "tool_setup.h")
groff -T ascii -man $(2) > $(OBJ_DIR)/tool_hugehelp.tmp
$(PERL) $(CURL_ROOT)/src/mkhelp.pl < $(OBJ_DIR)/tool_hugehelp.tmp >> $(1)
@echo
endef
# $(OBJ_DIR)/src/tool_listhelp.c: $(CURL_ROOT)/docs/cmdline-opts/gen.pl $(OBJ_DIR)/src/tool_listhelp.c.patch $(MDEPEND) | $(OBJ_DIR)
# $(call green_msg, Generating $@)
# $(PERL) $< listhelp $(CURL_ROOT)/docs/cmdline-opts/*.d > $@
# $(call apply_patch, $(OBJ_DIR)/src/tool_listhelp.c.patch)
define generate_listhelp
$(call green_msg, Generating $(1))
$(PERL) $(CURL_ROOT)/docs/cmdline-opts/gen.pl listhelp $(CURL_ROOT)/docs/cmdline-opts/*.d > $(1)
$(call apply_patch, $(2))
endef
EXTRA_CFLAGS ?=
define C_compile
$(strip $(CC) $(EXTRA_CFLAGS) @$(CC).args -Fo./$(strip $(1) $(2)))
endef
define C_compile_MP
$(call green_msg, Compiling $(words $(filter %.c, $(1))) .c-files for $(BRIGHT_WHITE)$(strip $(2)) $(BRIGHT_GREEN)in one go...)
$(strip $(CC) $(EXTRA_CFLAGS) @$(CC).args -MP -Fo./$(OBJ_DIR)\\ $(1))
@echo
endef
ifeq ($(USE_ASTYLE),1)
pp_filter = | astyle
pp_comment = The preprocessed and AStyle'd output of '$(strip $(1)')
else
pp_filter =
pp_comment = The preprocessed raw output of '$(strip $(1)')
endif
define C_preprocess
$(file > $(1),/* $(call pp_comment, $(2)):)
$(file >> $(1), * $(CC) -E)
@$(foreach f, $(CFLAGS), $(file >> $(1), * $(f)))
$(file >> $(1), * With these EXTRA_CFLAGS:)
@$(foreach f, $(EXTRA_CFLAGS), $(file >> $(1), * $(f)))
$(file >> $(1), *----------------------------------------------------------------------------------)
$(file >> $(1), */)
$(CC) -E @$(CC).args $(strip $(EXTRA_CFLAGS) $(2)) | $(PYTHON) cpp-filter.py $(pp_filter) >> $(1)
endef
define link_EXE
$(call green_msg, Linking $(1))
$(call create_resp_file, link.args, $(LDFLAGS) $(2))
link -out:$(strip $(1)) @link.args > link.tmp
@cat link.tmp >> $(1:.exe=.map)
@rm -f $(1:.exe=.exp) $(1:.exe=.lib)
@$(PYTHON) check-for-unused-libs.py link.tmp
endef
define create_res_file
rc $(RCFLAGS) -Fo./$(strip $(1)) $(2)
@echo
endef
define curl.rc
#include <winver.h>
#include "tool_version.h"
APPICON ICON "curl.ico"
#if defined(__clang__)
#define RC_COMPILER "clang-cl"
#elif defined(_MSC_VER)
#define RC_COMPILER "MSVC"
#else
#error "Unsupported target"
#endif
LANGUAGE 0, 0
#define RC_VERSION CURL_VERSION_MAJOR, CURL_VERSION_MINOR, CURL_VERSION_PATCH, 0
VS_VERSION_INFO VERSIONINFO
FILEVERSION RC_VERSION
PRODUCTVERSION RC_VERSION
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
#if defined(DEBUGBUILD) || defined(_DEBUG)
FILEFLAGS VS_FF_DEBUG
#else
FILEFLAGS 0L
#endif
FILEOS VOS__WINDOWS32
FILETYPE VFT_APP
FILESUBTYPE 0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "CompanyName", "curl, https://curl.se/"
VALUE "FileDescription", "The curl executable impersonating Chrome (" RC_COMPILER ", $(CPU))"
VALUE "FileVersion", CURL_VERSION
VALUE "InternalName", "curl-impersonate-chrome"
VALUE "OriginalFilename", "curl-impersonate-chrome"
VALUE "ProductName", "The curl executable"
VALUE "ProductVersion", CURL_VERSION
VALUE "LegalCopyright", "\xa9 " CURL_COPYRIGHT /* a9: Copyright symbol */
VALUE "License", "https://curl.se/docs/copyright.html"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END
endef
define curl_config.h
#define CURL_CA_BUNDLE "$(realpath $(CURL_CA_BUNDLE))"
#include "config-win32.h"
endef
define unistd.h
// #include <io.h>
endef
#
# Tell make to shut-up about the use of '$\\':
# warning: reference to undefined variable '\'
#
\ = \
define impersonate.c
#include "curl_setup.h"
#include <curl/curl.h>
#include "impersonate.h"
const struct impersonate_opts impersonations[] = {
{
.target = "ff91esr",
.httpversion = CURL_HTTP_VERSION_2_0,
.ssl_version = CURL_SSLVERSION_TLSv1_2 | CURL_SSLVERSION_MAX_DEFAULT,
.ciphers =
"aes_128_gcm_sha_256,"
"chacha20_poly1305_sha_256,"
"aes_256_gcm_sha_384,"
"ecdhe_ecdsa_aes_128_gcm_sha_256,"
"ecdhe_rsa_aes_128_gcm_sha_256,"
"ecdhe_ecdsa_chacha20_poly1305_sha_256,"
"ecdhe_rsa_chacha20_poly1305_sha_256,"
"ecdhe_ecdsa_aes_256_gcm_sha_384,"
"ecdhe_rsa_aes_256_gcm_sha_384,"
"ecdhe_ecdsa_aes_256_sha,"
"ecdhe_ecdsa_aes_128_sha,"
"ecdhe_rsa_aes_128_sha,"
"ecdhe_rsa_aes_256_sha,"
"rsa_aes_128_gcm_sha_256,"
"rsa_aes_256_gcm_sha_384,"
"rsa_aes_128_sha,"
"rsa_aes_256_sha,"
"rsa_3des_ede_cbc_sha",
.http_headers = {
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:91.0) Gecko/20100101 Firefox/91.0",
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Accept-Language: en-US,en;q=0.5",
"Accept-Encoding: gzip, deflate, br",
"Upgrade-Insecure-Requests: 1",
"Sec-Fetch-Dest: document",
"Sec-Fetch-Mode: navigate",
"Sec-Fetch-Site: none",
"Sec-Fetch-User: ?1",
"TE: Trailers"
}
},
{
.target = "ff95",
.httpversion = CURL_HTTP_VERSION_2_0,
.ssl_version = CURL_SSLVERSION_TLSv1_2 | CURL_SSLVERSION_MAX_DEFAULT,
.ciphers =
"aes_128_gcm_sha_256,"
"chacha20_poly1305_sha_256,"
"aes_256_gcm_sha_384,"
"ecdhe_ecdsa_aes_128_gcm_sha_256,"
"ecdhe_rsa_aes_128_gcm_sha_256,"
"ecdhe_ecdsa_chacha20_poly1305_sha_256,"
"ecdhe_rsa_chacha20_poly1305_sha_256,"
"ecdhe_ecdsa_aes_256_gcm_sha_384,"
"ecdhe_rsa_aes_256_gcm_sha_384,"
"ecdhe_ecdsa_aes_256_sha,"
"ecdhe_ecdsa_aes_128_sha,"
"ecdhe_rsa_aes_128_sha,"
"ecdhe_rsa_aes_256_sha,"
"rsa_aes_128_gcm_sha_256,"
"rsa_aes_256_gcm_sha_384,"
"rsa_aes_128_sha,"
"rsa_aes_256_sha",
.http_headers = {
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:95.0) Gecko/20100101 Firefox/95.0",
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language: en-US,en;q=0.5",
"Accept-Encoding: gzip, deflate, br",
"Upgrade-Insecure-Requests: 1",
"Sec-Fetch-Dest: document",
"Sec-Fetch-Mode: navigate",
"Sec-Fetch-Site: none",
"Sec-Fetch-User: ?1",
"TE: Trailers"
}
},
{
.target = "ff98",
.httpversion = CURL_HTTP_VERSION_2_0,
.ssl_version = CURL_SSLVERSION_TLSv1_2 | CURL_SSLVERSION_MAX_DEFAULT,
.ciphers =
"aes_128_gcm_sha_256,"
"chacha20_poly1305_sha_256,"
"aes_256_gcm_sha_384,"
"ecdhe_ecdsa_aes_128_gcm_sha_256,"
"ecdhe_rsa_aes_128_gcm_sha_256,"
"ecdhe_ecdsa_chacha20_poly1305_sha_256,"
"ecdhe_rsa_chacha20_poly1305_sha_256,"
"ecdhe_ecdsa_aes_256_gcm_sha_384,"
"ecdhe_rsa_aes_256_gcm_sha_384,"
"ecdhe_ecdsa_aes_256_sha,"
"ecdhe_ecdsa_aes_128_sha,"
"ecdhe_rsa_aes_128_sha,"
"ecdhe_rsa_aes_256_sha,"
"rsa_aes_128_gcm_sha_256,"
"rsa_aes_256_gcm_sha_384,"
"rsa_aes_128_sha,"
"rsa_aes_256_sha",
.http_headers = {
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:98.0) Gecko/20100101 Firefox/98.0",
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language: en-US,en;q=0.5",
"Accept-Encoding: gzip, deflate, br",
"Upgrade-Insecure-Requests: 1",
"Sec-Fetch-Dest: document",
"Sec-Fetch-Mode: navigate",
"Sec-Fetch-Site: none",
"Sec-Fetch-User: ?1",
"TE: Trailers"
}
},
{
.target = "ff100",
.httpversion = CURL_HTTP_VERSION_2_0,
.ssl_version = CURL_SSLVERSION_TLSv1_2 | CURL_SSLVERSION_MAX_DEFAULT,
.ciphers =
"aes_128_gcm_sha_256,"
"chacha20_poly1305_sha_256,"
"aes_256_gcm_sha_384,"
"ecdhe_ecdsa_aes_128_gcm_sha_256,"
"ecdhe_rsa_aes_128_gcm_sha_256,"
"ecdhe_ecdsa_chacha20_poly1305_sha_256,"
"ecdhe_rsa_chacha20_poly1305_sha_256,"
"ecdhe_ecdsa_aes_256_gcm_sha_384,"
"ecdhe_rsa_aes_256_gcm_sha_384,"
"ecdhe_ecdsa_aes_256_sha,"
"ecdhe_ecdsa_aes_128_sha,"
"ecdhe_rsa_aes_128_sha,"
"ecdhe_rsa_aes_256_sha,"
"rsa_aes_128_gcm_sha_256,"
"rsa_aes_256_gcm_sha_384,"
"rsa_aes_128_sha,"
"rsa_aes_256_sha",
.http_headers = {
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:100.0) Gecko/20100101 Firefox/100.0",
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language: en-US,en;q=0.5",
"Accept-Encoding: gzip, deflate, br",
"Upgrade-Insecure-Requests: 1",
"Sec-Fetch-Dest: document",
"Sec-Fetch-Mode: navigate",
"Sec-Fetch-Site: none",
"Sec-Fetch-User: ?1",
"TE: Trailers"
}
},
{
.target = "ff102",
.httpversion = CURL_HTTP_VERSION_2_0,
.ssl_version = CURL_SSLVERSION_TLSv1_2 | CURL_SSLVERSION_MAX_DEFAULT,
.ciphers =
"aes_128_gcm_sha_256,"
"chacha20_poly1305_sha_256,"
"aes_256_gcm_sha_384,"
"ecdhe_ecdsa_aes_128_gcm_sha_256,"
"ecdhe_rsa_aes_128_gcm_sha_256,"
"ecdhe_ecdsa_chacha20_poly1305_sha_256,"
"ecdhe_rsa_chacha20_poly1305_sha_256,"
"ecdhe_ecdsa_aes_256_gcm_sha_384,"
"ecdhe_rsa_aes_256_gcm_sha_384,"
"ecdhe_ecdsa_aes_256_sha,"
"ecdhe_ecdsa_aes_128_sha,"
"ecdhe_rsa_aes_128_sha,"
"ecdhe_rsa_aes_256_sha,"
"rsa_aes_128_gcm_sha_256,"
"rsa_aes_256_gcm_sha_384,"
"rsa_aes_128_sha,"
"rsa_aes_256_sha",
.http_headers = {
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:102.0) Gecko/20100101 Firefox/102.0",
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language: en-US,en;q=0.5",
"Accept-Encoding: gzip, deflate, br",
"Upgrade-Insecure-Requests: 1",
"Sec-Fetch-Dest: document",
"Sec-Fetch-Mode: navigate",
"Sec-Fetch-Site: none",
"Sec-Fetch-User: ?1",
"TE: Trailers"
}
},
{
.target = "ff109",
.httpversion = CURL_HTTP_VERSION_2_0,
.ssl_version = CURL_SSLVERSION_TLSv1_2 | CURL_SSLVERSION_MAX_DEFAULT,
.ciphers =
"aes_128_gcm_sha_256,"
"chacha20_poly1305_sha_256,"
"aes_256_gcm_sha_384,"
"ecdhe_ecdsa_aes_128_gcm_sha_256,"
"ecdhe_rsa_aes_128_gcm_sha_256,"
"ecdhe_ecdsa_chacha20_poly1305_sha_256,"
"ecdhe_rsa_chacha20_poly1305_sha_256,"
"ecdhe_ecdsa_aes_256_gcm_sha_384,"
"ecdhe_rsa_aes_256_gcm_sha_384,"
"ecdhe_ecdsa_aes_256_sha,"
"ecdhe_ecdsa_aes_128_sha,"
"ecdhe_rsa_aes_128_sha,"
"ecdhe_rsa_aes_256_sha,"
"rsa_aes_128_gcm_sha_256,"
"rsa_aes_256_gcm_sha_384,"
"rsa_aes_128_sha,"
"rsa_aes_256_sha",
.http_headers = {
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/109.0",
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language: en-US,en;q=0.5",
"Accept-Encoding: gzip, deflate, br",
"Upgrade-Insecure-Requests: 1",
"Sec-Fetch-Dest: document",
"Sec-Fetch-Mode: navigate",
"Sec-Fetch-Site: none",
"Sec-Fetch-User: ?1",
"TE: Trailers"
}
},
{
.target = "ff117",
.httpversion = CURL_HTTP_VERSION_2_0,
.ssl_version = CURL_SSLVERSION_TLSv1_2 | CURL_SSLVERSION_MAX_DEFAULT,
.ciphers =
"aes_128_gcm_sha_256,"
"chacha20_poly1305_sha_256,"
"aes_256_gcm_sha_384,"
"ecdhe_ecdsa_aes_128_gcm_sha_256,"
"ecdhe_rsa_aes_128_gcm_sha_256,"
"ecdhe_ecdsa_chacha20_poly1305_sha_256,"
"ecdhe_rsa_chacha20_poly1305_sha_256,"
"ecdhe_ecdsa_aes_256_gcm_sha_384,"
"ecdhe_rsa_aes_256_gcm_sha_384,"
"ecdhe_ecdsa_aes_256_sha,"
"ecdhe_ecdsa_aes_128_sha,"
"ecdhe_rsa_aes_128_sha,"
"ecdhe_rsa_aes_256_sha,"
"rsa_aes_128_gcm_sha_256,"
"rsa_aes_256_gcm_sha_384,"
"rsa_aes_128_sha,"
"rsa_aes_256_sha",
.http_headers = {
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/117.0",
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language: en-US,en;q=0.5",
"Accept-Encoding: gzip, deflate, br",
"Upgrade-Insecure-Requests: 1",
"Sec-Fetch-Dest: document",
"Sec-Fetch-Mode: navigate",
"Sec-Fetch-Site: none",
"Sec-Fetch-User: ?1",
"TE: Trailers"
}
},
{
/* Last one must be NULL. */
.target = NULL
}
};
endef
define impersonate.h
#ifndef HEADER_CURL_IMPERSONATE_H
#define HEADER_CURL_IMPERSONATE_H
#define IMPERSONATE_MAX_HEADERS 32
/*
* curl-impersonate: Options to be set for each supported target browser.
*/
struct impersonate_opts {
const char *target;
int httpversion;
int ssl_version;
const char *ciphers;
/* Elliptic curves (TLS extension 10).
* Passed to CURLOPT_SSL_EC_CURVES
*/
const char *curves;
/* Signature hash algorithms (TLS extension 13).
* Passed to CURLOPT_SSL_SIG_HASH_ALGS
*/
const char *sig_hash_algs;
/* Enable TLS NPN extension. */
bool npn;
/* Enable TLS ALPN extension. */
bool alpn;
/* Enable TLS ALPS extension. */
bool alps;
/* Enable TLS session ticket extension. */
bool tls_session_ticket;
/* TLS certificate compression algorithms.
* (TLS extension 27)
*/
const char *cert_compression;
const char *http_headers[IMPERSONATE_MAX_HEADERS];
const char *http2_pseudo_headers_order;
bool http2_no_server_push;
bool tls_permute_extensions;
/* Other TLS options will come here in the future once they are
* configurable through curl_easy_setopt()
*/
};
/*
* curl-impersonate: Global array of supported browsers and their
* impersonation options.
*/
extern const struct impersonate_opts impersonations[];
#endif /* HEADER_CURL_IMPERSONATE_H */
endef
define curl.h.patch
--- a/include/curl/curl.h
+++ b/include/curl/curl.h
@@ -2207,6 +2207,50 @@ typedef enum {
/* Can leak things, gonna exit() soon */
CURLOPT(CURLOPT_QUICK_EXIT, CURLOPTTYPE_LONG, 322),
+ /* curl-impersonate: A list of headers used by the impersonated browser.
+ * If given, merged with CURLOPT_HTTPHEADER. */
+ CURLOPT(CURLOPT_HTTPBASEHEADER, CURLOPTTYPE_SLISTPOINT, 324),
+
+ /* curl-impersonate: A list of TLS signature hash algorithms.
+ * See https://datatracker.ietf.org/doc/html/rfc5246#section-7.4.1.4.1 */
+ CURLOPT(CURLOPT_SSL_SIG_HASH_ALGS, CURLOPTTYPE_STRINGPOINT, 325),
+
+ /* curl-impersonate: Whether to enable ALPS in TLS or not.
+ * See https://datatracker.ietf.org/doc/html/draft-vvv-tls-alps.
+ * Support for ALPS is minimal and is intended only for the TLS client
+ * hello to match. */
+ CURLOPT(CURLOPT_SSL_ENABLE_ALPS, CURLOPTTYPE_LONG, 326),
+
+ /* curl-impersonate: Comma-separated list of certificate compression
+ * algorithms to use. These are published in the client hello.
+ * Supported algorithms are "zlib" and "brotli".
+ * See https://datatracker.ietf.org/doc/html/rfc8879 */
+ CURLOPT(CURLOPT_SSL_CERT_COMPRESSION, CURLOPTTYPE_STRINGPOINT, 327),
+
+ /* Enable/disable TLS session ticket extension (RFC5077) */
+ CURLOPT(CURLOPT_SSL_ENABLE_TICKET, CURLOPTTYPE_LONG, 328),
+
+ /*
+ * curl-impersonate:
+ * Set the order of the HTTP/2 pseudo headers. The value must contain
+ * the letters 'm', 'a', 's', 'p' representing the pseudo-headers
+ * ":method", ":authority", ":scheme", ":path" in the desired order of
+ * appearance in the HTTP/2 HEADERS frame.
+ */
+ CURLOPT(CURLOPT_HTTP2_PSEUDO_HEADERS_ORDER, CURLOPTTYPE_STRINGPOINT, 329),
+
+ /*
+ * curl-impersonate:
+ * Disable HTTP2 server push in the HTTP2 SETTINGS.
+ */
+ CURLOPT(CURLOPT_HTTP2_NO_SERVER_PUSH, CURLOPTTYPE_LONG, 330),
+
+ /*
+ * curl-impersonate: Whether to enable Boringssl permute extensions
+ * See https://commondatastorage.googleapis.com/chromium-boringssl-docs/ssl.h.html#SSL_set_permute_extensions.
+ */
+ CURLOPT(CURLOPT_SSL_PERMUTE_EXTENSIONS, CURLOPTTYPE_LONG, 331),
+
CURLOPT_LASTENTRY /* the last unused */
} CURLoption;
endef
define easy.h.patch
--- a/include/curl/easy.h
+++ b/include/curl/easy.h
@@ -43,6 +43,16 @@ CURL_EXTERN CURLcode curl_easy_setopt(CURL *curl, CURLoption option, ...);
CURL_EXTERN CURLcode curl_easy_perform(CURL *curl);
CURL_EXTERN void curl_easy_cleanup(CURL *curl);
+/*
+ * curl-impersonate: Tell libcurl to impersonate a browser.
+ * This is a wrapper function that calls curl_easy_setopt()
+ * multiple times with all the parameters required. That's also why it was
+ * created as a separate API function and not just as another option to
+ * curl_easy_setopt().
+ */
+CURL_EXTERN CURLcode curl_easy_impersonate(CURL *curl, const char *target,
+ int default_headers);
+
/*
* NAME curl_easy_getinfo()
*
endef
define dynhds.c.patch
--- a/lib/dynhds.c
+++ b/lib/dynhds.c
@@ -52,6 +52,8 @@ entry_new(const char *name, size_t namelen,
e->valuelen = valuelen;
if(opts & DYNHDS_OPT_LOWERCASE)
Curl_strntolower(e->name, e->name, e->namelen);
+ if(opts & DYNHDS_OPT_LOWERCASE_VAL)
+ Curl_strntolower(e->value, e->value, e->valuelen);
return e;
}
@@ -134,6 +136,16 @@ void Curl_dynhds_set_opts(struct dynhds *dynhds, int opts)
dynhds->opts = opts;
}
+void Curl_dynhds_set_opt(struct dynhds *dynhds, int opt)
+{
+ dynhds->opts |= opt;
+}
+
+void Curl_dynhds_del_opt(struct dynhds *dynhds, int opt)
+{
+ dynhds->opts &= ~opt;
+}
+
struct dynhds_entry *Curl_dynhds_getn(struct dynhds *dynhds, size_t n)
{
DEBUGASSERT(dynhds);
endef
define dynhds.h.patch
--- a/lib/dynhds.h
+++ b/lib/dynhds.h
@@ -53,6 +53,7 @@ struct dynhds {
#define DYNHDS_OPT_NONE (0)
#define DYNHDS_OPT_LOWERCASE (1 << 0)
+#define DYNHDS_OPT_LOWERCASE_VAL (1 << 1)
/**
* Init for use on first time or after a reset.
@@ -82,6 +83,8 @@ size_t Curl_dynhds_count(struct dynhds *dynhds);
* This will not have an effect on already existing headers.
*/
void Curl_dynhds_set_opts(struct dynhds *dynhds, int opts);
+void Curl_dynhds_set_opt(struct dynhds *dynhds, int opt);
+void Curl_dynhds_del_opt(struct dynhds *dynhds, int opt);
/**
* Return the n-th header entry or NULL if it does not exist.
endef
define easy.c.patch
--- a/lib/easy.c
+++ b/lib/easy.c
@@ -73,6 +73,8 @@
#include "dynbuf.h"
#include "altsvc.h"
#include "hsts.h"
+#include "strcase.h"
+#include "impersonate.h"
#include "easy_lock.h"
@@ -330,6 +332,134 @@ CURLsslset curl_global_sslset(curl_sslbackend id, const char *name,
return rc;
}
+/*
+ * curl-impersonate:
+ * Call curl_easy_setopt() with all the needed options as defined in the
+ * 'impersonations' array.
+ * */
+CURLcode curl_easy_impersonate(struct Curl_easy *data, const char *target,
+ int default_headers)
+{
+ int i;
+ int ret;
+ const struct impersonate_opts *opts = NULL;
+ struct curl_slist *headers = NULL;
+
+ for(opts = impersonations; opts->target != NULL; opts++) {
+ if (strcasecompare(target, opts->target)) {
+ break;
+ }
+ }
+
+ if(opts->target == NULL) {
+ DEBUGF(fprintf(stderr, "Error: unknown impersonation target '%s'\n",
+ target));
+ return CURLE_BAD_FUNCTION_ARGUMENT;
+ }
+
+ if(opts->httpversion != CURL_HTTP_VERSION_NONE) {
+ ret = curl_easy_setopt(data, CURLOPT_HTTP_VERSION, opts->httpversion);
+ if(ret)
+ return ret;
+ }
+
+ if (opts->ssl_version != CURL_SSLVERSION_DEFAULT) {
+ ret = curl_easy_setopt(data, CURLOPT_SSLVERSION, opts->ssl_version);
+ if(ret)
+ return ret;
+ }
+
+ if(opts->ciphers) {
+ ret = curl_easy_setopt(data, CURLOPT_SSL_CIPHER_LIST, opts->ciphers);
+ if (ret)
+ return ret;
+ }
+
+ if(opts->curves) {
+ ret = curl_easy_setopt(data, CURLOPT_SSL_EC_CURVES, opts->curves);
+ if(ret)
+ return ret;
+ }
+
+ if(opts->sig_hash_algs) {
+ ret = curl_easy_setopt(data, CURLOPT_SSL_SIG_HASH_ALGS,
+ opts->sig_hash_algs);
+ if(ret)
+ return ret;
+ }
+
+ ret = curl_easy_setopt(data, CURLOPT_SSL_ENABLE_NPN, opts->npn ? 1 : 0);
+ if(ret)
+ return ret;
+
+ ret = curl_easy_setopt(data, CURLOPT_SSL_ENABLE_ALPN, opts->alpn ? 1 : 0);
+ if(ret)
+ return ret;
+
+ ret = curl_easy_setopt(data, CURLOPT_SSL_ENABLE_ALPS, opts->alps ? 1 : 0);
+ if(ret)
+ return ret;
+
+ ret = curl_easy_setopt(data, CURLOPT_SSL_ENABLE_TICKET,
+ opts->tls_session_ticket ? 1 : 0);
+ if(ret)
+ return ret;
+
+ if(opts->tls_permute_extensions) {
+ ret = curl_easy_setopt(data, CURLOPT_SSL_PERMUTE_EXTENSIONS, 1);
+ if(ret)
+ return ret;
+ }
+
+ if(opts->cert_compression) {
+ ret = curl_easy_setopt(data,
+ CURLOPT_SSL_CERT_COMPRESSION,
+ opts->cert_compression);
+ if(ret)
+ return ret;
+ }
+
+ if(default_headers) {
+ /* Build a linked list out of the static array of headers. */
+ for(i = 0; i < IMPERSONATE_MAX_HEADERS; i++) {
+ if(opts->http_headers[i]) {
+ headers = curl_slist_append(headers, opts->http_headers[i]);
+ if(!headers) {
+ return CURLE_OUT_OF_MEMORY;
+ }
+ }
+ }
+
+ if(headers) {
+ ret = curl_easy_setopt(data, CURLOPT_HTTPBASEHEADER, headers);
+ curl_slist_free_all(headers);
+ if(ret)
+ return ret;
+ }
+ }
+
+ if(opts->http2_pseudo_headers_order) {
+ ret = curl_easy_setopt(data,
+ CURLOPT_HTTP2_PSEUDO_HEADERS_ORDER,
+ opts->http2_pseudo_headers_order);
+ if(ret)
+ return ret;
+ }
+
+ if(opts->http2_no_server_push) {
+ ret = curl_easy_setopt(data, CURLOPT_HTTP2_NO_SERVER_PUSH, 1L);
+ if(ret)
+ return ret;
+ }
+
+ /* Always enable all supported compressions. */
+ ret = curl_easy_setopt(data, CURLOPT_ACCEPT_ENCODING, "");
+ if(ret)
+ return ret;
+
+ return CURLE_OK;
+}
+
/*
* curl_easy_init() is the external interface to alloc, setup and init an
* easy handle that is returned. If anything goes wrong, NULL is returned.
@@ -338,6 +468,8 @@ struct Curl_easy *curl_easy_init(void)
{
CURLcode result;
struct Curl_easy *data;
+ char *env_target;
+ char *env_headers;
/* Make sure we inited the global SSL stuff */
global_init_lock();
@@ -360,6 +492,29 @@ struct Curl_easy *curl_easy_init(void)
return NULL;
}
+ /*
+ * curl-impersonate: Hook into curl_easy_init() to set the required options
+ * from an environment variable.
+ * This is a bit hacky but allows seamless integration of libcurl-impersonate
+ * without code modifications to the app.
+ */
+ env_target = curl_getenv("CURL_IMPERSONATE");
+ if(env_target) {
+ env_headers = curl_getenv("CURL_IMPERSONATE_HEADERS");
+ if(env_headers) {
+ result = curl_easy_impersonate(data, env_target,
+ !strcasecompare(env_headers, "no"));
+ free(env_headers);
+ } else {
+ result = curl_easy_impersonate(data, env_target, true);
+ }
+ free(env_target);
+ if(result) {
+ Curl_close(&data);
+ return NULL;
+ }
+ }
+
return data;
}
@@ -930,6 +1085,13 @@ struct Curl_easy *curl_easy_duphandle(struct Curl_easy *data)
outcurl->state.referer_alloc = TRUE;
}
+ if(data->state.base_headers) {
+ outcurl->state.base_headers =
+ Curl_slist_duplicate(data->state.base_headers);
+ if(!outcurl->state.base_headers)
+ goto fail;
+ }
+
/* Reinitialize an SSL engine for the new handle
* note: the engine name has already been copied by dupset */
if(outcurl->set.str[STRING_SSL_ENGINE]) {
@@ -1019,6 +1181,9 @@ fail:
*/
void curl_easy_reset(struct Curl_easy *data)
{
+ char *env_target;
+ char *env_headers;
+
Curl_free_request_state(data);
/* zero out UserDefined data: */
@@ -1043,6 +1208,23 @@ void curl_easy_reset(struct Curl_easy *data)
#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_CRYPTO_AUTH)
Curl_http_auth_cleanup_digest(data);
#endif
+
+ /*
+ * curl-impersonate: Hook into curl_easy_reset() to set the required options
+ * from an environment variable, just like in curl_easy_init().
+ */
+ env_target = curl_getenv("CURL_IMPERSONATE");
+ if(env_target) {
+ env_headers = curl_getenv("CURL_IMPERSONATE_HEADERS");
+ if(env_headers) {
+ curl_easy_impersonate(data, env_target,
+ !strcasecompare(env_headers, "no"));
+ free(env_headers);
+ } else {
+ curl_easy_impersonate(data, env_target, true);
+ }
+ free(env_target);
+ }
}
endef
define http.c.patch
--- a/lib/http.c
+++ b/lib/http.c
@@ -90,6 +90,7 @@
#include "ws.h"
#include "c-hyper.h"
#include "curl_ctype.h"
+#include "slist.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
@@ -1881,6 +1882,15 @@ CURLcode Curl_add_custom_headers(struct Curl_easy *data,
int numlists = 1; /* by default */
int i;
+ /*
+ * curl-impersonate: Use the merged list of headers if it exists (i.e. when
+ * the CURLOPT_HTTPBASEHEADER option was set.
+ */
+ struct curl_slist *noproxyheaders =
+ (data->state.merged_headers ?
+ data->state.merged_headers :
+ data->set.headers);
+
#ifndef CURL_DISABLE_PROXY
enum proxy_use proxy;
@@ -1892,10 +1902,10 @@ CURLcode Curl_add_custom_headers(struct Curl_easy *data,
switch(proxy) {
case HEADER_SERVER:
- h[0] = data->set.headers;
+ h[0] = noproxyheaders;
break;
case HEADER_PROXY:
- h[0] = data->set.headers;
+ h[0] = noproxyheaders;
if(data->set.sep_headers) {
h[1] = data->set.proxyheaders;
numlists++;
@@ -1905,12 +1915,12 @@ CURLcode Curl_add_custom_headers(struct Curl_easy *data,
if(data->set.sep_headers)
h[0] = data->set.proxyheaders;
else
- h[0] = data->set.headers;
+ h[0] = noproxyheaders;
break;
}
#else
(void)is_connect;
- h[0] = data->set.headers;
+ h[0] = noproxyheaders;
#endif
/* loop through one or two lists */
@@ -2146,6 +2156,108 @@ void Curl_http_method(struct Curl_easy *data, struct connectdata *conn,
*reqp = httpreq;
}
+/*
+ * curl-impersonate:
+ * Create a new linked list of headers.
+ * The new list is a merge between the "base" headers and the application given
+ * headers. The "base" headers contain curl-impersonate's list of headers
+ * used by default by the impersonated browser.
+ *
+ * The application given headers will override the "base" headers if supplied.
+ */
+CURLcode Curl_http_merge_headers(struct Curl_easy *data)
+{
+ int i;
+ int ret;
+ struct curl_slist *head;
+ struct curl_slist *dup = NULL;
+ struct curl_slist *new_list = NULL;
+ char *uagent;
+
+ if (!data->state.base_headers)
+ return CURLE_OK;
+
+ /* Duplicate the list for temporary use. */
+ if (data->set.headers) {
+ dup = Curl_slist_duplicate(data->set.headers);
+ if(!dup)
+ return CURLE_OUT_OF_MEMORY;
+ }
+
+ for(head = data->state.base_headers; head; head = head->next) {
+ char *sep;
+ size_t prefix_len;
+ bool found = FALSE;
+ struct curl_slist *head2;
+
+ sep = strchr(head->data, ':');
+ if(!sep)
+ continue;
+
+ prefix_len = sep - head->data;
+
+ /* Check if this header was added by the application. */
+ for(head2 = dup; head2; head2 = head2->next) {
+ if(head2->data &&
+ strncasecompare(head2->data, head->data, prefix_len) &&
+ Curl_headersep(head2->data[prefix_len]) ) {
+ new_list = curl_slist_append(new_list, head2->data);
+ /* Free and set to NULL to mark that it's been added. */
+ Curl_safefree(head2->data);
+ found = TRUE;
+ break;
+ }
+ }
+
+ /* If the user agent was set with CURLOPT_USERAGENT, but not with
+ * CURLOPT_HTTPHEADER, take it from there instead. */
+ if(!found &&
+ strncasecompare(head->data, "User-Agent", prefix_len) &&
+ data->set.str[STRING_USERAGENT] &&
+ *data->set.str[STRING_USERAGENT]) {
+ uagent = aprintf("User-Agent: %s", data->set.str[STRING_USERAGENT]);
+ if(!uagent) {
+ ret = CURLE_OUT_OF_MEMORY;
+ goto fail;
+ }
+ new_list = Curl_slist_append_nodup(new_list, uagent);
+ found = TRUE;
+ }
+
+ if (!found) {
+ new_list = curl_slist_append(new_list, head->data);
+ }
+
+ if (!new_list) {
+ ret = CURLE_OUT_OF_MEMORY;
+ goto fail;
+ }
+ }
+
+ /* Now go over any additional application-supplied headers. */
+ for(head = dup; head; head = head->next) {
+ if(head->data) {
+ new_list = curl_slist_append(new_list, head->data);
+ if(!new_list) {
+ ret = CURLE_OUT_OF_MEMORY;
+ goto fail;
+ }
+ }
+ }
+
+ curl_slist_free_all(dup);
+ /* Save the new, merged list separately, so it can be freed later. */
+ curl_slist_free_all(data->state.merged_headers);
+ data->state.merged_headers = new_list;
+
+ return CURLE_OK;
+
+fail:
+ Curl_safefree(dup);
+ curl_slist_free_all(new_list);
+ return ret;
+}
+
CURLcode Curl_http_useragent(struct Curl_easy *data)
{
/* The User-Agent string might have been allocated in url.c already, because
@@ -3165,6 +3277,11 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done)
http = data->req.p.http;
DEBUGASSERT(http);
+ /* curl-impersonate: Add HTTP headers to impersonate real browsers. */
+ result = Curl_http_merge_headers(data);
+ if (result)
+ return result;
+
result = Curl_http_host(data, conn);
if(result)
return result;
@@ -4777,12 +4894,41 @@ static bool h2_non_field(const char *name, size_t namelen)
return FALSE;
}
+/*
+ * curl-impersonate:
+ * Determine the position of HTTP/2 pseudo headers.
+ * The pseudo headers ":method", ":path", ":scheme", ":authority"
+ * are sent in different order by different browsers. An important part of the
+ * impersonation is ordering them like the browser does.
+ */
+static CURLcode h2_check_pseudo_header_order(const char *order)
+{
+ if(strlen(order) != 4)
+ return CURLE_BAD_FUNCTION_ARGUMENT;
+
+ // :method should always be first
+ if(order[0] != 'm')
+ return CURLE_BAD_FUNCTION_ARGUMENT;
+
+ // All pseudo-headers must be present
+ if(!strchr(order, 'm') ||
+ !strchr(order, 'a') ||
+ !strchr(order, 's') ||
+ !strchr(order, 'p'))
+ return CURLE_BAD_FUNCTION_ARGUMENT;
+
+ return CURLE_OK;
+}
+
CURLcode Curl_http_req_to_h2(struct dynhds *h2_headers,
struct httpreq *req, struct Curl_easy *data)
{
const char *scheme = NULL, *authority = NULL;
struct dynhds_entry *e;
size_t i;
+ // Use the Chrome ordering by default:
+ // :method, :authority, :scheme, :path
+ char *order = "masp";
CURLcode result;
DEBUGASSERT(req);
@@ -4816,25 +4962,56 @@ CURLcode Curl_http_req_to_h2(struct dynhds *h2_headers,
Curl_dynhds_reset(h2_headers);
Curl_dynhds_set_opts(h2_headers, DYNHDS_OPT_LOWERCASE);
- result = Curl_dynhds_add(h2_headers, STRCONST(HTTP_PSEUDO_METHOD),
- req->method, strlen(req->method));
- if(!result && scheme) {
- result = Curl_dynhds_add(h2_headers, STRCONST(HTTP_PSEUDO_SCHEME),
- scheme, strlen(scheme));
- }
- if(!result && authority) {
- result = Curl_dynhds_add(h2_headers, STRCONST(HTTP_PSEUDO_AUTHORITY),
- authority, strlen(authority));
+
+ /* curl-impersonate: order of pseudo headers is different from the default */
+ if(data->set.str[STRING_HTTP2_PSEUDO_HEADERS_ORDER]) {
+ order = data->set.str[STRING_HTTP2_PSEUDO_HEADERS_ORDER];
}
- if(!result && req->path) {
- result = Curl_dynhds_add(h2_headers, STRCONST(HTTP_PSEUDO_PATH),
- req->path, strlen(req->path));
+
+ result = h2_check_pseudo_header_order(order);
+
+ /* curl-impersonate: add http2 pseudo headers according to the specified order. */
+ for(i = 0; !result && i < strlen(order); ++i) {
+ switch(order[i]) {
+ case 'm':
+ result = Curl_dynhds_add(h2_headers, STRCONST(HTTP_PSEUDO_METHOD),
+ req->method, strlen(req->method));
+ break;
+ case 'a':
+ if(authority) {
+ result = Curl_dynhds_add(h2_headers, STRCONST(HTTP_PSEUDO_AUTHORITY),
+ authority, strlen(authority));
+ }
+ break;
+ case 's':
+ if(scheme) {
+ result = Curl_dynhds_add(h2_headers, STRCONST(HTTP_PSEUDO_SCHEME),
+ scheme, strlen(scheme));
+ }
+ break;
+ case 'p':
+ if(req->path) {
+ result = Curl_dynhds_add(h2_headers, STRCONST(HTTP_PSEUDO_PATH),
+ req->path, strlen(req->path));
+ }
+ break;
+ }
}
+
for(i = 0; !result && i < Curl_dynhds_count(&req->headers); ++i) {
e = Curl_dynhds_getn(&req->headers, i);
if(!h2_non_field(e->name, e->namelen)) {
+ /* curl-impersonate:
+ * Some HTTP/2 servers reject 'te' header value that is not lowercase (e.g. 'Trailers).
+ * Convert to lowercase explicitly.
+ */
+ if(e->namelen == 2 && strcasecompare(e->name, "te"))
+ Curl_dynhds_set_opt(h2_headers, DYNHDS_OPT_LOWERCASE_VAL);
+
result = Curl_dynhds_add(h2_headers, e->name, e->namelen,
e->value, e->valuelen);
+
+ Curl_dynhds_del_opt(h2_headers, DYNHDS_OPT_LOWERCASE_VAL);
}
}
endef
define http2.c.patch
--- a/lib/http2.c
+++ b/lib/http2.c
@@ -47,6 +47,7 @@
#include "transfer.h"
#include "dynbuf.h"
#include "headers.h"
+#include "rand.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
#include "curl_memory.h"
@@ -69,7 +70,7 @@
* use 16K as chunk size, as that fits H2 DATA frames well */
#define H2_CHUNK_SIZE (16 * 1024)
/* this is how much we want "in flight" for a stream */
-#define H2_STREAM_WINDOW_SIZE (10 * 1024 * 1024)
+#define H2_STREAM_WINDOW_SIZE (1024 * 1024)
/* on receiving from TLS, we prep for holding a full stream window */
#define H2_NW_RECV_CHUNKS (H2_STREAM_WINDOW_SIZE / H2_CHUNK_SIZE)
/* on send into TLS, we just want to accumulate small frames */
@@ -87,24 +88,48 @@
* will block their received QUOTA in the connection window. And if we
* run out of space, the server is blocked from sending us any data.
* See #10988 for an issue with this. */
-#define HTTP2_HUGE_WINDOW_SIZE (100 * H2_STREAM_WINDOW_SIZE)
+/* curl-impersonate: match Chrome window size. */
+#define HTTP2_HUGE_WINDOW_SIZE (15 * H2_STREAM_WINDOW_SIZE)
-#define H2_SETTINGS_IV_LEN 3
+#define H2_SETTINGS_IV_LEN 8
#define H2_BINSETTINGS_LEN 80
static int populate_settings(nghttp2_settings_entry *iv,
struct Curl_easy *data)
{
- iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
- iv[0].value = Curl_multi_max_concurrent_streams(data->multi);
+ int i = 0;
- iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
- iv[1].value = H2_STREAM_WINDOW_SIZE;
+ /* curl-impersonate: Align HTTP/2 settings to Chrome's */
+ iv[i].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
+ iv[i].value = 0x10000;
+ i++;
+
+ if(data->set.http2_no_server_push) {
+ iv[i].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
+ iv[i].value = 0;
+ i++;
+ }
+
+ iv[i].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
+ iv[i].value = Curl_multi_max_concurrent_streams(data->multi);
+ i++;
+
+ iv[i].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
+ iv[i].value = 0x600000;
+ i++;
+
+ iv[i].settings_id = NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE;
+ iv[i].value = 0x40000;
+ i++;
+
+ // curl-impersonate:
+ // Up until Chrome 98, there was a randomly chosen setting number in the
+ // HTTP2 SETTINGS frame. This might be something similar to TLS GREASE.
+ // However, it seems to have been removed since.
+ // Curl_rand(data, (unsigned char *)&iv[4].settings_id, sizeof(iv[4].settings_id));
+ // Curl_rand(data, (unsigned char *)&iv[4].value, sizeof(iv[4].value));
- iv[2].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
- iv[2].value = data->multi->push_cb != NULL;
-
- return 3;
+ return i;
}
static size_t populate_binsettings(uint8_t *binsettings,
@@ -1750,11 +1775,17 @@
return rv;
}
+/*
+ * curl-impersonate: Use Chrome's default HTTP/2 stream weight
+ * instead of NGINX default stream weight.
+ */
+#define CHROME_DEFAULT_STREAM_WEIGHT (256)
+
static int sweight_wanted(const struct Curl_easy *data)
{
/* 0 weight is not set by user and we take the nghttp2 default one */
return data->set.priority.weight?
- data->set.priority.weight : NGHTTP2_DEFAULT_WEIGHT;
+ data->set.priority.weight : CHROME_DEFAULT_STREAM_WEIGHT;
}
static int sweight_in_effect(const struct Curl_easy *data)
@@ -1776,9 +1807,11 @@
struct Curl_data_priority *prio = &data->set.priority;
struct stream_ctx *depstream = H2_STREAM_CTX(prio->parent);
int32_t depstream_id = depstream? depstream->id:0;
+ /* curl-impersonate: Set stream exclusive flag to true. */
+ int exclusive = 1;
nghttp2_priority_spec_init(pri_spec, depstream_id,
sweight_wanted(data),
- data->set.priority.exclusive);
+ exclusive);
data->state.priority = *prio;
}
@@ -1795,21 +1828,26 @@
struct stream_ctx *stream = H2_STREAM_CTX(data);
int rv = 0;
+ /* curl-impersonate: Check if stream exclusive flag is true. */
if(stream && stream->id > 0 &&
((sweight_wanted(data) != sweight_in_effect(data)) ||
- (data->set.priority.exclusive != data->state.priority.exclusive) ||
- (data->set.priority.parent != data->state.priority.parent)) ) {
+ (data->set.priority.exclusive != 1) ||
+ (data->set.priority.parent != data->state.priority.parent))) {
/* send new weight and/or dependency */
nghttp2_priority_spec pri_spec;
h2_pri_spec(data, &pri_spec);
- CURL_TRC_CF(data, cf, "[%d] Queuing PRIORITY", stream->id);
+ /* curl-impersonate: Don't send PRIORITY frames for main stream. */
+ if(stream->id != 1) {
+ DEBUGF(LOG_CF(data, cf, "[h2sid=%d] Queuing PRIORITY",
+ stream->id));
DEBUGASSERT(stream->id != -1);
rv = nghttp2_submit_priority(ctx->h2, NGHTTP2_FLAG_NONE,
stream->id, &pri_spec);
if(rv)
goto out;
}
+ }
ctx->nw_out_blocked = 0;
while(!rv && !ctx->nw_out_blocked && nghttp2_session_want_write(ctx->h2))
endef
define http2.h.patch
--- a/lib/http2.h
+++ b/lib/http2.h
@@ -31,7 +31,8 @@
/* value for MAX_CONCURRENT_STREAMS we use until we get an updated setting
from the peer */
-#define DEFAULT_MAX_CONCURRENT_STREAMS 100
+/* curl-impersonate: Use 1000 concurrent streams like Chrome. */
+#define DEFAULT_MAX_CONCURRENT_STREAMS 1000
/*
* Store nghttp2 version info in this buffer.
endef
define multi.c.patch
--- a/lib/multi.c
+++ b/lib/multi.c
@@ -424,7 +424,8 @@ struct Curl_multi *Curl_multi_handle(int hashsize, /* socket hash */
/* -1 means it not set by user, use the default value */
multi->maxconnects = -1;
- multi->max_concurrent_streams = 100;
+ /* curl-impersonate: Use 1000 concurrent streams like Chrome. */
+ multi->max_concurrent_streams = 1000;
#ifdef USE_WINSOCK
multi->wsa_event = WSACreateEvent();
endef
define setopt.c.patch
--- a/lib/setopt.c
+++ b/lib/setopt.c
@@ -50,6 +50,7 @@
#include "multiif.h"
#include "altsvc.h"
#include "hsts.h"
+#include "slist.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
@@ -712,6 +713,23 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
va_arg(param, char *));
break;
+ case CURLOPT_HTTPBASEHEADER:
+ /*
+ * curl-impersonate:
+ * Set a list of "base" headers. These will be merged with any headers
+ * set by CURLOPT_HTTPHEADER. curl-impersonate uses this option in order
+ * to set a list of default browser headers.
+ *
+ * Unlike CURLOPT_HTTPHEADER,
+ * the list is copied and can be immediately freed by the user.
+ */
+ curl_slist_free_all(data->state.base_headers);
+ data->state.base_headers = $\\
+ Curl_slist_duplicate(va_arg(param, struct curl_slist *));
+ if (!data->state.base_headers)
+ result = CURLE_OUT_OF_MEMORY;
+ break;
+
#ifndef CURL_DISABLE_PROXY
case CURLOPT_PROXYHEADER:
/*
@@ -2410,6 +2428,27 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
result = Curl_setstropt(&data->set.str[STRING_SSL_EC_CURVES],
va_arg(param, char *));
break;
+
+ case CURLOPT_SSL_SIG_HASH_ALGS:
+ /*
+ * Set the list of hash algorithms we want to use in the SSL connection.
+ * Specify comma-delimited list of algorithms to use.
+ */
+ result = Curl_setstropt(&data->set.str[STRING_SSL_SIG_HASH_ALGS],
+ va_arg(param, char *));
+ break;
+
+ case CURLOPT_SSL_CERT_COMPRESSION:
+ /*
+ * Set the list of ceritifcate compression algorithms we support in the TLS
+ * connection.
+ * Specify comma-delimited list of algorithms to use. Options are "zlib"
+ * and "brotli".
+ */
+ result = Curl_setstropt(&data->set.str[STRING_SSL_CERT_COMPRESSION],
+ va_arg(param, char *));
+ break;
+
#endif
case CURLOPT_IPRESOLVE:
arg = va_arg(param, long);
@@ -2953,6 +2992,22 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
case CURLOPT_SSL_ENABLE_ALPN:
data->set.ssl_enable_alpn = (0 != va_arg(param, long)) ? TRUE : FALSE;
break;
+ case CURLOPT_SSL_ENABLE_ALPS:
+ data->set.ssl_enable_alps = (0 != va_arg(param, long)) ? TRUE : FALSE;
+ break;
+ case CURLOPT_SSL_ENABLE_TICKET:
+ data->set.ssl_enable_ticket = (0 != va_arg(param, long)) ? TRUE : FALSE;
+ break;
+ case CURLOPT_SSL_PERMUTE_EXTENSIONS:
+ data->set.ssl_permute_extensions = (0 != va_arg(param, long)) ? TRUE : FALSE;
+ break;
+ case CURLOPT_HTTP2_PSEUDO_HEADERS_ORDER:
+ result = Curl_setstropt(&data->set.str[STRING_HTTP2_PSEUDO_HEADERS_ORDER],
+ va_arg(param, char *));
+ break;
+ case CURLOPT_HTTP2_NO_SERVER_PUSH:
+ data->set.http2_no_server_push = (0 != va_arg(param, long)) ? TRUE : FALSE;
+ break;
#ifdef USE_UNIX_SOCKETS
case CURLOPT_UNIX_SOCKET_PATH:
data->set.abstract_unix_socket = FALSE;
endef
define transfer.c.patch
--- a/lib/transfer.c
+++ b/lib/transfer.c
@@ -106,7 +106,15 @@ char *Curl_checkheaders(const struct Curl_easy *data,
DEBUGASSERT(thislen);
DEBUGASSERT(thisheader[thislen-1] != ':');
- for(head = data->set.headers; head; head = head->next) {
+ /*
+ * curl-impersonate:
+ * Check if we have overriden the user-supplied list of headers.
+ */
+ head = data->set.headers;
+ if (data->state.merged_headers)
+ head = data->state.merged_headers;
+
+ for(; head; head = head->next) {
if(strncasecompare(head->data, thisheader, thislen) &&
Curl_headersep(head->data[thislen]) )
return head->data;
endef
define url.c.patch
--- a/lib/url.c
+++ b/lib/url.c
@@ -444,6 +444,11 @@ CURLcode Curl_close(struct Curl_easy **datap)
Curl_safefree(data->state.aptr.proxyuser);
Curl_safefree(data->state.aptr.proxypasswd);
+ /* curl-impersonate: Free the list set by CURLOPT_HTTPBASEHEADER. */
+ curl_slist_free_all(data->state.base_headers);
+ /* curl-impersonate: Free the dynamic list of headers. */
+ curl_slist_free_all(data->state.merged_headers);
+
#ifndef CURL_DISABLE_DOH
if(data->req.doh) {
Curl_dyn_free(&data->req.doh->probe[0].serverdoh);
@@ -595,6 +600,7 @@ CURLcode Curl_init_userdefined(struct Curl_easy *data)
set->tcp_fastopen = FALSE;
set->tcp_nodelay = TRUE;
set->ssl_enable_alpn = TRUE;
+ set->ssl_enable_ticket = TRUE;
set->expect_100_timeout = 1000L; /* Wait for a second by default. */
set->sep_headers = TRUE; /* separated header lists by default */
set->buffer_size = READBUFFER_SIZE;
@@ -3584,6 +3590,9 @@ static CURLcode create_conn(struct Curl_easy *data,
data->set.ssl.primary.cert_blob = data->set.blobs[BLOB_CERT];
data->set.ssl.primary.ca_info_blob = data->set.blobs[BLOB_CAINFO];
data->set.ssl.primary.curves = data->set.str[STRING_SSL_EC_CURVES];
+ data->set.ssl.primary.sig_hash_algs = data->set.str[STRING_SSL_SIG_HASH_ALGS];
+ data->set.ssl.primary.cert_compression =
+ data->set.str[STRING_SSL_CERT_COMPRESSION];
#ifndef CURL_DISABLE_PROXY
data->set.proxy_ssl.primary.CApath = data->set.str[STRING_SSL_CAPATH_PROXY];
@@ -3695,6 +3704,11 @@ static CURLcode create_conn(struct Curl_easy *data,
(default) */
if(data->set.ssl_enable_alpn)
conn->bits.tls_enable_alpn = TRUE;
+
+ /* curl-impersonate: Turn on ALPS if ALPN is enabled and the bit is
+ * enabled. */
+ if(data->set.ssl_enable_alps)
+ conn->bits.tls_enable_alps = TRUE;
}
if(waitpipe)
endef
define urldata.h.patch
--- a/lib/urldata.h
+++ b/lib/urldata.h
@@ -283,6 +283,8 @@
char *password; /* TLS password (for, e.g., SRP) */
#endif
char *curves; /* list of curves to use */
+ char *sig_hash_algs; /* List of signature hash algorithms to use */
+ char *cert_compression; /* List of certificate compression algorithms. */
unsigned char ssl_options; /* the CURLOPT_SSL_OPTIONS bitmask */
unsigned int version_max; /* max supported version the client wants to use */
unsigned char version; /* what version the client wants to use */
@@ -534,6 +536,9 @@
BIT(multiplex); /* connection is multiplexed */
BIT(tcp_fastopen); /* use TCP Fast Open */
BIT(tls_enable_alpn); /* TLS ALPN extension? */
+ BIT(tls_enable_alps); /* TLS ALPS extension? */
+ BIT(tls_enable_ticket); /* TLS session ticket extension? */
+ BIT(tls_permute_extensions); /* TLS extension permutations */
#ifndef CURL_DISABLE_DOH
BIT(doh);
#endif
@@ -1427,6 +1432,19 @@
CURLcode hresult; /* used to pass return codes back from hyper callbacks */
#endif
+ /*
+ * curl-impersonate:
+ * List of "base" headers set by CURLOPT_HTTPBASEHEADER.
+ */
+ struct curl_slist *base_headers;
+ /*
+ * curl-impersonate:
+ * Dynamically-constructed list of HTTP headers.
+ * This list is a merge of the default HTTP headers needed to impersonate a
+ * browser, together with any user-supplied headers.
+ */
+ struct curl_slist *merged_headers;
+
/* Dynamically allocated strings, MUST be freed before this struct is
killed. */
struct dynamically_allocated_data {
@@ -1600,6 +1618,9 @@
STRING_SSL_EC_CURVES,
STRING_AWS_SIGV4, /* Parameters for V4 signature */
STRING_HAPROXY_CLIENT_IP, /* CURLOPT_HAPROXY_CLIENT_IP */
+ STRING_SSL_SIG_HASH_ALGS,
+ STRING_SSL_CERT_COMPRESSION,
+ STRING_HTTP2_PSEUDO_HEADERS_ORDER,
/* -- end of null-terminated strings -- */
@@ -1900,6 +1921,10 @@
BIT(tcp_keepalive); /* use TCP keepalives */
BIT(tcp_fastopen); /* use TCP Fast Open */
BIT(ssl_enable_alpn);/* TLS ALPN extension? */
+ BIT(ssl_enable_alps);/* TLS ALPS extension? */
+ BIT(ssl_enable_ticket); /* TLS session ticket extension */
+ BIT(ssl_permute_extensions); /* TLS Permute extensions */
+ BIT(http2_no_server_push); /* Disable HTTP2 server push */
BIT(path_as_is); /* allow dotdots? */
BIT(pipewait); /* wait for multiplex status before starting a new
connection */
endef
define openssl.c.patch
--- a/lib/vtls/openssl.c
+++ b/lib/vtls/openssl.c
@@ -79,6 +79,14 @@
#include <openssl/bio.h>
#include <openssl/buffer.h>
#include <openssl/pkcs12.h>
+#include <openssl/pool.h>
+
+#ifdef HAVE_LIBZ
+#include <zlib.h>
+#endif
+#ifdef HAVE_BROTLI
+#include <brotli/decode.h>
+#endif
#if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_OCSP)
#include <openssl/ocsp.h>
@@ -269,6 +277,113 @@
#define HAVE_OPENSSL_VERSION
#endif
+#if defined(OPENSSL_IS_BORINGSSL)
+#define HAVE_SSL_CTX_SET_VERIFY_ALGORITHM_PREFS
+
+/*
+ * kMaxSignatureAlgorithmNameLen and kSignatureAlgorithmNames
+ * Taken from BoringSSL, see ssl/ssl_privkey.cc
+ * */
+#define kMaxSignatureAlgorithmNameLen 23
+
+static const struct {
+ uint16_t signature_algorithm;
+ const char *name;
+} kSignatureAlgorithmNames[] = {
+ {SSL_SIGN_RSA_PKCS1_MD5_SHA1, "rsa_pkcs1_md5_sha1"},
+ {SSL_SIGN_RSA_PKCS1_SHA1, "rsa_pkcs1_sha1"},
+ {SSL_SIGN_RSA_PKCS1_SHA256, "rsa_pkcs1_sha256"},
+ {SSL_SIGN_RSA_PKCS1_SHA384, "rsa_pkcs1_sha384"},
+ {SSL_SIGN_RSA_PKCS1_SHA512, "rsa_pkcs1_sha512"},
+ {SSL_SIGN_ECDSA_SHA1, "ecdsa_sha1"},
+ {SSL_SIGN_ECDSA_SECP256R1_SHA256, "ecdsa_secp256r1_sha256"},
+ {SSL_SIGN_ECDSA_SECP384R1_SHA384, "ecdsa_secp384r1_sha384"},
+ {SSL_SIGN_ECDSA_SECP521R1_SHA512, "ecdsa_secp521r1_sha512"},
+ {SSL_SIGN_RSA_PSS_RSAE_SHA256, "rsa_pss_rsae_sha256"},
+ {SSL_SIGN_RSA_PSS_RSAE_SHA384, "rsa_pss_rsae_sha384"},
+ {SSL_SIGN_RSA_PSS_RSAE_SHA512, "rsa_pss_rsae_sha512"},
+ {SSL_SIGN_ED25519, "ed25519"},
+};
+
+#define MAX_SIG_ALGS $\\
+ sizeof(kSignatureAlgorithmNames) / sizeof(kSignatureAlgorithmNames[0])
+
+/* Default signature hash algorithms taken from Chrome/Chromium.
+ * See kVerifyPeers @ net/socket/ssl_client_socket_impl.cc */
+static const uint16_t default_sig_algs[] = {
+ SSL_SIGN_ECDSA_SECP256R1_SHA256, SSL_SIGN_RSA_PSS_RSAE_SHA256,
+ SSL_SIGN_RSA_PKCS1_SHA256, SSL_SIGN_ECDSA_SECP384R1_SHA384,
+ SSL_SIGN_RSA_PSS_RSAE_SHA384, SSL_SIGN_RSA_PKCS1_SHA384,
+ SSL_SIGN_RSA_PSS_RSAE_SHA512, SSL_SIGN_RSA_PKCS1_SHA512,
+};
+
+#define DEFAULT_SIG_ALGS_LENGTH $\\
+ sizeof(default_sig_algs) / sizeof(default_sig_algs[0])
+
+static CURLcode parse_sig_algs(struct Curl_easy *data,
+ const char *sigalgs,
+ uint16_t *algs,
+ size_t *nalgs)
+{
+ *nalgs = 0;
+ while (sigalgs && sigalgs[0]) {
+ int i;
+ bool found = FALSE;
+ const char *end;
+ size_t len;
+ char algname[kMaxSignatureAlgorithmNameLen + 1];
+
+ end = strpbrk(sigalgs, ":,");
+ if (end)
+ len = end - sigalgs;
+ else
+ len = strlen(sigalgs);
+
+ if (len > kMaxSignatureAlgorithmNameLen) {
+ failf(data, "Bad signature hash algorithm list");
+ return CURLE_BAD_FUNCTION_ARGUMENT;
+ }
+
+ if (!len) {
+ ++sigalgs;
+ continue;
+ }
+
+ if (*nalgs == MAX_SIG_ALGS) {
+ /* Reached the maximum number of possible algorithms, but more data
+ * available in the list. */
+ failf(data, "Bad signature hash algorithm list");
+ return CURLE_BAD_FUNCTION_ARGUMENT;
+ }
+
+ memcpy(algname, sigalgs, len);
+ algname[len] = 0;
+
+ for (i = 0; i < MAX_SIG_ALGS; i++) {
+ if (strcasecompare(algname, kSignatureAlgorithmNames[i].name)) {
+ algs[*nalgs] = kSignatureAlgorithmNames[i].signature_algorithm;
+ (*nalgs)++;
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (!found) {
+ failf(data, "Unknown signature hash algorithm: '%s'", algname);
+ return CURLE_BAD_FUNCTION_ARGUMENT;
+ }
+
+ if (end)
+ sigalgs = ++end;
+ else
+ break;
+ }
+
+ return CURLE_OK;
+}
+
+#endif
+
#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC)
typedef uint32_t sslerr_t;
#else
@@ -2614,6 +2729,151 @@
}
}
+#ifdef HAVE_LIBZ
+int DecompressZlibCert(SSL *ssl,
+ CRYPTO_BUFFER** out,
+ size_t uncompressed_len,
+ const uint8_t* in,
+ size_t in_len)
+{
+ z_stream strm;
+ uint8_t* data;
+ CRYPTO_BUFFER* decompressed = CRYPTO_BUFFER_alloc(&data, uncompressed_len);
+ if(!decompressed) {
+ return 0;
+ }
+
+ strm.zalloc = NULL;
+ strm.zfree = NULL;
+ strm.opaque = NULL;
+ strm.next_in = (Bytef *)in;
+ strm.avail_in = in_len;
+ strm.next_out = (Bytef *)data;
+ strm.avail_out = uncompressed_len;
+
+ if(inflateInit(&strm) != Z_OK) {
+ CRYPTO_BUFFER_free(decompressed);
+ return 0;
+ }
+
+ if(inflate(&strm, Z_FINISH) != Z_STREAM_END ||
+ strm.avail_in != 0 ||
+ strm.avail_out != 0) {
+ inflateEnd(&strm);
+ CRYPTO_BUFFER_free(decompressed);
+ return 0;
+ }
+
+ inflateEnd(&strm);
+ *out = decompressed;
+ return 1;
+}
+#endif
+
+#ifdef HAVE_BROTLI
+
+/* Taken from Chromium and adapted to C,
+ * see net/ssl/cert_compression.cc
+ */
+int DecompressBrotliCert(SSL* ssl,
+ CRYPTO_BUFFER** out,
+ size_t uncompressed_len,
+ const uint8_t* in,
+ size_t in_len) {
+ uint8_t* data;
+ CRYPTO_BUFFER* decompressed = CRYPTO_BUFFER_alloc(&data, uncompressed_len);
+ if (!decompressed) {
+ return 0;
+ }
+
+ size_t output_size = uncompressed_len;
+ if (BrotliDecoderDecompress(in_len, in, &output_size, data) !=
+ BROTLI_DECODER_RESULT_SUCCESS ||
+ output_size != uncompressed_len) {
+ CRYPTO_BUFFER_free(decompressed);
+ return 0;
+ }
+
+ *out = decompressed;
+ return 1;
+}
+#endif
+
+#if defined(HAVE_LIBZ) || defined(HAVE_BROTLI)
+static struct {
+ char *alg_name;
+ uint16_t alg_id;
+ ssl_cert_compression_func_t compress;
+ ssl_cert_decompression_func_t decompress;
+} cert_compress_algs[] = {
+#ifdef HAVE_LIBZ
+ {"zlib", TLSEXT_cert_compression_zlib, NULL, DecompressZlibCert},
+#endif
+#ifdef HAVE_BROTLI
+ {"brotli", TLSEXT_cert_compression_brotli, NULL, DecompressBrotliCert},
+#endif
+};
+
+#define NUM_CERT_COMPRESSION_ALGS $\\
+ sizeof(cert_compress_algs) / sizeof(cert_compress_algs[0])
+
+/*
+ * curl-impersonate:
+ * Add support for TLS extension 27 - compress_certificate.
+ * This calls the BoringSSL-specific API SSL_CTX_add_cert_compression_alg
+ * for each algorithm specified in cert_compression, which is a comma separated list.
+ */
+static CURLcode add_cert_compression(struct Curl_easy *data,
+ SSL_CTX *ctx,
+ const char *algorithms)
+{
+ int i;
+ const char *s = algorithms;
+ char *alg_name;
+ size_t alg_name_len;
+ bool found;
+
+ while (s && s[0]) {
+ found = FALSE;
+
+ for(i = 0; i < NUM_CERT_COMPRESSION_ALGS; i++) {
+ alg_name = cert_compress_algs[i].alg_name;
+ alg_name_len = strlen(alg_name);
+ if(strlen(s) >= alg_name_len &&
+ strncasecompare(s, alg_name, alg_name_len) &&
+ (s[alg_name_len] == ',' || s[alg_name_len] == 0)) {
+ if(!SSL_CTX_add_cert_compression_alg(ctx,
+ cert_compress_algs[i].alg_id,
+ cert_compress_algs[i].compress,
+ cert_compress_algs[i].decompress)) {
+ failf(data, "Error adding certificate compression algorithm '%s'",
+ alg_name);
+ return CURLE_SSL_CIPHER;
+ }
+ s += alg_name_len;
+ if(*s == ',')
+ s += 1;
+ found = TRUE;
+ break;
+ }
+ }
+
+ if(!found) {
+ failf(data, "Invalid compression algorithm list");
+ return CURLE_BAD_FUNCTION_ARGUMENT;
+ }
+ }
+
+ return CURLE_OK;
+}
+#else
+static CURLcode add_cert_compression(SSL_CTX *ctx, const char *algorithms)
+{
+ /* No compression algorithms are available. */
+ return CURLE_BAD_FUNCTION_ARGUMENT;
+}
+#endif
+
/*
* Our callback from the SSL/TLS layers.
*/
@@ -3570,7 +3830,14 @@
ctx_options = SSL_OP_ALL;
#ifdef SSL_OP_NO_TICKET
+ if(data->set.ssl_enable_ticket) {
+ /* curl-impersonate:
+ * Turn off SSL_OP_NO_TICKET, we want TLS extension 35 (session_ticket)
+ * to be present in the client hello. */
+ ctx_options &= ~SSL_OP_NO_TICKET;
+ } else {
ctx_options |= SSL_OP_NO_TICKET;
+ }
#endif
#ifdef SSL_OP_NO_COMPRESSION
@@ -3637,6 +3904,16 @@
}
#endif
+ SSL_CTX_set_options(backend->ctx,
+ SSL_OP_LEGACY_SERVER_CONNECT);
+ SSL_CTX_set_mode(backend->ctx,
+ SSL_MODE_CBC_RECORD_SPLITTING | SSL_MODE_ENABLE_FALSE_START);
+
+ /* curl-impersonate: Enable TLS extensions 5 - status_request and
+ * 18 - signed_certificate_timestamp. */
+ SSL_CTX_enable_signed_cert_timestamps(backend->ctx);
+ SSL_CTX_enable_ocsp_stapling(backend->ctx);
+
if(ssl_cert || ssl_cert_blob || ssl_cert_type) {
if(!result &&
!cert_stuff(data, backend->ctx,
@@ -3690,6 +3967,35 @@
}
#endif
+#ifdef HAVE_SSL_CTX_SET_VERIFY_ALGORITHM_PREFS
+ {
+ uint16_t algs[MAX_SIG_ALGS];
+ size_t nalgs;
+ /* curl-impersonate: Set the signature algorithms (TLS extension 13).
+ * See net/socket/ssl_client_socket_impl.cc in Chromium's source. */
+ char *sig_hash_algs = conn_config->sig_hash_algs;
+ if (sig_hash_algs) {
+ CURLcode result = parse_sig_algs(data, sig_hash_algs, algs, &nalgs);
+ if (result)
+ return result;
+ if (!SSL_CTX_set_verify_algorithm_prefs(backend->ctx, algs, nalgs)) {
+ failf(data, "failed setting signature hash algorithms list: '%s'",
+ sig_hash_algs);
+ return CURLE_SSL_CIPHER;
+ }
+ } else {
+ /* Use defaults from Chrome. */
+ if (!SSL_CTX_set_verify_algorithm_prefs(backend->ctx,
+ default_sig_algs,
+ DEFAULT_SIG_ALGS_LENGTH)) {
+ failf(data, "failed setting signature hash algorithms list: '%s'",
+ sig_hash_algs);
+ return CURLE_SSL_CIPHER;
+ }
+ }
+ }
+#endif
+
#ifdef USE_OPENSSL_SRP
if(ssl_config->primary.username && Curl_auth_allowed_to_host(data)) {
char * const ssl_username = ssl_config->primary.username;
@@ -3715,6 +4021,29 @@
}
#endif
+ /* curl-impersonate:
+ * Configure BoringSSL to behave like Chrome.
+ * See Constructor of SSLContext at net/socket/ssl_client_socket_impl.cc
+ * and SSLClientSocketImpl::Init()
+ * in the Chromium's source code. */
+
+ /* Enable TLS GREASE. */
+ SSL_CTX_set_grease_enabled(backend->ctx, 1);
+
+ /*
+ * curl-impersonate: Enable TLS extension permutation, enabled by default
+ * since Chrome 110.
+ */
+ if(data->set.ssl_permute_extensions) {
+ SSL_CTX_set_permute_extensions(backend->ctx, 1);
+ }
+
+ if(conn_config->cert_compression &&
+ add_cert_compression(data,
+ backend->ctx,
+ conn_config->cert_compression))
+ return CURLE_SSL_CIPHER;
+
/* OpenSSL always tries to verify the peer, this only says whether it should
* fail to connect if the verification fails, or if it should continue
* anyway. In the latter case the result of the verification is checked with
@@ -3770,6 +4099,23 @@
SSL_set_app_data(backend->handle, cf);
+#ifdef HAS_ALPN
+ if(connssl->alps) {
+ size_t i;
+ struct alpn_proto_buf proto;
+
+ for(i = 0; i < connssl->alps->count; ++i) {
+ /* curl-impersonate: Add the ALPS extension (17513) like Chrome does. */
+ SSL_add_application_settings(backend->handle, connssl->alps->entries[i],
+ strlen(connssl->alps->entries[i]), NULL,
+ 0);
+ }
+
+ Curl_alpn_to_proto_str(&proto, connssl->alps);
+ infof(data, VTLS_INFOF_ALPS_OFFER_1STR, proto.data);
+ }
+#endif
+
#if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_TLSEXT) && $\\
!defined(OPENSSL_NO_OCSP)
if(conn_config->verifystatus)
endef
define vtls.c.patch
--- a/lib/vtls/vtls.c
+++ b/lib/vtls/vtls.c
@@ -141,6 +141,9 @@ static const struct alpn_spec ALPN_SPEC_H11 = {
static const struct alpn_spec ALPN_SPEC_H2_H11 = {
{ ALPN_H2, ALPN_HTTP_1_1 }, 2
};
+static const struct alpn_spec ALPN_SPEC_H2 = {
+ { ALPN_H2 }, 1
+};
#endif
static const struct alpn_spec *alpn_get_spec(int httpwant, bool use_alpn)
@@ -155,6 +158,17 @@ static const struct alpn_spec *alpn_get_spec(int httpwant, bool use_alpn)
#endif
return &ALPN_SPEC_H11;
}
+
+static const struct alpn_spec *alps_get_spec(int httpwant, bool use_alps)
+{
+ if(!use_alps)
+ return NULL;
+#ifdef USE_HTTP2
+ if(httpwant >= CURL_HTTP_VERSION_2)
+ return &ALPN_SPEC_H2;
+#endif
+ return NULL;
+}
#endif /* USE_SSL */
@@ -182,6 +196,8 @@ Curl_ssl_config_matches(struct ssl_primary_config *data,
strcasecompare(data->cipher_list, needle->cipher_list) &&
strcasecompare(data->cipher_list13, needle->cipher_list13) &&
strcasecompare(data->curves, needle->curves) &&
+ strcasecompare(data->sig_hash_algs, needle->sig_hash_algs) &&
+ strcasecompare(data->cert_compression, needle->cert_compression) &&
strcasecompare(data->CRLfile, needle->CRLfile) &&
strcasecompare(data->pinned_key, needle->pinned_key))
return TRUE;
@@ -212,6 +228,8 @@ Curl_clone_primary_ssl_config(struct ssl_primary_config *source,
CLONE_STRING(cipher_list13);
CLONE_STRING(pinned_key);
CLONE_STRING(curves);
+ CLONE_STRING(sig_hash_algs);
+ CLONE_STRING(cert_compression);
CLONE_STRING(CRLfile);
#ifdef USE_TLS_SRP
CLONE_STRING(username);
@@ -234,6 +252,8 @@ void Curl_free_primary_ssl_config(struct ssl_primary_config *sslc)
Curl_safefree(sslc->ca_info_blob);
Curl_safefree(sslc->issuercert_blob);
Curl_safefree(sslc->curves);
+ Curl_safefree(sslc->sig_hash_algs);
+ Curl_safefree(sslc->cert_compression);
Curl_safefree(sslc->CRLfile);
#ifdef USE_TLS_SRP
Curl_safefree(sslc->username);
@@ -318,7 +338,8 @@ static bool ssl_prefs_check(struct Curl_easy *data)
}
static struct ssl_connect_data *cf_ctx_new(struct Curl_easy *data,
- const struct alpn_spec *alpn)
+ const struct alpn_spec *alpn,
+ const struct alpn_spec *alps)
{
struct ssl_connect_data *ctx;
@@ -328,6 +349,7 @@ static struct ssl_connect_data *cf_ctx_new(struct Curl_easy *data,
return NULL;
ctx->alpn = alpn;
+ ctx->alps = alps;
ctx->backend = calloc(1, Curl_ssl->sizeof_ssl_backend_data);
if(!ctx->backend) {
free(ctx);
@@ -1760,8 +1782,11 @@ static CURLcode cf_ssl_create(struct Curl_cfilter **pcf,
DEBUGASSERT(data->conn);
- ctx = cf_ctx_new(data, alpn_get_spec(data->state.httpwant,
- conn->bits.tls_enable_alpn));
+ ctx = cf_ctx_new(data,
+ alpn_get_spec(data->state.httpwant,
+ conn->bits.tls_enable_alpn),
+ alps_get_spec(data->state.httpwant,
+ conn->bits.tls_enable_alps));
if(!ctx) {
result = CURLE_OUT_OF_MEMORY;
goto out;
@@ -1811,6 +1836,7 @@ static CURLcode cf_ssl_proxy_create(struct Curl_cfilter **pcf,
struct ssl_connect_data *ctx;
CURLcode result;
bool use_alpn = conn->bits.tls_enable_alpn;
+ bool use_alps = conn->bits.tls_enable_alps;
int httpwant = CURL_HTTP_VERSION_1_1;
#ifdef USE_HTTP2
@@ -1820,7 +1846,8 @@ static CURLcode cf_ssl_proxy_create(struct Curl_cfilter **pcf,
}
#endif
- ctx = cf_ctx_new(data, alpn_get_spec(httpwant, use_alpn));
+ ctx = cf_ctx_new(data, alpn_get_spec(httpwant, use_alpn),
+ alps_get_spec(httpwant, use_alps));
if(!ctx) {
result = CURLE_OUT_OF_MEMORY;
goto out;
endef
define vtls.h.patch
--- a/lib/vtls/vtls.h
+++ b/lib/vtls/vtls.h
@@ -44,6 +44,8 @@ struct Curl_ssl_session;
"ALPN: server did not agree on a protocol. Uses default."
#define VTLS_INFOF_ALPN_OFFER_1STR $\\
"ALPN: curl offers %s"
+#define VTLS_INFOF_ALPS_OFFER_1STR $\\
+ "ALPS: curl offers %s"
#define VTLS_INFOF_ALPN_ACCEPTED_1STR $\\
ALPN_ACCEPTED "%s"
#define VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR $\\
endef
define vtls_int.h.patch
--- a/lib/vtls/vtls_int.h
+++ b/lib/vtls/vtls_int.h
@@ -73,6 +73,7 @@
char *hostname; /* hostname for verification */
char *dispname; /* display version of hostname */
const struct alpn_spec *alpn; /* ALPN to use or NULL for none */
+ const struct alpn_spec *alps; /* ALPS to use or NULL for none */
void *backend; /* vtls backend specific props */
struct cf_call_data call_data; /* data handle used in current call */
struct curltime handshake_done; /* time when handshake finished */
endef
define tool_cfgable.c.patch
--- a/src/tool_cfgable.c
+++ b/src/tool_cfgable.c
@@ -175,6 +175,11 @@
Curl_safefree(config->aws_sigv4);
Curl_safefree(config->proto_str);
Curl_safefree(config->proto_redir_str);
+
+ /* curl-impersonate */
+ Curl_safefree(config->ssl_sig_hash_algs);
+ Curl_safefree(config->ssl_cert_compression);
+ Curl_safefree(config->http2_pseudo_headers_order);
}
void config_free(struct OperationConfig *config)
endef
define tool_cfgable.h.patch
--- a/src/tool_cfgable.h
+++ b/src/tool_cfgable.h
@@ -161,8 +161,12 @@
bool crlf;
char *customrequest;
char *ssl_ec_curves;
+ char *ssl_sig_hash_algs;
+ char *ssl_cert_compression;
char *krblevel;
char *request_target;
+ char *http2_pseudo_headers_order;
+ bool http2_no_server_push;
long httpversion;
bool http09_allowed;
bool nobuffer;
@@ -192,6 +196,7 @@
struct curl_slist *prequote;
long ssl_version;
long ssl_version_max;
+ bool ssl_permute_extensions;
long proxy_ssl_version;
long ip_version;
long create_file_mode; /* CURLOPT_NEW_FILE_PERMS */
@@ -268,6 +273,8 @@
bool proxy_ssl_auto_client_cert; /* proxy version of ssl_auto_client_cert */
char *oauth_bearer; /* OAuth 2.0 bearer token */
bool noalpn; /* enable/disable TLS ALPN extension */
+ bool alps; /* enable/disable TLS ALPS extension */
+ bool noticket; /* enable/disable TLS session ticket */
char *unix_socket_path; /* path to Unix domain socket */
bool abstract_unix_socket; /* path to an abstract Unix domain socket */
bool falsestart;
endef
define tool_getparam.c.patch
--- a/src/tool_getparam.c
+++ b/src/tool_getparam.c
@@ -296,6 +296,13 @@
{"EC", "etag-save", ARG_FILENAME},
{"ED", "etag-compare", ARG_FILENAME},
{"EE", "curves", ARG_STRING},
+ {"EX", "signature-hashes", ARG_STRING},
+ {"EY", "alps", ARG_BOOL},
+ {"EI", "cert-compression", ARG_STRING},
+ {"EJ", "tls-session-ticket", ARG_BOOL},
+ {"EK", "http2-pseudo-headers-order", ARG_STRING},
+ {"EL", "http2-no-server-push", ARG_BOOL},
+ {"EM", "tls-permute-extensions", ARG_BOOL},
{"f", "fail", ARG_BOOL},
{"fa", "fail-early", ARG_BOOL},
{"fb", "styled-output", ARG_BOOL},
@@ -2117,6 +2124,39 @@
GetStr(&config->ssl_ec_curves, nextarg);
break;
+ case 'X':
+ /* --signature-hashes */
+ GetStr(&config->ssl_sig_hash_algs, nextarg);
+ break;
+
+ case 'Y':
+ /* --alps */
+ config->alps = toggle;
+ break;
+
+ case 'I':
+ /* --cert-compression */
+ GetStr(&config->ssl_cert_compression, nextarg);
+ break;
+
+ case 'J':
+ /* --tls-session-ticket */
+ config->noticket = (!toggle)?TRUE:FALSE;
+ break;
+
+ case 'K':
+ /* --http2-pseudo-headers-order */
+ GetStr(&config->http2_pseudo_headers_order, nextarg);
+ break;
+
+ case 'L':
+ /* --http2-no-server-push */
+ config->http2_no_server_push = toggle;
+ break;
+ case 'M':
+ /* --tls-permute-extensions */
+ config->ssl_permute_extensions = toggle;
+ break;
default: /* unknown flag */
err = PARAM_OPTION_UNKNOWN;
break;
endef
define tool_listhelp.c.patch
--- a/src/tool_listhelp.c
+++ b/src/tool_listhelp.c
@@ -108,6 +108,24 @@ const struct helptxt helptext[] = {
{" --curves <algorithm list>",
"(EC) TLS key exchange algorithm(s) to request",
CURLHELP_TLS},
+ {" --signature-hashes <algorithm list>",
+ "TLS signature hash algorithm(s) to use",
+ CURLHELP_TLS},
+ {" --cert-compression <algorithm list>",
+ "TLS cert compressions algorithm(s) to use",
+ CURLHELP_TLS},
+ {" --no-tls-session-ticket",
+ "Disable the TLS session ticket extension",
+ CURLHELP_TLS},
+ {" --http2-pseudo-headers-order",
+ "Change the order of the HTTP2 pseudo headers",
+ CURLHELP_HTTP},
+ {" --http2-no-server-push",
+ "Send HTTP2 setting to disable server push",
+ CURLHELP_HTTP},
+ {" --tls-permute-extensions",
+ "Enable BoringSSL TLS extensions permutations on client hello",
+ CURLHELP_TLS},
{"-d, --data <data>",
"HTTP POST data",
CURLHELP_IMPORTANT | CURLHELP_HTTP | CURLHELP_POST | CURLHELP_UPLOAD},
@@ -387,6 +405,9 @@ const struct helptxt helptext[] = {
{" --no-alpn",
"Disable the ALPN TLS extension",
CURLHELP_TLS | CURLHELP_HTTP},
+ {" --alps",
+ "Enable the ALPS TLS extension",
+ CURLHELP_TLS | CURLHELP_HTTP},
{"-N, --no-buffer",
"Disable buffering of the output stream",
CURLHELP_CURL},
endef
define tool_operate.c.patch
--- a/src/tool_operate.c
+++ b/src/tool_operate.c
@@ -1493,6 +1493,15 @@ static CURLcode single_transfer(struct GlobalConfig *global,
return result;
}
+ if(config->http2_pseudo_headers_order)
+ my_setopt_str(curl,
+ CURLOPT_HTTP2_PSEUDO_HEADERS_ORDER,
+ config->http2_pseudo_headers_order);
+
+ if(config->http2_no_server_push)
+ my_setopt(curl, CURLOPT_HTTP2_NO_SERVER_PUSH,
+ config->http2_no_server_push ? 1L : 0L);
+
} /* (proto_http) */
if(proto_ftp)
@@ -1581,6 +1590,14 @@ static CURLcode single_transfer(struct GlobalConfig *global,
if(config->ssl_ec_curves)
my_setopt_str(curl, CURLOPT_SSL_EC_CURVES, config->ssl_ec_curves);
+ if(config->ssl_sig_hash_algs)
+ my_setopt_str(curl, CURLOPT_SSL_SIG_HASH_ALGS,
+ config->ssl_sig_hash_algs);
+
+ if(config->ssl_cert_compression)
+ my_setopt_str(curl, CURLOPT_SSL_CERT_COMPRESSION,
+ config->ssl_cert_compression);
+
if(config->writeout)
my_setopt_str(curl, CURLOPT_CERTINFO, 1L);
@@ -1914,6 +1931,10 @@ static CURLcode single_transfer(struct GlobalConfig *global,
my_setopt_str(curl, CURLOPT_PROXY_TLS13_CIPHERS,
config->proxy_cipher13_list);
+ /* curl-impersonate */
+ if(config->ssl_permute_extensions)
+ my_setopt(curl, CURLOPT_SSL_PERMUTE_EXTENSIONS, 1L);
+
/* new in libcurl 7.9.2: */
if(config->disable_epsv)
/* disable it */
@@ -2123,6 +2144,14 @@ static CURLcode single_transfer(struct GlobalConfig *global,
my_setopt(curl, CURLOPT_SSL_ENABLE_ALPN, 0L);
}
+ if(config->alps) {
+ my_setopt(curl, CURLOPT_SSL_ENABLE_ALPS, 1L);
+ }
+
+ if (config->noticket) {
+ my_setopt(curl, CURLOPT_SSL_ENABLE_TICKET, 0L);
+ }
+
/* new in 7.40.0, abstract support added in 7.53.0 */
if(config->unix_socket_path) {
if(config->abstract_unix_socket) {
endef
define tool_setopt.c.patch
--- a/src/tool_setopt.c
+++ b/src/tool_setopt.c
@@ -153,6 +153,8 @@ static const struct NameValue setopt_nv_CURLNONZERODEFAULTS[] = {
NV1(CURLOPT_SSL_VERIFYHOST, 1),
NV1(CURLOPT_SSL_ENABLE_NPN, 1),
NV1(CURLOPT_SSL_ENABLE_ALPN, 1),
+ NV1(CURLOPT_SSL_ENABLE_TICKET, 1),
+ NV1(CURLOPT_SSL_PERMUTE_EXTENSIONS, 1),
NV1(CURLOPT_TCP_NODELAY, 1),
NV1(CURLOPT_PROXY_SSL_VERIFYPEER, 1),
NV1(CURLOPT_PROXY_SSL_VERIFYHOST, 1),
endef
define cpp-filter.py
import sys, os
empty_lines = 0
while True:
line = sys.stdin.readline()
if not line:
break
line = line.rstrip()
if line == "":
empty_lines += 1
continue
#
# MSVC or clang-cl 'line' directive
#
if line.lstrip().startswith("#line") or line.lstrip().startswith("# "):
line = line.replace (r"\\", "/")
print (line)
#
# Print a newline after a functions or structs
#
if line == "}" or line == "};":
print ("")
print ("Removed %d empty lines." % empty_lines, file=sys.stderr)
endef
define check-for-unused-lib.py
import os, sys
map_file = sys.argv[1]
ignore_libs = [ "oldnames.lib" ]
class State():
IDLE = 0
UNUSED = 1
class Colour():
RED = WHITE = RESET = ""
try:
import colorama
colorama.init()
Colour.RED = colorama.Fore.RED + colorama.Style.BRIGHT
Colour.WHITE = colorama.Fore.WHITE + colorama.Style.BRIGHT
Colour.RESET = colorama.Style.RESET_ALL
except:
pass
def cprint (colour, s):
print ("%s%s%s" % (colour, s, Colour.RESET))
def report (unused):
num = len(unused)
if num > 0:
cprint (Colour.RED, "%d unused %s in %s:" % (num, ["library", "libraries"][num > 1], map_file))
for u in unused:
print (" " + u)
cprint (Colour.WHITE, "Done.\n")
def process_map (state):
unused_libs = []
f = open (map_file, "rt")
lines = f.readlines()
f.close()
for l in lines:
l = l.strip()
if l == "Unused libraries:":
state = State.UNUSED
continue
if state == State.UNUSED:
if l == "":
break
if os.path.basename (l).lower() not in ignore_libs:
unused_libs.append (l)
return unused_libs
report (process_map(State.IDLE))
endef
-include .depend.Windows
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment