mirror of
https://github.com/sqlmapproject/sqlmap.git
synced 2026-06-27 20:11:02 +00:00
307 lines
10 KiB
Python
307 lines
10 KiB
Python
#!/usr/bin/env python
|
|
|
|
"""
|
|
Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org)
|
|
See the file 'LICENSE' for copying permission
|
|
"""
|
|
|
|
import socket
|
|
import threading
|
|
import time
|
|
|
|
from lib.core.data import conf
|
|
from lib.core.settings import KEEPALIVE_IDLE_TIMEOUT
|
|
from lib.core.settings import KEEPALIVE_MAX_REQUESTS
|
|
from thirdparty.six.moves import http_client as _http_client
|
|
from thirdparty.six.moves import urllib as _urllib
|
|
|
|
# Note: prior to Python 2.4 it was the HTTP handler's job to decide what to handle
|
|
# specially; since 2.4 that belongs to HTTPErrorProcessor, hence everything is passed up
|
|
HANDLE_ERRORS = 0
|
|
|
|
class _ConnectionPool(threading.local):
|
|
"""
|
|
Per-thread pool of reusable persistent connections.
|
|
|
|
Keeping one connection per (scheme, host) and per worker thread is what
|
|
keeps Keep-Alive safe under '--threads': a socket is never shared between
|
|
threads, so concurrent requests can never interleave on the same wire (the
|
|
classic cause of response desynchronization). Synchronous reuse within a
|
|
single thread is fine because the previous response is always fully drained
|
|
before the next request is issued (see L{_KeepAliveResponseMixin}).
|
|
"""
|
|
|
|
def __init__(self):
|
|
self.conns = {} # key -> [connection, request_count, last_used]
|
|
|
|
class _KeepAliveHandler(object):
|
|
def __init__(self):
|
|
self._pool = _ConnectionPool()
|
|
|
|
def _take(self, key):
|
|
"""
|
|
Returns a (still usable) pooled connection for L{key} or None
|
|
"""
|
|
|
|
entry = self._pool.conns.pop(key, None)
|
|
|
|
if entry is not None:
|
|
conn, count, last = entry
|
|
if (time.time() - last) <= KEEPALIVE_IDLE_TIMEOUT and count < KEEPALIVE_MAX_REQUESTS:
|
|
return conn, count
|
|
|
|
# Too old or too heavily used; drop it
|
|
try:
|
|
conn.close()
|
|
except Exception:
|
|
pass
|
|
|
|
return None, 0
|
|
|
|
def _give_back(self, key, conn, count):
|
|
self._pool.conns[key] = [conn, count, time.time()]
|
|
|
|
def do_open(self, req):
|
|
# Note: 'selector'/'host' attributes on Python 3 (Request.get_host() was deprecated since
|
|
# 3.3 and removed in 3.12); the get_*() fallbacks are only reachable under Python 2
|
|
host = req.host if hasattr(req, "host") else req.get_host()
|
|
|
|
if not host:
|
|
raise _urllib.error.URLError("no host given")
|
|
|
|
key = "%s://%s" % (self._scheme, host)
|
|
|
|
conn, count = self._take(key)
|
|
reused = conn is not None
|
|
|
|
try:
|
|
if reused:
|
|
# A pooled socket may have been closed by the server in the
|
|
# meantime; treat any failure (or a bogus HTTP/0.9 reply, which
|
|
# is httplib's tell-tale for a dead socket) as a stale connection
|
|
try:
|
|
self._send_request(conn, req)
|
|
response = conn.getresponse()
|
|
if response is None or getattr(response, "version", 0) == 9:
|
|
raise _http_client.HTTPException("stale connection")
|
|
except (socket.error, _http_client.HTTPException):
|
|
try:
|
|
conn.close()
|
|
except Exception:
|
|
pass
|
|
conn = None
|
|
reused = False
|
|
|
|
if conn is None:
|
|
conn = self._get_connection(host)
|
|
count = 0
|
|
self._send_request(conn, req)
|
|
response = conn.getresponse()
|
|
except (socket.error, _http_client.HTTPException) as ex:
|
|
raise _urllib.error.URLError(ex)
|
|
|
|
count += 1
|
|
|
|
# Honor an explicit 'Connection: close' even when L{will_close} wasn't set
|
|
willClose = response.will_close
|
|
if not willClose:
|
|
try:
|
|
headers = getattr(response, "msg", None) or getattr(response, "headers", None)
|
|
value = headers.get("connection") or headers.get("Connection") if headers else None
|
|
if value and "close" in value.lower():
|
|
willClose = True
|
|
except Exception:
|
|
pass
|
|
|
|
keep = not willClose and count < KEEPALIVE_MAX_REQUESTS
|
|
|
|
self._adapt(response, req.get_full_url())
|
|
self._instrument(response, key, conn, count, keep)
|
|
|
|
if response.status == 200 or not HANDLE_ERRORS:
|
|
return response
|
|
else:
|
|
return self.parent.error("http", req, response, response.status, response.reason, response.headers)
|
|
|
|
def _adapt(self, response, url):
|
|
"""
|
|
Makes a raw httplib response indistinguishable from the object normally
|
|
returned by C{urlopen} (the surface the rest of sqlmap relies on)
|
|
"""
|
|
|
|
headers = getattr(response, "headers", None)
|
|
if headers is None:
|
|
headers = response.msg # Python 2: msg holds the parsed headers
|
|
|
|
response.url = url
|
|
response.code = response.status
|
|
response.headers = headers
|
|
|
|
if not hasattr(response, "info"):
|
|
response.info = lambda headers=headers: headers
|
|
if not hasattr(response, "geturl"):
|
|
response.geturl = lambda url=url: url
|
|
if not hasattr(response, "getcode"):
|
|
response.getcode = lambda response=response: response.status
|
|
|
|
# Note: Python 2's httplib.HTTPResponse lacks readline()/readlines(), which urllib2's
|
|
# error wrapping (addinfourl, for any non-2xx response) requires; provide them over read()
|
|
if not hasattr(response, "readline"):
|
|
response.readline = _makeReadline(response)
|
|
response.readlines = _makeReadlines(response)
|
|
|
|
# Note: must come last as on Python 3 'msg' initially aliases the headers
|
|
response.msg = response.reason
|
|
|
|
def _instrument(self, response, key, conn, count, keep):
|
|
"""
|
|
Returns the connection to the pool once (and only once) its body has been
|
|
fully consumed; otherwise the socket is closed. A partially read response
|
|
(e.g. sqlmap hitting a size cap) leaves unread bytes on the wire, so such
|
|
a connection is never reused.
|
|
"""
|
|
|
|
state = {"handled": False}
|
|
_read = response.read
|
|
_close = response.close
|
|
|
|
def drained():
|
|
checker = getattr(response, "isclosed", None)
|
|
if callable(checker):
|
|
try:
|
|
return checker()
|
|
except Exception:
|
|
return False
|
|
return getattr(response, "fp", None) is None
|
|
|
|
def settle():
|
|
# Once (and only once) the body is fully drained, decide the socket's fate
|
|
if state["handled"] or not drained():
|
|
return
|
|
state["handled"] = True
|
|
if keep:
|
|
self._give_back(key, conn, count)
|
|
else:
|
|
try:
|
|
conn.close()
|
|
except Exception:
|
|
pass
|
|
|
|
def read(*args, **kwargs):
|
|
data = _read(*args, **kwargs)
|
|
settle()
|
|
return data
|
|
|
|
def close():
|
|
# Note: on Python 2 httplib.read() calls close() itself upon EOF
|
|
_close()
|
|
settle()
|
|
if not state["handled"]:
|
|
# Closed before the body was fully consumed; unsafe to reuse
|
|
state["handled"] = True
|
|
try:
|
|
conn.close()
|
|
except Exception:
|
|
pass
|
|
|
|
response.read = read
|
|
response.close = close
|
|
|
|
class HTTPKeepAliveHandler(_KeepAliveHandler, _urllib.request.HTTPHandler):
|
|
_scheme = "http"
|
|
|
|
def __init__(self):
|
|
_KeepAliveHandler.__init__(self)
|
|
|
|
def http_open(self, req):
|
|
return self.do_open(req)
|
|
|
|
def _get_connection(self, host):
|
|
return _http_client.HTTPConnection(host)
|
|
|
|
def _send_request(self, conn, req):
|
|
_sendRequest(conn, req)
|
|
|
|
class HTTPSKeepAliveHandler(_KeepAliveHandler, _urllib.request.HTTPSHandler):
|
|
_scheme = "https"
|
|
|
|
def __init__(self):
|
|
_KeepAliveHandler.__init__(self)
|
|
|
|
def https_open(self, req):
|
|
return self.do_open(req)
|
|
|
|
def _get_connection(self, host):
|
|
# Note: reuses sqlmap's SSL-negotiating connection (lib/request/httpshandler.py)
|
|
from lib.request.httpshandler import HTTPSConnection
|
|
from lib.request.httpshandler import ssl
|
|
return HTTPSConnection(host) if ssl else _http_client.HTTPSConnection(host)
|
|
|
|
def _send_request(self, conn, req):
|
|
_sendRequest(conn, req)
|
|
|
|
def _sendRequest(conn, req):
|
|
"""
|
|
Issues L{req} on the (possibly reused) low-level connection L{conn}
|
|
"""
|
|
|
|
data = getattr(req, "data", None)
|
|
method = req.get_method() or ("POST" if data is not None else "GET")
|
|
selector = req.selector if hasattr(req, "selector") else req.get_selector()
|
|
|
|
try:
|
|
conn.putrequest(method, selector, skip_host=req.has_header("Host"), skip_accept_encoding=req.has_header("Accept-encoding"))
|
|
|
|
if data is not None:
|
|
if not req.has_header("Content-type"):
|
|
conn.putheader("Content-type", "application/x-www-form-urlencoded")
|
|
if not req.has_header("Content-length"):
|
|
conn.putheader("Content-length", "%d" % len(data))
|
|
except (socket.error, _http_client.HTTPException) as ex:
|
|
raise _urllib.error.URLError(ex)
|
|
|
|
if not req.has_header("Connection"):
|
|
conn.putheader("Connection", "keep-alive")
|
|
|
|
for key, value in req.header_items():
|
|
conn.putheader(key, value)
|
|
|
|
conn.endheaders()
|
|
|
|
if data is not None:
|
|
conn.send(data)
|
|
|
|
def _makeReadline(response):
|
|
"""
|
|
A buffered readline() over response.read() (Python 2 httplib.HTTPResponse lacks one)
|
|
"""
|
|
|
|
buffer = {"data": b""}
|
|
|
|
def readline(*args, **kwargs):
|
|
while b"\n" not in buffer["data"]:
|
|
chunk = response.read(8192)
|
|
if not chunk:
|
|
break
|
|
buffer["data"] += chunk
|
|
data = buffer["data"]
|
|
index = data.find(b"\n")
|
|
if index == -1:
|
|
buffer["data"] = b""
|
|
return data
|
|
buffer["data"] = data[index + 1:]
|
|
return data[:index + 1]
|
|
|
|
return readline
|
|
|
|
def _makeReadlines(response):
|
|
def readlines(*args, **kwargs):
|
|
result = []
|
|
while True:
|
|
line = response.readline()
|
|
if not line:
|
|
break
|
|
result.append(line)
|
|
return result
|
|
|
|
return readlines
|