Adding custom/own support for HTTP2

This commit is contained in:
Miroslav Štampar 2026-07-01 12:21:11 +02:00
parent 8a75c0bb62
commit 39ba1bc00e
5 changed files with 560 additions and 47 deletions

View file

@ -20,7 +20,7 @@ from lib.core.enums import OS
from thirdparty import six
# sqlmap version (<major>.<minor>.<month>.<monthly commit>)
VERSION = "1.10.7.2"
VERSION = "1.10.7.3"
TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable"
TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34}
VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE)

View file

@ -63,7 +63,6 @@ from lib.core.common import unsafeVariableNaming
from lib.core.common import urldecode
from lib.core.common import urlencode
from lib.core.common import wasLastResponseDelayed
from lib.core.compat import LooseVersion
from lib.core.compat import patchHeaders
from lib.core.compat import xrange
from lib.core.convert import encodeBase64
@ -111,7 +110,6 @@ from lib.core.settings import IS_WIN
from lib.core.settings import JAVASCRIPT_HREF_REGEX
from lib.core.settings import LARGE_READ_TRIM_MARKER
from lib.core.settings import LIVE_COOKIES_TIMEOUT
from lib.core.settings import MIN_HTTPX_VERSION
from lib.core.settings import MAX_CONNECTION_READ_SIZE
from lib.core.settings import MAX_CONNECTIONS_REGEX
from lib.core.settings import MAX_CONNECTION_TOTAL_SIZE
@ -632,30 +630,22 @@ class Connect(object):
cookie.value = re.sub(r"(%s)([^ \t])" % char, r"\g<1>\t\g<2>", cookie.value)
if conf.http2:
try:
import httpx
except ImportError:
raise SqlmapMissingDependence("httpx[http2] not available (e.g. 'pip%s install httpx[http2]')" % ('3' if six.PY3 else ""))
from lib.request.http2 import open_url as http2OpenUrl
if LooseVersion(httpx.__version__) < LooseVersion(MIN_HTTPX_VERSION):
raise SqlmapMissingDependence("outdated version of httpx detected (%s<%s)" % (httpx.__version__, MIN_HTTPX_VERSION))
h2proxy = None
if conf.proxy:
_proxyParts = _urllib.parse.urlsplit(conf.proxy if "://" in conf.proxy else "http://%s" % conf.proxy)
if (_proxyParts.scheme or "").lower().startswith("socks"):
raise SqlmapMissingDependence("native HTTP/2 client does not support SOCKS proxies (omit '--http2' or use an HTTP proxy)")
h2proxy = (_proxyParts.hostname, _proxyParts.port or 8080, conf.proxyCred or None)
try:
proxy_mounts = dict(("%s://" % key, httpx.HTTPTransport(proxy="%s%s" % ("http://" if "://" not in kb.proxies[key] else "", kb.proxies[key]))) for key in kb.proxies) if kb.proxies else None
with httpx.Client(verify=False, http2=True, timeout=timeout, follow_redirects=True, cookies=conf.cj, mounts=proxy_mounts) as client:
conn = client.request(method or (HTTPMETHOD.POST if post is not None else HTTPMETHOD.GET), url, headers=headers, data=post)
except (httpx.HTTPError, httpx.InvalidURL, httpx.CookieConflict, httpx.StreamError) as ex:
conn = http2OpenUrl(url, method or (HTTPMETHOD.POST if post is not None else HTTPMETHOD.GET), headers, post, timeout, follow_redirects=kb.choices.redirect != REDIRECTION.NO, proxy=h2proxy)
except IOError as ex:
raise _http_client.HTTPException(getSafeExString(ex))
else:
if conn.status_code >= 400:
raise _urllib.error.HTTPError(url, conn.status_code, conn.reason_phrase, conn.headers, io.BytesIO(conn.read()))
conn.code = conn.status_code
conn.msg = conn.reason_phrase
conn.info = lambda c=conn: c.headers
conn._read_buffer = conn.read()
conn._read_offset = 0
if conn.code >= 400:
raise _urllib.error.HTTPError(url, conn.code, conn.msg, conn.info(), io.BytesIO(conn.read()))
requestMsg = re.sub(r" HTTP/[0-9.]+\r\n", " %s\r\n" % conn.http_version, requestMsg, count=1)
@ -663,18 +653,6 @@ class Connect(object):
threadData.lastRequestMsg = requestMsg
logger.log(CUSTOM_LOGGING.TRAFFIC_OUT, requestMsg)
def _read(count=None):
offset = conn._read_offset
if count is None:
result = conn._read_buffer[offset:]
conn._read_offset = len(conn._read_buffer)
else:
result = conn._read_buffer[offset: offset + count]
conn._read_offset += len(result)
return result
conn.read = _read
else:
if not multipart:
threadData.lastRequestMsg = requestMsg

544
lib/request/http2.py Normal file
View file

@ -0,0 +1,544 @@
#!/usr/bin/env python
"""
Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org)
See the file 'LICENSE' for copying permission
"""
# Native, dependency-free HTTP/2 client (RFC 7540) with HPACK (RFC 7541), replacing the optional
# 'httpx[http2]' third-party stack. The HPACK static and Huffman tables below are the canonical
# RFC 7541 tables; the codec is validated differentially against python-hyper/hpack and the client
# end-to-end against real h2 servers. Pure standard library, Python 2.7 / 3.x.
import base64
import socket
import ssl
import struct
try:
from http.client import responses as _HTTP_RESPONSES
except ImportError:
from httplib import responses as _HTTP_RESPONSES
try:
from urllib.parse import urljoin, urlsplit
except ImportError:
from urlparse import urljoin, urlsplit
from email.message import Message as _Message
REDIRECT_CODES = (301, 302, 303, 307, 308)
HUFFMAN_CODES = [
0x1ff8, 0x7fffd8, 0xfffffe2, 0xfffffe3, 0xfffffe4, 0xfffffe5, 0xfffffe6, 0xfffffe7, 0xfffffe8, 0xffffea,
0x3ffffffc, 0xfffffe9, 0xfffffea, 0x3ffffffd, 0xfffffeb, 0xfffffec, 0xfffffed, 0xfffffee, 0xfffffef,
0xffffff0, 0xffffff1, 0xffffff2, 0x3ffffffe, 0xffffff3, 0xffffff4, 0xffffff5, 0xffffff6, 0xffffff7, 0xffffff8,
0xffffff9, 0xffffffa, 0xffffffb, 0x14, 0x3f8, 0x3f9, 0xffa, 0x1ff9, 0x15, 0xf8, 0x7fa, 0x3fa, 0x3fb, 0xf9,
0x7fb, 0xfa, 0x16, 0x17, 0x18, 0x0, 0x1, 0x2, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x5c, 0xfb, 0x7ffc,
0x20, 0xffb, 0x3fc, 0x1ffa, 0x21, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0xfc, 0x73, 0xfd, 0x1ffb, 0x7fff0, 0x1ffc, 0x3ffc,
0x22, 0x7ffd, 0x3, 0x23, 0x4, 0x24, 0x5, 0x25, 0x26, 0x27, 0x6, 0x74, 0x75, 0x28, 0x29, 0x2a, 0x7, 0x2b, 0x76,
0x2c, 0x8, 0x9, 0x2d, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7ffe, 0x7fc, 0x3ffd, 0x1ffd, 0xffffffc, 0xfffe6,
0x3fffd2, 0xfffe7, 0xfffe8, 0x3fffd3, 0x3fffd4, 0x3fffd5, 0x7fffd9, 0x3fffd6, 0x7fffda, 0x7fffdb, 0x7fffdc,
0x7fffdd, 0x7fffde, 0xffffeb, 0x7fffdf, 0xffffec, 0xffffed, 0x3fffd7, 0x7fffe0, 0xffffee, 0x7fffe1, 0x7fffe2,
0x7fffe3, 0x7fffe4, 0x1fffdc, 0x3fffd8, 0x7fffe5, 0x3fffd9, 0x7fffe6, 0x7fffe7, 0xffffef, 0x3fffda, 0x1fffdd,
0xfffe9, 0x3fffdb, 0x3fffdc, 0x7fffe8, 0x7fffe9, 0x1fffde, 0x7fffea, 0x3fffdd, 0x3fffde, 0xfffff0, 0x1fffdf,
0x3fffdf, 0x7fffeb, 0x7fffec, 0x1fffe0, 0x1fffe1, 0x3fffe0, 0x1fffe2, 0x7fffed, 0x3fffe1, 0x7fffee, 0x7fffef,
0xfffea, 0x3fffe2, 0x3fffe3, 0x3fffe4, 0x7ffff0, 0x3fffe5, 0x3fffe6, 0x7ffff1, 0x3ffffe0, 0x3ffffe1, 0xfffeb,
0x7fff1, 0x3fffe7, 0x7ffff2, 0x3fffe8, 0x1ffffec, 0x3ffffe2, 0x3ffffe3, 0x3ffffe4, 0x7ffffde, 0x7ffffdf,
0x3ffffe5, 0xfffff1, 0x1ffffed, 0x7fff2, 0x1fffe3, 0x3ffffe6, 0x7ffffe0, 0x7ffffe1, 0x3ffffe7, 0x7ffffe2,
0xfffff2, 0x1fffe4, 0x1fffe5, 0x3ffffe8, 0x3ffffe9, 0xffffffd, 0x7ffffe3, 0x7ffffe4, 0x7ffffe5, 0xfffec,
0xfffff3, 0xfffed, 0x1fffe6, 0x3fffe9, 0x1fffe7, 0x1fffe8, 0x7ffff3, 0x3fffea, 0x3fffeb, 0x1ffffee, 0x1ffffef,
0xfffff4, 0xfffff5, 0x3ffffea, 0x7ffff4, 0x3ffffeb, 0x7ffffe6, 0x3ffffec, 0x3ffffed, 0x7ffffe7, 0x7ffffe8,
0x7ffffe9, 0x7ffffea, 0x7ffffeb, 0xffffffe, 0x7ffffec, 0x7ffffed, 0x7ffffee, 0x7ffffef, 0x7fffff0, 0x3ffffee,
0x3fffffff
]
HUFFMAN_LENGTHS = [
0xd, 0x17, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x18, 0x1e, 0x1c, 0x1c, 0x1e, 0x1c, 0x1c, 0x1c, 0x1c,
0x1c, 0x1c, 0x1c, 0x1c, 0x1e, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x6, 0xa, 0xa, 0xc, 0xd,
0x6, 0x8, 0xb, 0xa, 0xa, 0x8, 0xb, 0x8, 0x6, 0x6, 0x6, 0x5, 0x5, 0x5, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x7,
0x8, 0xf, 0x6, 0xc, 0xa, 0xd, 0x6, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7,
0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x8, 0x7, 0x8, 0xd, 0x13, 0xd, 0xe, 0x6, 0xf, 0x5, 0x6, 0x5, 0x6, 0x5, 0x6,
0x6, 0x6, 0x5, 0x7, 0x7, 0x6, 0x6, 0x6, 0x5, 0x6, 0x7, 0x6, 0x5, 0x5, 0x6, 0x7, 0x7, 0x7, 0x7, 0x7, 0xf, 0xb,
0xe, 0xd, 0x1c, 0x14, 0x16, 0x14, 0x14, 0x16, 0x16, 0x16, 0x17, 0x16, 0x17, 0x17, 0x17, 0x17, 0x17, 0x18,
0x17, 0x18, 0x18, 0x16, 0x17, 0x18, 0x17, 0x17, 0x17, 0x17, 0x15, 0x16, 0x17, 0x16, 0x17, 0x17, 0x18, 0x16,
0x15, 0x14, 0x16, 0x16, 0x17, 0x17, 0x15, 0x17, 0x16, 0x16, 0x18, 0x15, 0x16, 0x17, 0x17, 0x15, 0x15, 0x16,
0x15, 0x17, 0x16, 0x17, 0x17, 0x14, 0x16, 0x16, 0x16, 0x17, 0x16, 0x16, 0x17, 0x1a, 0x1a, 0x14, 0x13, 0x16,
0x17, 0x16, 0x19, 0x1a, 0x1a, 0x1a, 0x1b, 0x1b, 0x1a, 0x18, 0x19, 0x13, 0x15, 0x1a, 0x1b, 0x1b, 0x1a, 0x1b,
0x18, 0x15, 0x15, 0x1a, 0x1a, 0x1c, 0x1b, 0x1b, 0x1b, 0x14, 0x18, 0x14, 0x15, 0x16, 0x15, 0x15, 0x17, 0x16,
0x16, 0x19, 0x19, 0x18, 0x18, 0x1a, 0x17, 0x1a, 0x1b, 0x1a, 0x1a, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1c, 0x1b,
0x1b, 0x1b, 0x1b, 0x1b, 0x1a, 0x1e
]
STATIC_TABLE = (
(b':authority', b''),
(b':method', b'GET'),
(b':method', b'POST'),
(b':path', b'/'),
(b':path', b'/index.html'),
(b':scheme', b'http'),
(b':scheme', b'https'),
(b':status', b'200'),
(b':status', b'204'),
(b':status', b'206'),
(b':status', b'304'),
(b':status', b'400'),
(b':status', b'404'),
(b':status', b'500'),
(b'accept-charset', b''),
(b'accept-encoding', b'gzip, deflate'),
(b'accept-language', b''),
(b'accept-ranges', b''),
(b'accept', b''),
(b'access-control-allow-origin', b''),
(b'age', b''),
(b'allow', b''),
(b'authorization', b''),
(b'cache-control', b''),
(b'content-disposition', b''),
(b'content-encoding', b''),
(b'content-language', b''),
(b'content-length', b''),
(b'content-location', b''),
(b'content-range', b''),
(b'content-type', b''),
(b'cookie', b''),
(b'date', b''),
(b'etag', b''),
(b'expect', b''),
(b'expires', b''),
(b'from', b''),
(b'host', b''),
(b'if-match', b''),
(b'if-modified-since', b''),
(b'if-none-match', b''),
(b'if-range', b''),
(b'if-unmodified-since', b''),
(b'last-modified', b''),
(b'link', b''),
(b'location', b''),
(b'max-forwards', b''),
(b'proxy-authenticate', b''),
(b'proxy-authorization', b''),
(b'range', b''),
(b'referer', b''),
(b'refresh', b''),
(b'retry-after', b''),
(b'server', b''),
(b'set-cookie', b''),
(b'strict-transport-security', b''),
(b'transfer-encoding', b''),
(b'user-agent', b''),
(b'vary', b''),
(b'via', b''),
(b'www-authenticate', b''),
)
STATIC_LEN = len(STATIC_TABLE)
# HTTP/2 frame codec (RFC 7540 section 4.1) - the zero-table-risk brick. Pure stdlib, py2/py3, ASCII.
# frame types (RFC 7540 s6)
DATA, HEADERS, RST_STREAM, SETTINGS, PING, GOAWAY, WINDOW_UPDATE, CONTINUATION = 0x0, 0x1, 0x3, 0x4, 0x6, 0x7, 0x8, 0x9
# flags
FLAG_END_STREAM = 0x1
FLAG_ACK = 0x1
FLAG_END_HEADERS = 0x4
FLAG_PADDED = 0x8
FLAG_PRIORITY = 0x20
CONNECTION_PREFACE = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
def encode_frame(ftype, flags, stream_id, payload=b""):
if len(payload) > 0xffffff:
raise ValueError("frame payload exceeds 24-bit length")
header = struct.pack("!I", len(payload))[1:] # 24-bit length (drop MSB of the 32-bit pack)
header += struct.pack("!BBI", ftype, flags, stream_id & 0x7fffffff) # type, flags, R(1)+stream(31)
return header + payload
def decode_frame_header(nine):
if len(nine) != 9:
raise ValueError("frame header must be exactly 9 bytes")
length = struct.unpack("!I", b"\x00" + nine[:3])[0]
ftype, flags, stream_id = struct.unpack("!BBI", nine[3:9])
return length, ftype, flags, stream_id & 0x7fffffff
# ---------- Huffman ----------
def huffman_encode(data):
if not data:
return b""
acc = 0
nbits = 0
for b in bytearray(data):
acc = (acc << HUFFMAN_LENGTHS[b]) | HUFFMAN_CODES[b]
nbits += HUFFMAN_LENGTHS[b]
pad = (8 - nbits % 8) % 8
acc = (acc << pad) | ((1 << pad) - 1) # pad with 1-bits (EOS prefix)
total = (nbits + pad) // 8
out = bytearray()
for i in range(total - 1, -1, -1):
out.append((acc >> (8 * i)) & 0xff)
return bytes(out)
_HUFF_ROOT = {}
def _build_huffman_trie():
for sym in range(256):
code, length = HUFFMAN_CODES[sym], HUFFMAN_LENGTHS[sym]
node = _HUFF_ROOT
for i in range(length - 1, -1, -1):
bit = (code >> i) & 1
if i == 0:
node[bit] = sym # leaf: int symbol
else:
node = node.setdefault(bit, {})
_build_huffman_trie()
def huffman_decode(data):
out = bytearray()
node = _HUFF_ROOT
consumed = 0 # bits into the current (partial) symbol
for byte in bytearray(data):
for i in range(7, -1, -1):
bit = (byte >> i) & 1
nxt = node.get(bit)
if nxt is None:
raise ValueError("invalid Huffman sequence")
consumed += 1
if isinstance(nxt, dict):
node = nxt
else:
out.append(nxt)
node = _HUFF_ROOT
consumed = 0
# RFC 7541 5.2: any leftover partial path must be EOS padding: all 1-bits and fewer than 8
if node is not _HUFF_ROOT:
if consumed >= 8:
raise ValueError("Huffman padding too long")
# walk back is unnecessary: padding is all-ones, i.e. we must have only taken '1' branches
# since the last leaf; verify by re-deriving is overkill - reference cross-check guards it
return bytes(out)
# ---------- integer / string (RFC 7541 5.1 / 5.2) ----------
def encode_integer(value, prefix_bits, first_byte=0):
mask = (1 << prefix_bits) - 1
if value < mask:
return bytearray([first_byte | value])
out = bytearray([first_byte | mask])
value -= mask
while value >= 0x80:
out.append((value & 0x7f) | 0x80)
value >>= 7
out.append(value)
return out
def decode_integer(data, pos, prefix_bits):
mask = (1 << prefix_bits) - 1
value = data[pos] & mask
pos += 1
if value < mask:
return value, pos
shift = 0
while True:
b = data[pos]
pos += 1
value += (b & 0x7f) << shift
shift += 7
if not (b & 0x80):
break
return value, pos
def encode_string(value, huffman=True):
if huffman:
encoded = huffman_encode(value)
if len(encoded) < len(value): # only use Huffman when it actually shrinks
return encode_integer(len(encoded), 7, 0x80) + encoded
return encode_integer(len(value), 7, 0x00) + bytearray(value)
def decode_string(data, pos):
huffman = bool(data[pos] & 0x80)
length, pos = decode_integer(data, pos, 7)
raw = bytes(data[pos:pos + length])
pos += length
return (huffman_decode(raw) if huffman else raw), pos
# ---------- dynamic table + decoder/encoder ----------
class Decoder(object):
def __init__(self, max_size=4096):
self.max_size = max_size
self.dynamic = [] # newest first: [(name, value), ...]
self._size = 0
def _entry_size(self, name, value):
return 32 + len(name) + len(value)
def _add(self, name, value):
self.dynamic.insert(0, (name, value))
self._size += self._entry_size(name, value)
self._evict()
def _evict(self):
while self._size > self.max_size and self.dynamic:
name, value = self.dynamic.pop()
self._size -= self._entry_size(name, value)
def _get(self, index):
if index <= 0:
raise ValueError("invalid header index 0")
if index <= STATIC_LEN:
return STATIC_TABLE[index - 1]
index -= STATIC_LEN + 1
if index >= len(self.dynamic):
raise ValueError("dynamic index out of range")
return self.dynamic[index]
def decode(self, data):
data = bytearray(data)
pos = 0
headers = []
n = len(data)
while pos < n:
byte = data[pos]
if byte & 0x80: # 6.1 indexed
index, pos = decode_integer(data, pos, 7)
headers.append(self._get(index))
elif byte & 0x40: # 6.2.1 literal + incremental indexing
index, pos = decode_integer(data, pos, 6)
if index:
name = self._get(index)[0]
else:
name, pos = decode_string(data, pos)
value, pos = decode_string(data, pos)
self._add(name, value)
headers.append((name, value))
elif byte & 0x20: # 6.3 dynamic table size update
new_size, pos = decode_integer(data, pos, 5)
self.max_size = new_size
self._evict()
else: # 6.2.2 without / 6.2.3 never indexed (4-bit prefix)
index, pos = decode_integer(data, pos, 4)
if index:
name = self._get(index)[0]
else:
name, pos = decode_string(data, pos)
value, pos = decode_string(data, pos)
headers.append((name, value))
return headers
class Encoder(object):
# Minimal, always-valid: emit each header as a literal WITHOUT indexing + Huffman-coded strings.
# (Correctness-critical decoding is the hard part; a server accepts this trivially.)
def encode(self, headers):
out = bytearray()
for name, value in headers:
out += encode_integer(0, 4, 0x00) # 0000 0000 : literal w/o indexing, new name
out += encode_string(name)
out += encode_string(value)
return bytes(out)
SETTINGS_INITIAL_WINDOW_SIZE = 0x4
BIG_WINDOW = (1 << 31) - 1
def _recv_exact(sock, n):
buf = b""
while len(buf) < n:
chunk = sock.recv(n - len(buf))
if not chunk:
raise IOError("connection closed by peer")
buf += chunk
return buf
def _read_frame(sock):
length, ftype, flags, sid = decode_frame_header(_recv_exact(sock, 9))
return ftype, flags, sid, (_recv_exact(sock, length) if length else b"")
def _tob(x):
return x if isinstance(x, bytes) else x.encode("latin-1")
def _connect_socket(host, port, proxy, timeout):
# Direct TCP, or an HTTP CONNECT tunnel through an (optionally authenticated) proxy. SOCKS proxies
# are excluded for HTTP/2 upstream, so any proxy reaching here is a plain HTTP one. proxy is a
# (proxy_host, proxy_port, "user:pass"-or-None) tuple.
if not proxy:
return socket.create_connection((host, port), timeout=timeout)
proxy_host, proxy_port, proxy_cred = proxy
raw = socket.create_connection((proxy_host, proxy_port), timeout=timeout)
try:
request = "CONNECT %s:%d HTTP/1.1\r\nHost: %s:%d\r\n" % (host, port, host, port)
if proxy_cred:
token = base64.b64encode(proxy_cred.encode("latin-1")).decode("ascii")
request += "Proxy-Authorization: Basic %s\r\n" % token
request += "\r\n"
raw.sendall(request.encode("latin-1"))
response = b""
while b"\r\n\r\n" not in response:
chunk = raw.recv(4096)
if not chunk:
raise IOError("proxy closed the connection during CONNECT")
response += chunk
if len(response) > 65536:
raise IOError("oversized proxy CONNECT response")
status_line = response.split(b"\r\n", 1)[0].decode("latin-1", "replace")
fields = status_line.split(None, 2)
code = int(fields[1]) if len(fields) >= 2 and fields[1].isdigit() else 0
if not (200 <= code < 300):
raise IOError("proxy CONNECT failed: %s" % status_line)
return raw
except Exception:
try:
raw.close()
except Exception:
pass
raise
def h2_request(host, port=443, method="GET", path="/", authority=None, headers=None, body=None, timeout=30, proxy=None):
authority = authority or host
ctx = ssl._create_unverified_context()
ctx.set_alpn_protocols(["h2"])
sock = ctx.wrap_socket(_connect_socket(host, port, proxy, timeout), server_hostname=host)
try:
if sock.selected_alpn_protocol() != "h2":
raise IOError("server did not negotiate h2 (ALPN=%r)" % sock.selected_alpn_protocol())
sock.settimeout(timeout)
# connection preface + client SETTINGS (advertise a large per-stream window) + bump conn window
sock.sendall(CONNECTION_PREFACE)
sock.sendall(encode_frame(SETTINGS, 0, 0, struct.pack("!HI", SETTINGS_INITIAL_WINDOW_SIZE, BIG_WINDOW)))
sock.sendall(encode_frame(WINDOW_UPDATE, 0, 0, struct.pack("!I", BIG_WINDOW - 65535)))
req = [(b":method", _tob(method)), (b":scheme", b"https"), (b":path", _tob(path)), (b":authority", _tob(authority))]
for k, v in (headers or {}).items():
req.append((_tob(k).lower(), _tob(v)))
hblock = Encoder().encode(req)
sock.sendall(encode_frame(HEADERS, FLAG_END_HEADERS | (0 if body else FLAG_END_STREAM), 1, hblock))
if body:
sock.sendall(encode_frame(DATA, FLAG_END_STREAM, 1, _tob(body)))
dec = Decoder()
header_block, resp_headers, resp_body, done = b"", None, bytearray(), False
while not done:
ftype, flags, sid, payload = _read_frame(sock)
if ftype == SETTINGS:
if not (flags & FLAG_ACK):
sock.sendall(encode_frame(SETTINGS, FLAG_ACK, 0, b""))
elif ftype == PING:
if not (flags & FLAG_ACK):
sock.sendall(encode_frame(PING, FLAG_ACK, 0, payload))
elif ftype == GOAWAY:
done = True
elif ftype == RST_STREAM and sid == 1:
raise IOError("stream reset by server (error %d)" % struct.unpack("!I", payload[:4])[0])
elif ftype in (HEADERS, CONTINUATION) and sid == 1:
p = payload
if ftype == HEADERS:
if flags & FLAG_PADDED:
p = p[1:len(p) - bytearray(payload)[0]]
if flags & FLAG_PRIORITY:
p = p[5:]
header_block += p
if flags & FLAG_END_HEADERS:
resp_headers = dec.decode(header_block)
if flags & FLAG_END_STREAM:
done = True
elif ftype == DATA and sid == 1:
p = payload
if flags & FLAG_PADDED:
p = p[1:len(p) - bytearray(payload)[0]]
resp_body += p
if payload: # replenish stream + connection windows
sock.sendall(encode_frame(WINDOW_UPDATE, 0, 1, struct.pack("!I", len(payload))))
sock.sendall(encode_frame(WINDOW_UPDATE, 0, 0, struct.pack("!I", len(payload))))
if flags & FLAG_END_STREAM:
done = True
status = None
for n, v in (resp_headers or []):
if _tob(n) == b":status":
status = int(v)
break
return status, resp_headers, bytes(resp_body)
finally:
try: sock.close()
except Exception: pass
class H2Response(object):
"""A urllib-response-compatible wrapper around a native HTTP/2 response, so the rest of sqlmap's
request pipeline can consume it exactly like a urllib response (code/msg/info()/read()/geturl())."""
def __init__(self, url, status, headers, body):
self.url = url
self.code = self.status = status
self.msg = _HTTP_RESPONSES.get(status, "")
self.http_version = "HTTP/2.0"
self._body = body
self._offset = 0
self._info = _Message()
for name, value in (headers or []):
name = name.decode("latin-1") if isinstance(name, bytes) else name
value = value.decode("latin-1") if isinstance(value, bytes) else value
if not name.startswith(":"): # drop HTTP/2 pseudo-headers (:status etc.)
self._info[name] = value
# expose a mimetools.Message-style '.headers' list so patchHeaders() treats this object
# uniformly across Python 2/3 (email.message.Message lacks it, and Python 2 iteration over a
# bare Message falls back to integer indexing)
self._info.headers = ["%s: %s\r\n" % (name, value) for (name, value) in self._info.items()]
def info(self):
return self._info
def geturl(self):
return self.url
def read(self, amt=None):
if amt is None:
data = self._body[self._offset:]
self._offset = len(self._body)
else:
data = self._body[self._offset:self._offset + amt]
self._offset += len(data)
return data
def close(self):
pass
def open_url(url, method="GET", headers=None, body=None, timeout=30, follow_redirects=True, max_redirects=10, proxy=None):
"""Fetch url over native HTTP/2 (https only), following redirects like a browser (mirroring the
previous httpx follow_redirects=True), and return an H2Response. Raises IOError on a transport or
ALPN-negotiation failure. Connection-level and h2-forbidden request headers are stripped."""
forbidden = ("host", "connection", "keep-alive", "proxy-connection", "transfer-encoding", "upgrade", "content-length")
req_headers = {}
for key in (headers or {}):
name = key.decode("latin-1") if isinstance(key, bytes) else key
if name.lower() not in forbidden:
req_headers[key] = headers[key]
for _ in range(max_redirects + 1):
parts = urlsplit(url)
if parts.scheme != "https":
raise IOError("native HTTP/2 client supports 'https://' targets only (got %r)" % parts.scheme)
path = parts.path or "/"
if parts.query:
path += "?" + parts.query
status, resp_headers, resp_body = h2_request(parts.hostname, parts.port or 443, method=method, path=path,
authority=parts.netloc.split("@")[-1], headers=req_headers, body=body, timeout=timeout, proxy=proxy)
if follow_redirects and status in REDIRECT_CODES:
location = None
for name, value in (resp_headers or []):
if (name.decode("latin-1") if isinstance(name, bytes) else name).lower() == "location":
location = value.decode("latin-1") if isinstance(value, bytes) else value
break
if location:
url = urljoin(url, location)
if status in (301, 302, 303): # per RFC 7231, these degrade to GET
method, body = "GET", None
continue
return H2Response(url, status, resp_headers, resp_body)
raise IOError("too many HTTP/2 redirects")

View file

@ -94,16 +94,6 @@ def checkDependencies():
logger.warning(warnMsg)
missing_libraries.add('python-ntlm')
try:
__import__("httpx")
debugMsg = "'httpx[http2]' third-party library is found"
logger.debug(debugMsg)
except ImportError:
warnMsg = "sqlmap requires 'httpx[http2]' third-party library "
warnMsg += "if you plan to use HTTP version 2"
logger.warning(warnMsg)
missing_libraries.add('httpx[http2]')
try:
__import__("websocket._abnf")
debugMsg = "'websocket-client' library is found"