diff --git a/data/shell/README.txt b/data/shell/README.txt
index 77b1c57ee..4c64c4116 100644
--- a/data/shell/README.txt
+++ b/data/shell/README.txt
@@ -1,7 +1,7 @@
-Due to the anti-virus positive detection of shell scripts stored inside this folder, we needed to somehow circumvent this. As from the plain sqlmap users perspective nothing has to be done prior to their usage by sqlmap, but if you want to have access to their original source code use the decrypt functionality of the ../extra/cloak/cloak.py utility.
+Due to the anti-virus positive detection of shell scripts stored inside this folder, we needed to somehow circumvent this. As from the plain sqlmap users perspective nothing has to be done prior to their usage by sqlmap, but if you want to have access to their original source code use the decrypt functionality of the ../../extra/cloak/cloak.py utility.
To prepare the original scripts to the cloaked form use this command:
-find backdoors/backdoor.* stagers/stager.* -type f -exec python ../extra/cloak/cloak.py -i '{}' \;
+find backdoors/backdoor.* stagers/stager.* -type f -exec python ../../extra/cloak/cloak.py -i '{}' \;
To get back them into the original form use this:
-find backdoors/backdoor.*_ stagers/stager.*_ -type f -exec python ../extra/cloak/cloak.py -d -i '{}' \;
+find backdoors/backdoor.*_ stagers/stager.*_ -type f -exec python ../../extra/cloak/cloak.py -d -i '{}' \;
diff --git a/data/shell/backdoors/backdoor.asp_ b/data/shell/backdoors/backdoor.asp_
index ebcf6bf57..6480be499 100644
Binary files a/data/shell/backdoors/backdoor.asp_ and b/data/shell/backdoors/backdoor.asp_ differ
diff --git a/data/shell/backdoors/backdoor.aspx_ b/data/shell/backdoors/backdoor.aspx_
index fa865e71d..aeb326465 100644
Binary files a/data/shell/backdoors/backdoor.aspx_ and b/data/shell/backdoors/backdoor.aspx_ differ
diff --git a/data/shell/backdoors/backdoor.jsp_ b/data/shell/backdoors/backdoor.jsp_
index 103b166ea..0702a8098 100644
Binary files a/data/shell/backdoors/backdoor.jsp_ and b/data/shell/backdoors/backdoor.jsp_ differ
diff --git a/data/shell/backdoors/backdoor.php_ b/data/shell/backdoors/backdoor.php_
index ee2aa1c28..6c4b4d990 100644
Binary files a/data/shell/backdoors/backdoor.php_ and b/data/shell/backdoors/backdoor.php_ differ
diff --git a/data/shell/stagers/stager.asp_ b/data/shell/stagers/stager.asp_
index b9dd8e714..556085c2b 100644
Binary files a/data/shell/stagers/stager.asp_ and b/data/shell/stagers/stager.asp_ differ
diff --git a/data/shell/stagers/stager.aspx_ b/data/shell/stagers/stager.aspx_
index efd39317c..b2aad5f87 100644
Binary files a/data/shell/stagers/stager.aspx_ and b/data/shell/stagers/stager.aspx_ differ
diff --git a/data/shell/stagers/stager.jsp_ b/data/shell/stagers/stager.jsp_
index 2abd02252..d2fc99e9c 100644
Binary files a/data/shell/stagers/stager.jsp_ and b/data/shell/stagers/stager.jsp_ differ
diff --git a/data/shell/stagers/stager.php_ b/data/shell/stagers/stager.php_
index 1c974369a..8093fad03 100644
Binary files a/data/shell/stagers/stager.php_ and b/data/shell/stagers/stager.php_ differ
diff --git a/data/udf/mysql/linux/32/lib_mysqludf_sys.so_ b/data/udf/mysql/linux/32/lib_mysqludf_sys.so_
index 51af4d2bc..2dd96d96a 100644
Binary files a/data/udf/mysql/linux/32/lib_mysqludf_sys.so_ and b/data/udf/mysql/linux/32/lib_mysqludf_sys.so_ differ
diff --git a/data/udf/mysql/linux/64/lib_mysqludf_sys.so_ b/data/udf/mysql/linux/64/lib_mysqludf_sys.so_
index f39e2c4b0..40932ba44 100644
Binary files a/data/udf/mysql/linux/64/lib_mysqludf_sys.so_ and b/data/udf/mysql/linux/64/lib_mysqludf_sys.so_ differ
diff --git a/data/udf/mysql/windows/32/lib_mysqludf_sys.dll_ b/data/udf/mysql/windows/32/lib_mysqludf_sys.dll_
index a706d35cc..2844b9956 100644
Binary files a/data/udf/mysql/windows/32/lib_mysqludf_sys.dll_ and b/data/udf/mysql/windows/32/lib_mysqludf_sys.dll_ differ
diff --git a/data/udf/mysql/windows/64/lib_mysqludf_sys.dll_ b/data/udf/mysql/windows/64/lib_mysqludf_sys.dll_
index fbcdff625..95046969b 100644
Binary files a/data/udf/mysql/windows/64/lib_mysqludf_sys.dll_ and b/data/udf/mysql/windows/64/lib_mysqludf_sys.dll_ differ
diff --git a/data/udf/postgresql/linux/32/10/lib_postgresqludf_sys.so_ b/data/udf/postgresql/linux/32/10/lib_postgresqludf_sys.so_
index 36d507d0a..571f3a4b7 100644
Binary files a/data/udf/postgresql/linux/32/10/lib_postgresqludf_sys.so_ and b/data/udf/postgresql/linux/32/10/lib_postgresqludf_sys.so_ differ
diff --git a/data/udf/postgresql/linux/32/11/lib_postgresqludf_sys.so_ b/data/udf/postgresql/linux/32/11/lib_postgresqludf_sys.so_
index 1d4bb5afd..52e70186c 100644
Binary files a/data/udf/postgresql/linux/32/11/lib_postgresqludf_sys.so_ and b/data/udf/postgresql/linux/32/11/lib_postgresqludf_sys.so_ differ
diff --git a/data/udf/postgresql/linux/32/8.2/lib_postgresqludf_sys.so_ b/data/udf/postgresql/linux/32/8.2/lib_postgresqludf_sys.so_
index 11f041e1d..c35f6d034 100644
Binary files a/data/udf/postgresql/linux/32/8.2/lib_postgresqludf_sys.so_ and b/data/udf/postgresql/linux/32/8.2/lib_postgresqludf_sys.so_ differ
diff --git a/data/udf/postgresql/linux/32/8.3/lib_postgresqludf_sys.so_ b/data/udf/postgresql/linux/32/8.3/lib_postgresqludf_sys.so_
index a3d0e74ca..337962be6 100644
Binary files a/data/udf/postgresql/linux/32/8.3/lib_postgresqludf_sys.so_ and b/data/udf/postgresql/linux/32/8.3/lib_postgresqludf_sys.so_ differ
diff --git a/data/udf/postgresql/linux/32/8.4/lib_postgresqludf_sys.so_ b/data/udf/postgresql/linux/32/8.4/lib_postgresqludf_sys.so_
index f088490bd..2d4082395 100644
Binary files a/data/udf/postgresql/linux/32/8.4/lib_postgresqludf_sys.so_ and b/data/udf/postgresql/linux/32/8.4/lib_postgresqludf_sys.so_ differ
diff --git a/data/udf/postgresql/linux/32/9.0/lib_postgresqludf_sys.so_ b/data/udf/postgresql/linux/32/9.0/lib_postgresqludf_sys.so_
index 98cf537dd..870577473 100644
Binary files a/data/udf/postgresql/linux/32/9.0/lib_postgresqludf_sys.so_ and b/data/udf/postgresql/linux/32/9.0/lib_postgresqludf_sys.so_ differ
diff --git a/data/udf/postgresql/linux/32/9.1/lib_postgresqludf_sys.so_ b/data/udf/postgresql/linux/32/9.1/lib_postgresqludf_sys.so_
index 8182362e8..fca855a84 100644
Binary files a/data/udf/postgresql/linux/32/9.1/lib_postgresqludf_sys.so_ and b/data/udf/postgresql/linux/32/9.1/lib_postgresqludf_sys.so_ differ
diff --git a/data/udf/postgresql/linux/32/9.2/lib_postgresqludf_sys.so_ b/data/udf/postgresql/linux/32/9.2/lib_postgresqludf_sys.so_
index da6ffc73e..7a99f92f5 100644
Binary files a/data/udf/postgresql/linux/32/9.2/lib_postgresqludf_sys.so_ and b/data/udf/postgresql/linux/32/9.2/lib_postgresqludf_sys.so_ differ
diff --git a/data/udf/postgresql/linux/32/9.3/lib_postgresqludf_sys.so_ b/data/udf/postgresql/linux/32/9.3/lib_postgresqludf_sys.so_
index 1d640b790..d204b81d2 100644
Binary files a/data/udf/postgresql/linux/32/9.3/lib_postgresqludf_sys.so_ and b/data/udf/postgresql/linux/32/9.3/lib_postgresqludf_sys.so_ differ
diff --git a/data/udf/postgresql/linux/32/9.4/lib_postgresqludf_sys.so_ b/data/udf/postgresql/linux/32/9.4/lib_postgresqludf_sys.so_
index ce6b70528..c4ac46ae8 100644
Binary files a/data/udf/postgresql/linux/32/9.4/lib_postgresqludf_sys.so_ and b/data/udf/postgresql/linux/32/9.4/lib_postgresqludf_sys.so_ differ
diff --git a/data/udf/postgresql/linux/32/9.5/lib_postgresqludf_sys.so_ b/data/udf/postgresql/linux/32/9.5/lib_postgresqludf_sys.so_
index f0d339c25..51e17d30f 100644
Binary files a/data/udf/postgresql/linux/32/9.5/lib_postgresqludf_sys.so_ and b/data/udf/postgresql/linux/32/9.5/lib_postgresqludf_sys.so_ differ
diff --git a/data/udf/postgresql/linux/32/9.6/lib_postgresqludf_sys.so_ b/data/udf/postgresql/linux/32/9.6/lib_postgresqludf_sys.so_
index 9d4ac61a4..103d3d8c0 100644
Binary files a/data/udf/postgresql/linux/32/9.6/lib_postgresqludf_sys.so_ and b/data/udf/postgresql/linux/32/9.6/lib_postgresqludf_sys.so_ differ
diff --git a/data/udf/postgresql/linux/64/10/lib_postgresqludf_sys.so_ b/data/udf/postgresql/linux/64/10/lib_postgresqludf_sys.so_
index e68237f32..d70067011 100644
Binary files a/data/udf/postgresql/linux/64/10/lib_postgresqludf_sys.so_ and b/data/udf/postgresql/linux/64/10/lib_postgresqludf_sys.so_ differ
diff --git a/data/udf/postgresql/linux/64/11/lib_postgresqludf_sys.so_ b/data/udf/postgresql/linux/64/11/lib_postgresqludf_sys.so_
index 772f4d684..7d8dce0e0 100644
Binary files a/data/udf/postgresql/linux/64/11/lib_postgresqludf_sys.so_ and b/data/udf/postgresql/linux/64/11/lib_postgresqludf_sys.so_ differ
diff --git a/data/udf/postgresql/linux/64/8.2/lib_postgresqludf_sys.so_ b/data/udf/postgresql/linux/64/8.2/lib_postgresqludf_sys.so_
index 63f387333..9e8d56894 100644
Binary files a/data/udf/postgresql/linux/64/8.2/lib_postgresqludf_sys.so_ and b/data/udf/postgresql/linux/64/8.2/lib_postgresqludf_sys.so_ differ
diff --git a/data/udf/postgresql/linux/64/8.3/lib_postgresqludf_sys.so_ b/data/udf/postgresql/linux/64/8.3/lib_postgresqludf_sys.so_
index 00b0c7051..c66c38c62 100644
Binary files a/data/udf/postgresql/linux/64/8.3/lib_postgresqludf_sys.so_ and b/data/udf/postgresql/linux/64/8.3/lib_postgresqludf_sys.so_ differ
diff --git a/data/udf/postgresql/linux/64/8.4/lib_postgresqludf_sys.so_ b/data/udf/postgresql/linux/64/8.4/lib_postgresqludf_sys.so_
index 5225ac925..d3c9b21ba 100644
Binary files a/data/udf/postgresql/linux/64/8.4/lib_postgresqludf_sys.so_ and b/data/udf/postgresql/linux/64/8.4/lib_postgresqludf_sys.so_ differ
diff --git a/data/udf/postgresql/linux/64/9.0/lib_postgresqludf_sys.so_ b/data/udf/postgresql/linux/64/9.0/lib_postgresqludf_sys.so_
index e0851b862..1fe3bb87e 100644
Binary files a/data/udf/postgresql/linux/64/9.0/lib_postgresqludf_sys.so_ and b/data/udf/postgresql/linux/64/9.0/lib_postgresqludf_sys.so_ differ
diff --git a/data/udf/postgresql/linux/64/9.1/lib_postgresqludf_sys.so_ b/data/udf/postgresql/linux/64/9.1/lib_postgresqludf_sys.so_
index 59ac7d7b8..512223bf0 100644
Binary files a/data/udf/postgresql/linux/64/9.1/lib_postgresqludf_sys.so_ and b/data/udf/postgresql/linux/64/9.1/lib_postgresqludf_sys.so_ differ
diff --git a/data/udf/postgresql/linux/64/9.2/lib_postgresqludf_sys.so_ b/data/udf/postgresql/linux/64/9.2/lib_postgresqludf_sys.so_
index 16e4dc49d..740efe933 100644
Binary files a/data/udf/postgresql/linux/64/9.2/lib_postgresqludf_sys.so_ and b/data/udf/postgresql/linux/64/9.2/lib_postgresqludf_sys.so_ differ
diff --git a/data/udf/postgresql/linux/64/9.3/lib_postgresqludf_sys.so_ b/data/udf/postgresql/linux/64/9.3/lib_postgresqludf_sys.so_
index a915f601a..5a62d9a18 100644
Binary files a/data/udf/postgresql/linux/64/9.3/lib_postgresqludf_sys.so_ and b/data/udf/postgresql/linux/64/9.3/lib_postgresqludf_sys.so_ differ
diff --git a/data/udf/postgresql/linux/64/9.4/lib_postgresqludf_sys.so_ b/data/udf/postgresql/linux/64/9.4/lib_postgresqludf_sys.so_
index b147e94ea..73eeb439a 100644
Binary files a/data/udf/postgresql/linux/64/9.4/lib_postgresqludf_sys.so_ and b/data/udf/postgresql/linux/64/9.4/lib_postgresqludf_sys.so_ differ
diff --git a/data/udf/postgresql/linux/64/9.5/lib_postgresqludf_sys.so_ b/data/udf/postgresql/linux/64/9.5/lib_postgresqludf_sys.so_
index 44a646325..b98868f67 100644
Binary files a/data/udf/postgresql/linux/64/9.5/lib_postgresqludf_sys.so_ and b/data/udf/postgresql/linux/64/9.5/lib_postgresqludf_sys.so_ differ
diff --git a/data/udf/postgresql/linux/64/9.6/lib_postgresqludf_sys.so_ b/data/udf/postgresql/linux/64/9.6/lib_postgresqludf_sys.so_
index 96f088a78..0596e9473 100644
Binary files a/data/udf/postgresql/linux/64/9.6/lib_postgresqludf_sys.so_ and b/data/udf/postgresql/linux/64/9.6/lib_postgresqludf_sys.so_ differ
diff --git a/data/udf/postgresql/windows/32/8.2/lib_postgresqludf_sys.dll_ b/data/udf/postgresql/windows/32/8.2/lib_postgresqludf_sys.dll_
index ae8245172..17d1140c1 100644
Binary files a/data/udf/postgresql/windows/32/8.2/lib_postgresqludf_sys.dll_ and b/data/udf/postgresql/windows/32/8.2/lib_postgresqludf_sys.dll_ differ
diff --git a/data/udf/postgresql/windows/32/8.3/lib_postgresqludf_sys.dll_ b/data/udf/postgresql/windows/32/8.3/lib_postgresqludf_sys.dll_
index 1afa35610..d448b184e 100644
Binary files a/data/udf/postgresql/windows/32/8.3/lib_postgresqludf_sys.dll_ and b/data/udf/postgresql/windows/32/8.3/lib_postgresqludf_sys.dll_ differ
diff --git a/data/udf/postgresql/windows/32/8.4/lib_postgresqludf_sys.dll_ b/data/udf/postgresql/windows/32/8.4/lib_postgresqludf_sys.dll_
index f91123682..22524cb3b 100644
Binary files a/data/udf/postgresql/windows/32/8.4/lib_postgresqludf_sys.dll_ and b/data/udf/postgresql/windows/32/8.4/lib_postgresqludf_sys.dll_ differ
diff --git a/data/udf/postgresql/windows/32/9.0/lib_postgresqludf_sys.dll_ b/data/udf/postgresql/windows/32/9.0/lib_postgresqludf_sys.dll_
index d072e4116..0d407bc3f 100644
Binary files a/data/udf/postgresql/windows/32/9.0/lib_postgresqludf_sys.dll_ and b/data/udf/postgresql/windows/32/9.0/lib_postgresqludf_sys.dll_ differ
diff --git a/data/xml/errors.xml b/data/xml/errors.xml
index e7aa190b1..568b61bbe 100644
--- a/data/xml/errors.xml
+++ b/data/xml/errors.xml
@@ -83,7 +83,7 @@
-
+
diff --git a/data/xml/queries.xml b/data/xml/queries.xml
index 25ddbd261..33267e870 100644
--- a/data/xml/queries.xml
+++ b/data/xml/queries.xml
@@ -301,8 +301,8 @@
-
-
+
+
diff --git a/doc/THIRD-PARTY.md b/doc/THIRD-PARTY.md
index eca318269..04d558f61 100644
--- a/doc/THIRD-PARTY.md
+++ b/doc/THIRD-PARTY.md
@@ -277,7 +277,7 @@ be bound by the terms and conditions of this License Agreement.
* The `bottle` web framework library located under `thirdparty/bottle/`.
Copyright (C) 2012, Marcel Hellkamp.
* The `identYwaf` library located under `thirdparty/identywaf/`.
- Copyright (C) 2019, Miroslav Stampar.
+ Copyright (C) 2019-2020, Miroslav Stampar.
* The `ordereddict` library located under `thirdparty/odict/`.
Copyright (C) 2009, Raymond Hettinger.
* The `six` Python 2 and 3 compatibility library located under `thirdparty/six/`.
diff --git a/doc/translations/README-fr-FR.md b/doc/translations/README-fr-FR.md
index 83c4884b6..8c87faf54 100644
--- a/doc/translations/README-fr-FR.md
+++ b/doc/translations/README-fr-FR.md
@@ -32,7 +32,7 @@ Pour afficher une liste complète des options et des commutateurs (switches), ta
python sqlmap.py -hh
-Vous pouvez regarder un vidéo [ici](https://asciinema.org/a/46601) pour plus d'exemples.
+Vous pouvez regarder une vidéo [ici](https://asciinema.org/a/46601) pour plus d'exemples.
Pour obtenir un aperçu des ressources de __sqlmap__, une liste des fonctionnalités prises en charge, la description de toutes les options, ainsi que des exemples, nous vous recommandons de consulter [le wiki](https://github.com/sqlmapproject/sqlmap/wiki/Usage).
Liens
diff --git a/doc/translations/README-id-ID.md b/doc/translations/README-id-ID.md
index 6029b3630..bd2ffd092 100644
--- a/doc/translations/README-id-ID.md
+++ b/doc/translations/README-id-ID.md
@@ -43,7 +43,7 @@ Tautan
* Situs: http://sqlmap.org
* Unduh: [.tar.gz](https://github.com/sqlmapproject/sqlmap/tarball/master) atau [.zip](https://github.com/sqlmapproject/sqlmap/zipball/master)
* RSS feed dari commits: https://github.com/sqlmapproject/sqlmap/commits/master.atom
-* Issue tracker: https://github.com/sqlmapproject/sqlmap/issues
+* Pelacak Masalah: https://github.com/sqlmapproject/sqlmap/issues
* Wiki Manual Penggunaan: https://github.com/sqlmapproject/sqlmap/wiki
* Pertanyaan yang Sering Ditanyakan (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ
* Twitter: [@sqlmap](https://twitter.com/sqlmap)
diff --git a/extra/cloak/cloak.py b/extra/cloak/cloak.py
index bdfd7b09a..3576b6c99 100644
--- a/extra/cloak/cloak.py
+++ b/extra/cloak/cloak.py
@@ -21,7 +21,7 @@ if sys.version_info >= (3, 0):
xrange = range
ord = lambda _: _
-KEY = b"Beeth7hoyooleeF0"
+KEY = b"MOZFqVjlk1CY436G"
def xor(message, key):
return b"".join(struct.pack('B', ord(message[i]) ^ ord(key[i % len(key)])) for i in range(len(message)))
diff --git a/extra/icmpsh/icmpsh.exe_ b/extra/icmpsh/icmpsh.exe_
index 9ce69eb5e..cf0bc7095 100644
Binary files a/extra/icmpsh/icmpsh.exe_ and b/extra/icmpsh/icmpsh.exe_ differ
diff --git a/extra/runcmd/runcmd.exe_ b/extra/runcmd/runcmd.exe_
index 5bec2c1c9..0ff20d9a1 100644
Binary files a/extra/runcmd/runcmd.exe_ and b/extra/runcmd/runcmd.exe_ differ
diff --git a/extra/shellcodeexec/windows/shellcodeexec.x32.exe_ b/extra/shellcodeexec/windows/shellcodeexec.x32.exe_
index 3f8d5a5b3..910768588 100644
Binary files a/extra/shellcodeexec/windows/shellcodeexec.x32.exe_ and b/extra/shellcodeexec/windows/shellcodeexec.x32.exe_ differ
diff --git a/extra/shutils/drei.sh b/extra/shutils/drei.sh
index f73027a30..24875d331 100755
--- a/extra/shutils/drei.sh
+++ b/extra/shutils/drei.sh
@@ -7,7 +7,7 @@
export SQLMAP_DREI=1
#for i in $(find . -iname "*.py" | grep -v __init__); do python3 -c 'import '`echo $i | cut -d '.' -f 2 | cut -d '/' -f 2- | sed 's/\//./g'`''; done
-for i in $(find . -iname "*.py" | grep -v __init__); do PYTHONWARNINGS=all python3.7 -m compileall $i | sed 's/Compiling/Checking/g'; done
+for i in $(find . -iname "*.py" | grep -v __init__); do PYTHONWARNINGS=all python3 -m compileall $i | sed 's/Compiling/Checking/g'; done
unset SQLMAP_DREI
source `dirname "$0"`"/junk.sh"
diff --git a/extra/shutils/recloak.sh b/extra/shutils/recloak.sh
new file mode 100755
index 000000000..557ea51d9
--- /dev/null
+++ b/extra/shutils/recloak.sh
@@ -0,0 +1,16 @@
+#!/bin/bash
+
+# NOTE: this script is for dev usage after AV something something
+
+DIR=$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)
+
+cd $DIR/../..
+for file in $(find -regex ".*\.[a-z]*_" -type f | grep -v wordlist); do python extra/cloak/cloak.py -d -i $file; done
+
+cd $DIR/../cloak
+sed -i 's/KEY = .*/KEY = b"'`python -c 'import random; import string; print("".join(random.sample(string.ascii_letters + string.digits, 16)))'`'"/g' cloak.py
+
+cd $DIR/../..
+for file in $(find -regex ".*\.[a-z]*_" -type f | grep -v wordlist); do python extra/cloak/cloak.py -i `echo $file | sed 's/_$//g'`; done
+
+git clean -f > /dev/null
diff --git a/extra/vulnserver/vulnserver.py b/extra/vulnserver/vulnserver.py
index 3106eafeb..5f33ee975 100644
--- a/extra/vulnserver/vulnserver.py
+++ b/extra/vulnserver/vulnserver.py
@@ -9,6 +9,7 @@ See the file 'LICENSE' for copying permission
from __future__ import print_function
+import base64
import json
import re
import sqlite3
@@ -146,7 +147,10 @@ class ReqHandler(BaseHTTPRequestHandler):
if "query" in self.params:
_cursor.execute(self.params["query"])
elif "id" in self.params:
- _cursor.execute("SELECT * FROM users WHERE id=%s LIMIT 0, 1" % self.params["id"])
+ if "base64" in self.params:
+ _cursor.execute("SELECT * FROM users WHERE id=%s LIMIT 0, 1" % base64.b64decode("%s===" % self.params["id"], altchars=self.params.get("altchars")).decode())
+ else:
+ _cursor.execute("SELECT * FROM users WHERE id=%s LIMIT 0, 1" % self.params["id"])
results = _cursor.fetchall()
output += "SQL results:
\n"
diff --git a/lib/controller/checks.py b/lib/controller/checks.py
index 919859fed..24d8d70d1 100644
--- a/lib/controller/checks.py
+++ b/lib/controller/checks.py
@@ -1581,7 +1581,7 @@ def checkConnection(suppressOutput=False):
kb.originalPage = kb.pageTemplate = threadData.lastPage
kb.originalCode = threadData.lastCode
- if conf.cj and not conf.cookie and not conf.dropSetCookie:
+ if conf.cj and not conf.cookie and not any(_[0] == HTTP_HEADER.COOKIE for _ in conf.httpHeaders) and not conf.dropSetCookie:
candidate = DEFAULT_COOKIE_DELIMITER.join("%s=%s" % (_.name, _.value) for _ in conf.cj)
message = "you have not declared cookie(s), while "
diff --git a/lib/core/agent.py b/lib/core/agent.py
index e11afe200..67dd2b505 100644
--- a/lib/core/agent.py
+++ b/lib/core/agent.py
@@ -42,6 +42,7 @@ from lib.core.enums import PAYLOAD
from lib.core.enums import PLACE
from lib.core.enums import POST_HINT
from lib.core.exception import SqlmapNoneDataException
+from lib.core.settings import BOUNDED_BASE64_MARKER
from lib.core.settings import BOUNDARY_BACKSLASH_MARKER
from lib.core.settings import BOUNDED_INJECTION_MARKER
from lib.core.settings import DEFAULT_COOKIE_DELIMITER
@@ -183,7 +184,7 @@ class Agent(object):
newValue = self.adjustLateValues(newValue)
# TODO: support for POST_HINT
- newValue = encodeBase64(newValue, binary=False, encoding=conf.encoding or UNICODE_ENCODING, safe=conf.base64Safe)
+ newValue = "%s%s%s" % (BOUNDED_BASE64_MARKER, newValue, BOUNDED_BASE64_MARKER)
if parameter in kb.base64Originals:
origValue = kb.base64Originals[parameter]
@@ -397,6 +398,10 @@ class Agent(object):
"""
if payload:
+ for match in re.finditer(r"%s(.*?)%s" % (BOUNDED_BASE64_MARKER, BOUNDED_BASE64_MARKER), payload):
+ _ = encodeBase64(match.group(1), binary=False, encoding=conf.encoding or UNICODE_ENCODING, safe=conf.base64Safe)
+ payload = payload.replace(match.group(0), _)
+
payload = payload.replace(SLEEP_TIME_MARKER, str(conf.timeSec))
payload = payload.replace(SINGLE_QUOTE_MARKER, "'")
@@ -1202,12 +1207,15 @@ class Agent(object):
def whereQuery(self, query):
if conf.dumpWhere and query:
- match = re.search(r" (LIMIT|ORDER).+", query, re.I)
- if match:
- suffix = match.group(0)
- prefix = query[:-len(suffix)]
+ if Backend.isDbms(DBMS.ORACLE) and re.search(r"qq ORDER BY \w+\)", query, re.I) is not None:
+ prefix, suffix = re.sub(r"(?i)(qq)( ORDER BY \w+\))", r"\g<1> WHERE %s\g<2>" % conf.dumpWhere, query), ""
else:
- prefix, suffix = query, ""
+ match = re.search(r" (LIMIT|ORDER).+", query, re.I)
+ if match:
+ suffix = match.group(0)
+ prefix = query[:-len(suffix)]
+ else:
+ prefix, suffix = query, ""
if conf.tbl and "%s)" % conf.tbl.upper() in prefix.upper():
prefix = re.sub(r"(?i)%s\)" % re.escape(conf.tbl), "%s WHERE %s)" % (conf.tbl, conf.dumpWhere), prefix)
diff --git a/lib/core/common.py b/lib/core/common.py
index 4e2169bcc..8fe6e0aaa 100644
--- a/lib/core/common.py
+++ b/lib/core/common.py
@@ -632,6 +632,7 @@ def paramToDict(place, parameters=None):
if parameter in (conf.base64Parameter or []):
try:
kb.base64Originals[parameter] = oldValue = value
+ value = urldecode(value, convall=True)
value = decodeBase64(value, binary=False, encoding=conf.encoding or UNICODE_ENCODING)
parameters = re.sub(r"\b%s(\b|\Z)" % re.escape(oldValue), value, parameters)
except:
@@ -1051,6 +1052,16 @@ def dataToDumpFile(dumpFile, data):
raise
def dataToOutFile(filename, data):
+ """
+ Saves data to filename
+
+ >>> pushValue(conf.get("filePath"))
+ >>> conf.filePath = tempfile.gettempdir()
+ >>> "_etc_passwd" in dataToOutFile("/etc/passwd", b":::*")
+ True
+ >>> conf.filePath = popValue()
+ """
+
retVal = None
if data:
@@ -1714,6 +1725,11 @@ def escapeJsonValue(value):
Escapes JSON value (used in payloads)
# Reference: https://stackoverflow.com/a/16652683
+
+ >>> "\\n" in escapeJsonValue("foo\\nbar")
+ False
+ >>> "\\\\t" in escapeJsonValue("foo\\tbar")
+ True
"""
retVal = ""
@@ -1888,6 +1904,12 @@ def getLocalIP():
def getRemoteIP():
"""
Get remote/target IP address
+
+ >>> pushValue(conf.hostname)
+ >>> conf.hostname = "localhost"
+ >>> getRemoteIP() == "127.0.0.1"
+ True
+ >>> conf.hostname = popValue()
"""
retVal = None
@@ -2014,6 +2036,9 @@ def normalizePath(filepath):
def safeFilepathEncode(filepath):
"""
Returns filepath in (ASCII) format acceptable for OS handling (e.g. reading)
+
+ >>> 'sqlmap' in safeFilepathEncode(paths.SQLMAP_HOME_PATH)
+ True
"""
retVal = filepath
@@ -2046,6 +2071,8 @@ def safeStringFormat(format_, params):
>>> safeStringFormat('SELECT foo FROM %s LIMIT %d', ('bar', '1'))
'SELECT foo FROM bar LIMIT 1'
+ >>> safeStringFormat("SELECT foo FROM %s WHERE name LIKE '%susan%' LIMIT %d", ('bar', '1'))
+ "SELECT foo FROM bar WHERE name LIKE '%susan%' LIMIT 1"
"""
if format_.count(PAYLOAD_DELIMITER) == 2:
@@ -2089,7 +2116,10 @@ def safeStringFormat(format_, params):
warnMsg += "Please report by e-mail content \"%r | %r | %r\" to '%s'" % (format_, params, retVal, DEV_EMAIL_ADDRESS)
raise SqlmapValueException(warnMsg)
else:
- retVal = re.sub(r"(\A|[^A-Za-z0-9])(%s)([^A-Za-z0-9]|\Z)", r"\g<1>%s\g<3>" % params[count], retVal, 1)
+ try:
+ retVal = re.sub(r"(\A|[^A-Za-z0-9])(%s)([^A-Za-z0-9]|\Z)", r"\g<1>%s\g<3>" % params[count], retVal, 1)
+ except re.error:
+ retVal = retVal.replace(match.group(0), match.group(0) % params[count], 1)
count += 1
else:
break
@@ -2220,6 +2250,15 @@ def isHexEncodedString(subject):
def isMultiThreadMode():
"""
Checks if running in multi-thread(ing) mode
+
+ >>> isMultiThreadMode()
+ False
+ >>> _ = lambda: time.sleep(0.1)
+ >>> thread = threading.Thread(target=_)
+ >>> thread.daemon = True
+ >>> thread.start()
+ >>> isMultiThreadMode()
+ True
"""
return threading.activeCount() > 1
@@ -2228,6 +2267,9 @@ def isMultiThreadMode():
def getConsoleWidth(default=80):
"""
Returns console width
+
+ >>> any((getConsoleWidth(), True))
+ True
"""
width = None
@@ -2434,6 +2476,9 @@ def initCommonOutputs():
def getFileItems(filename, commentPrefix='#', unicoded=True, lowercase=False, unique=False):
"""
Returns newline delimited items contained inside file
+
+ >>> "SELECT" in getFileItems(paths.SQL_KEYWORDS)
+ True
"""
retVal = list() if not unique else OrderedDict()
@@ -2540,8 +2585,8 @@ def goGoodSamaritan(prevValue, originalCharset):
def getPartRun(alias=True):
"""
- Goes through call stack and finds constructs matching conf.dbmsHandler.*.
- Returns it or its alias used in 'txt/common-outputs.txt'
+ Goes through call stack and finds constructs matching
+ conf.dbmsHandler.*. Returns it or its alias used in 'txt/common-outputs.txt'
"""
retVal = None
@@ -4734,7 +4779,7 @@ def serializeObject(object_):
"""
Serializes given object
- >>> type(serializeObject([1, 2, 3, ('a', 'b')])) == six.binary_type
+ >>> type(serializeObject([1, 2, 3, ('a', 'b')])) == str
True
"""
@@ -4964,6 +5009,14 @@ def decloakToTemp(filename):
>>> openFile(_, "rb", encoding=None).read().startswith(b'<%')
True
>>> os.remove(_)
+ >>> _ = decloakToTemp(os.path.join(paths.SQLMAP_SHELL_PATH, "backdoors", "backdoor.asp_"))
+ >>> openFile(_, "rb", encoding=None).read().startswith(b'<%')
+ True
+ >>> os.remove(_)
+ >>> _ = decloakToTemp(os.path.join(paths.SQLMAP_UDF_PATH, "postgresql", "linux", "64", "11", "lib_postgresqludf_sys.so_"))
+ >>> b'sys_eval' in openFile(_, "rb", encoding=None).read()
+ True
+ >>> os.remove(_)
"""
content = decloak(filename)
@@ -4997,6 +5050,12 @@ def getRequestHeader(request, name):
Solving an issue with an urllib2 Request header case sensitivity
# Reference: http://bugs.python.org/issue2275
+
+ >>> _ = lambda _: _
+ >>> _.headers = {"FOO": "BAR"}
+ >>> _.header_items = lambda: _.headers.items()
+ >>> getText(getRequestHeader(_, "foo"))
+ 'BAR'
"""
retVal = None
@@ -5094,6 +5153,13 @@ def pollProcess(process, suppress_errors=False):
def parseRequestFile(reqFile, checkParams=True):
"""
Parses WebScarab and Burp logs and adds results to the target URL list
+
+ >>> handle, reqFile = tempfile.mkstemp(suffix=".req")
+ >>> content = b"POST / HTTP/1.0\\nUser-agent: foobar\\nHost: www.example.com\\n\\nid=1\\n"
+ >>> _ = os.write(handle, content)
+ >>> os.close(handle)
+ >>> next(parseRequestFile(reqFile)) == ('http://www.example.com:80/', 'POST', 'id=1', None, (('User-agent', 'foobar'), ('Host', 'www.example.com')))
+ True
"""
def _parseWebScarabLog(content):
@@ -5236,7 +5302,7 @@ def parseRequestFile(reqFile, checkParams=True):
params = True
# Avoid proxy and connection type related headers
- elif key not in (HTTP_HEADER.PROXY_CONNECTION, HTTP_HEADER.CONNECTION):
+ elif key not in (HTTP_HEADER.PROXY_CONNECTION, HTTP_HEADER.CONNECTION, HTTP_HEADER.IF_MODIFIED_SINCE, HTTP_HEADER.IF_NONE_MATCH):
headers.append((getUnicode(key), getUnicode(value)))
if kb.customInjectionMark in re.sub(PROBLEMATIC_CUSTOM_INJECTION_PATTERNS, "", value or ""):
diff --git a/lib/core/convert.py b/lib/core/convert.py
index 11197b537..8aa22490c 100644
--- a/lib/core/convert.py
+++ b/lib/core/convert.py
@@ -48,16 +48,16 @@ def base64pickle(value):
retVal = None
try:
- retVal = encodeBase64(pickle.dumps(value, PICKLE_PROTOCOL))
+ retVal = encodeBase64(pickle.dumps(value, PICKLE_PROTOCOL), binary=False)
except:
warnMsg = "problem occurred while serializing "
warnMsg += "instance of a type '%s'" % type(value)
singleTimeWarnMessage(warnMsg)
try:
- retVal = encodeBase64(pickle.dumps(value))
+ retVal = encodeBase64(pickle.dumps(value), binary=False)
except:
- retVal = encodeBase64(pickle.dumps(str(value), PICKLE_PROTOCOL))
+ retVal = encodeBase64(pickle.dumps(str(value), PICKLE_PROTOCOL), binary=False)
return retVal
diff --git a/lib/core/enums.py b/lib/core/enums.py
index 16039fb66..b8fae85f6 100644
--- a/lib/core/enums.py
+++ b/lib/core/enums.py
@@ -239,6 +239,7 @@ class HTTP_HEADER(object):
EXPIRES = "Expires"
HOST = "Host"
IF_MODIFIED_SINCE = "If-Modified-Since"
+ IF_NONE_MATCH = "If-None-Match"
LAST_MODIFIED = "Last-Modified"
LOCATION = "Location"
PRAGMA = "Pragma"
diff --git a/lib/core/option.py b/lib/core/option.py
index 7e78ce032..7512cb116 100644
--- a/lib/core/option.py
+++ b/lib/core/option.py
@@ -825,7 +825,7 @@ def _setTamperingFunctions():
def _setPreprocessFunctions():
"""
- Loads preprocess functions from given script(s)
+ Loads preprocess function(s) from given script(s)
"""
if conf.preprocess:
@@ -870,17 +870,95 @@ def _setPreprocessFunctions():
raise SqlmapSyntaxException("cannot import preprocess module '%s' (%s)" % (getUnicode(filename[:-3]), getSafeExString(ex)))
for name, function in inspect.getmembers(module, inspect.isfunction):
- if name == "preprocess" and inspect.getargspec(function).args and all(_ in inspect.getargspec(function).args for _ in ("page", "headers", "code")):
+ try:
+ if name == "preprocess" and inspect.getargspec(function).args and all(_ in inspect.getargspec(function).args for _ in ("req",)):
+ found = True
+
+ kb.preprocessFunctions.append(function)
+ function.__name__ = module.__name__
+
+ break
+ except ValueError: # Note: https://github.com/sqlmapproject/sqlmap/issues/4357
+ pass
+
+ if not found:
+ errMsg = "missing function 'preprocess(req)' "
+ errMsg += "in preprocess script '%s'" % script
+ raise SqlmapGenericException(errMsg)
+ else:
+ try:
+ function(_urllib.request.Request("http://localhost"))
+ except:
+ handle, filename = tempfile.mkstemp(prefix=MKSTEMP_PREFIX.PREPROCESS, suffix=".py")
+ os.close(handle)
+
+ openFile(filename, "w+b").write("#!/usr/bin/env\n\ndef preprocess(req):\n pass\n")
+ openFile(os.path.join(os.path.dirname(filename), "__init__.py"), "w+b").write("pass")
+
+ errMsg = "function 'preprocess(req)' "
+ errMsg += "in preprocess script '%s' " % script
+ errMsg += "appears to be invalid "
+ errMsg += "(Note: find template script at '%s')" % filename
+ raise SqlmapGenericException(errMsg)
+
+def _setPostprocessFunctions():
+ """
+ Loads postprocess function(s) from given script(s)
+ """
+
+ if conf.postprocess:
+ for script in re.split(PARAMETER_SPLITTING_REGEX, conf.postprocess):
+ found = False
+ function = None
+
+ script = safeFilepathEncode(script.strip())
+
+ try:
+ if not script:
+ continue
+
+ if not os.path.exists(script):
+ errMsg = "postprocess script '%s' does not exist" % script
+ raise SqlmapFilePathException(errMsg)
+
+ elif not script.endswith(".py"):
+ errMsg = "postprocess script '%s' should have an extension '.py'" % script
+ raise SqlmapSyntaxException(errMsg)
+ except UnicodeDecodeError:
+ errMsg = "invalid character provided in option '--postprocess'"
+ raise SqlmapSyntaxException(errMsg)
+
+ dirname, filename = os.path.split(script)
+ dirname = os.path.abspath(dirname)
+
+ infoMsg = "loading postprocess module '%s'" % filename[:-3]
+ logger.info(infoMsg)
+
+ if not os.path.exists(os.path.join(dirname, "__init__.py")):
+ errMsg = "make sure that there is an empty file '__init__.py' "
+ errMsg += "inside of postprocess scripts directory '%s'" % dirname
+ raise SqlmapGenericException(errMsg)
+
+ if dirname not in sys.path:
+ sys.path.insert(0, dirname)
+
+ try:
+ module = __import__(safeFilepathEncode(filename[:-3]))
+ except Exception as ex:
+ raise SqlmapSyntaxException("cannot import postprocess module '%s' (%s)" % (getUnicode(filename[:-3]), getSafeExString(ex)))
+
+ for name, function in inspect.getmembers(module, inspect.isfunction):
+ if name == "postprocess" and inspect.getargspec(function).args and all(_ in inspect.getargspec(function).args for _ in ("page", "headers", "code")):
found = True
- kb.preprocessFunctions.append(function)
+ kb.postprocessFunctions.append(function)
function.__name__ = module.__name__
break
if not found:
- errMsg = "missing function 'preprocess(page, headers=None, code=None)' "
- errMsg += "in preprocess script '%s'" % script
+ errMsg = "missing function 'postprocess(page, headers=None, code=None)' "
+ errMsg += "in postprocess script '%s'" % script
raise SqlmapGenericException(errMsg)
else:
try:
@@ -889,11 +967,11 @@ def _setPreprocessFunctions():
handle, filename = tempfile.mkstemp(prefix=MKSTEMP_PREFIX.PREPROCESS, suffix=".py")
os.close(handle)
- open(filename, "w+b").write("#!/usr/bin/env\n\ndef preprocess(page, headers=None, code=None):\n return page, headers, code\n")
- open(os.path.join(os.path.dirname(filename), "__init__.py"), "w+b").write("pass")
+ openFile(filename, "w+b").write("#!/usr/bin/env\n\ndef postprocess(page, headers=None, code=None):\n return page, headers, code\n")
+ openFile(os.path.join(os.path.dirname(filename), "__init__.py"), "w+b").write("pass")
- errMsg = "function 'preprocess(page, headers=None, code=None)' "
- errMsg += "in preprocess script '%s' " % script
+ errMsg = "function 'postprocess(page, headers=None, code=None)' "
+ errMsg += "in postprocess script '%s' " % script
errMsg += "should return a tuple '(page, headers, code)' "
errMsg += "(Note: find template script at '%s')" % filename
raise SqlmapGenericException(errMsg)
@@ -1450,8 +1528,8 @@ def _createHomeDirectories():
if conf.get("purge"):
return
- for context in "output", "history":
- directory = paths["SQLMAP_%s_PATH" % context.upper()]
+ for context in ("output", "history"):
+ directory = paths["SQLMAP_%s_PATH" % getUnicode(context).upper()] # NOTE: https://github.com/sqlmapproject/sqlmap/issues/4363
try:
if not os.path.isdir(directory):
os.makedirs(directory)
@@ -1762,6 +1840,8 @@ def _cleanupOptions():
if not regex:
conf.exclude = re.sub(r"\s*,\s*", ',', conf.exclude)
conf.exclude = r"\A%s\Z" % '|'.join(re.escape(_) for _ in conf.exclude.split(','))
+ else:
+ conf.exclude = re.sub(r"(\w+)\$", r"\g<1>\$", conf.exclude)
if conf.binaryFields:
conf.binaryFields = conf.binaryFields.replace(" ", "")
@@ -2009,10 +2089,11 @@ def _setKnowledgeBaseAttributes(flushAll=True):
kb.skipSeqMatcher = False
kb.smokeMode = False
kb.reduceTests = None
- kb.tlsSNI = {}
+ kb.sslSuccess = False
kb.stickyDBMS = False
kb.storeHashesChoice = None
kb.suppressResumeInfo = False
+ kb.tableExistsChoice = None
kb.tableFrom = None
kb.technique = None
kb.tempDir = None
@@ -2022,7 +2103,7 @@ def _setKnowledgeBaseAttributes(flushAll=True):
kb.testType = None
kb.threadContinue = True
kb.threadException = False
- kb.tableExistsChoice = None
+ kb.tlsSNI = {}
kb.uChar = NULL
kb.udfFail = False
kb.unionDuplicates = False
@@ -2037,6 +2118,7 @@ def _setKnowledgeBaseAttributes(flushAll=True):
kb.keywords = set(getFileItems(paths.SQL_KEYWORDS))
kb.normalizeCrawlingChoice = None
kb.passwordMgr = None
+ kb.postprocessFunctions = []
kb.preprocessFunctions = []
kb.skipVulnHost = None
kb.storeCrawlingChoice = None
@@ -2683,6 +2765,7 @@ def init():
_listTamperingFunctions()
_setTamperingFunctions()
_setPreprocessFunctions()
+ _setPostprocessFunctions()
_setTrafficOutputFP()
_setupHTTPCollector()
_setHttpChunked()
diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py
index 57a28d3ef..c0cfe5d0d 100644
--- a/lib/core/optiondict.py
+++ b/lib/core/optiondict.py
@@ -222,6 +222,7 @@ optDict = {
"hexConvert": "boolean",
"outputDir": "string",
"parseErrors": "boolean",
+ "postprocess": "string",
"preprocess": "string",
"repair": "boolean",
"saveConfig": "string",
diff --git a/lib/core/patch.py b/lib/core/patch.py
index b1c5773dd..ada695427 100644
--- a/lib/core/patch.py
+++ b/lib/core/patch.py
@@ -6,6 +6,7 @@ See the file 'LICENSE' for copying permission
"""
import codecs
+import os
import random
import lib.controller.checks
@@ -76,6 +77,15 @@ def dirtyPatches():
# to prevent too much "guessing" in case of binary data retrieval
thirdparty.chardet.universaldetector.MINIMUM_THRESHOLD = 0.90
+ # https://github.com/sqlmapproject/sqlmap/issues/4314
+ try:
+ os.urandom(1)
+ except NotImplemented:
+ if six.PY3:
+ os.urandom = lambda size: bytes(random.randint(0, 255) for _ in range(size))
+ else:
+ os.urandom = lambda size: "".join(chr(random.randint(0, 255)) for _ in xrange(size))
+
def resolveCrossReferences():
"""
Place for cross-reference resolution
diff --git a/lib/core/settings.py b/lib/core/settings.py
index 19d4a3b57..837784503 100644
--- a/lib/core/settings.py
+++ b/lib/core/settings.py
@@ -18,7 +18,7 @@ from lib.core.enums import OS
from thirdparty.six import unichr as _unichr
# sqlmap version (...)
-VERSION = "1.4.8.9"
+VERSION = "1.4.10.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)
@@ -66,6 +66,7 @@ PARTIAL_HEX_VALUE_MARKER = "__PARTIAL_HEX_VALUE__"
URI_QUESTION_MARKER = "__QUESTION_MARK__"
ASTERISK_MARKER = "__ASTERISK_MARK__"
REPLACEMENT_MARKER = "__REPLACEMENT_MARK__"
+BOUNDED_BASE64_MARKER = "__BOUNDED_BASE64_MARK__"
BOUNDED_INJECTION_MARKER = "__BOUNDED_INJECTION_MARK__"
SAFE_VARIABLE_MARKER = "__SAFE__"
SAFE_HEX_MARKER = "__SAFE_HEX__"
diff --git a/lib/core/target.py b/lib/core/target.py
index 6470be1c3..1f67770e3 100644
--- a/lib/core/target.py
+++ b/lib/core/target.py
@@ -111,7 +111,7 @@ def _setRequestParams():
def process(match, repl):
retVal = match.group(0)
- if not (conf.testParameter and match.group("name") not in [removePostHintPrefix(_) for _ in conf.testParameter]) and match.group("name") == match.group("name").strip('\\'):
+ if not (conf.testParameter and match.group("name") not in (removePostHintPrefix(_) for _ in conf.testParameter)) and match.group("name") == match.group("name").strip('\\'):
retVal = repl
while True:
_ = re.search(r"\\g<([^>]+)>", retVal)
diff --git a/lib/core/testing.py b/lib/core/testing.py
index cb9fe70d9..61dd1c3dc 100644
--- a/lib/core/testing.py
+++ b/lib/core/testing.py
@@ -44,10 +44,13 @@ def vulnTest():
(u"-c --flush-session --roles --statements --hostname --privileges --sql-query=\"SELECT '\u0161u\u0107uraj'\" --technique=U", (u": '\u0161u\u0107uraj'", "on SQLite it is not possible")),
(u"-u --flush-session --sql-query=\"SELECT '\u0161u\u0107uraj'\" --technique=B --no-escape --string=luther --unstable", (u": '\u0161u\u0107uraj'",)),
("--dummy", ("all tested parameters do not appear to be injectable", "does not seem to be injectable", "there is not at least one", "~might be injectable")),
+ ("-u '&id2=1' -p id2 -v 5 --flush-session --level=5 --test-filter='AND boolean-based blind - WHERE or HAVING clause (MySQL comment)'", ("~1AND",)),
("--list-tampers", ("between", "MySQL", "xforwardedfor")),
("-r --flush-session -v 5 --test-skip='heavy' --save=", ("CloudFlare", "possible DBMS: 'SQLite'", "User-agent: foobar", "~Type: time-based blind")),
("-l --flush-session --keep-alive --skip-waf -v 5 --technique=U --union-from=users --banner --parse-errors", ("banner: '3.", "ORDER BY term out of range", "~xp_cmdshell", "Connection: keep-alive")),
("-l --offline --banner -v 5", ("banner: '3.", "~[TRAFFIC OUT]")),
+ ("-u -p id --base64=id --data='base64=true' --flush-session --banner --technique=B", ("banner: '3.",)),
+ ("-u -p id --base64=id --data='base64=true' --flush-session --tables --technique=U", (" users ",)),
("-u --flush-session --banner --technique=B --not-string 'no results'", ("banner: '3.",)),
("-u --flush-session --banner --technique=B --first=1 --last=2", ("banner: '3.'",)),
("-u --flush-session --encoding=ascii --forms --crawl=2 --threads=2 --banner", ("total of 2 targets", "might be injectable", "Type: UNION query", "banner: '3.")),
@@ -125,7 +128,10 @@ def vulnTest():
status = '%d/%d (%d%%) ' % (count, len(TESTS), round(100.0 * count / len(TESTS)))
dataToStdout("\r[%s] [INFO] complete: %s" % (time.strftime("%X"), status))
- cmd = "%s %s %s --batch --non-interactive" % (sys.executable, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "sqlmap.py")), options.replace("", url).replace("", direct).replace("", request).replace("", log).replace("", config))
+ for tag, value in (("", url), ("", direct), ("", request), ("", log), ("", config), ("", url.replace("id=1", "id=MZ=%3d"))):
+ options = options.replace(tag, value)
+
+ cmd = "%s %s %s --batch --non-interactive" % (sys.executable, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "sqlmap.py")), options)
if "" in cmd:
handle, tmp = tempfile.mkstemp()
diff --git a/lib/parse/cmdline.py b/lib/parse/cmdline.py
index 9869199f3..f6deac160 100644
--- a/lib/parse/cmdline.py
+++ b/lib/parse/cmdline.py
@@ -623,7 +623,7 @@ def cmdLineParser(argv=None):
help="Parameter(s) containing Base64 encoded data")
general.add_argument("--base64-safe", dest="base64Safe", action="store_true",
- help="Use URL and filename safe Base64 alphabet")
+ help="Use URL and filename safe Base64 alphabet (RFC 4648)")
general.add_argument("--batch", dest="batch", action="store_true",
help="Never ask for user input, use the default behavior")
@@ -683,7 +683,10 @@ def cmdLineParser(argv=None):
help="Parse and display DBMS error messages from responses")
general.add_argument("--preprocess", dest="preprocess",
- help="Use given script(s) for preprocessing of response data")
+ help="Use given script(s) for preprocessing (request)")
+
+ general.add_argument("--postprocess", dest="postprocess",
+ help="Use given script(s) for postprocessing (response)")
general.add_argument("--repair", dest="repair", action="store_true",
help="Redump entries having unknown character marker (%s)" % INFERENCE_UNKNOWN_CHAR)
@@ -863,7 +866,7 @@ def cmdLineParser(argv=None):
_ = []
advancedHelp = True
extraHeaders = []
- tamperIndex = None
+ auxIndexes = {}
# Reference: https://stackoverflow.com/a/4012683 (Note: previously used "...sys.getfilesystemencoding() or UNICODE_ENCODING")
for arg in argv:
@@ -952,14 +955,20 @@ def cmdLineParser(argv=None):
argv[i] = ""
elif argv[i] in DEPRECATED_OPTIONS:
argv[i] = ""
- elif argv[i].startswith("--tamper"):
- if tamperIndex is None:
- tamperIndex = i if '=' in argv[i] else (i + 1 if i + 1 < len(argv) and not argv[i + 1].startswith('-') else None)
+ elif any(argv[i].startswith(_) for _ in ("--tamper", "--ignore-code", "--skip")):
+ key = re.search(r"\-?\-(\w+)\b", argv[i]).group(1)
+ index = auxIndexes.get(key, None)
+ if index is None:
+ index = i if '=' in argv[i] else (i + 1 if i + 1 < len(argv) and not argv[i + 1].startswith('-') else None)
+ auxIndexes[key] = index
else:
- argv[tamperIndex] = "%s,%s" % (argv[tamperIndex], argv[i].split('=')[1] if '=' in argv[i] else (argv[i + 1] if i + 1 < len(argv) and not argv[i + 1].startswith('-') else ""))
+ delimiter = ','
+ argv[index] = "%s%s%s" % (argv[index], delimiter, argv[i].split('=')[1] if '=' in argv[i] else (argv[i + 1] if i + 1 < len(argv) and not argv[i + 1].startswith('-') else ""))
argv[i] = ""
- elif argv[i] in ("-H", "--header"):
- if i + 1 < len(argv):
+ elif argv[i] in ("-H", "--header") or any(argv[i].startswith("%s=" % _) for _ in ("-H", "--header")):
+ if '=' in argv[i]:
+ extraHeaders.append(argv[i].split('=', 1)[1])
+ elif i + 1 < len(argv):
extraHeaders.append(argv[i + 1])
elif argv[i] == "--deps":
argv[i] = "--dependencies"
@@ -997,7 +1006,7 @@ def cmdLineParser(argv=None):
for verbosity in (_ for _ in argv if re.search(r"\A\-v+\Z", _)):
try:
if argv.index(verbosity) == len(argv) - 1 or not argv[argv.index(verbosity) + 1].isdigit():
- conf.verbose = verbosity.count('v') + 1
+ conf.verbose = verbosity.count('v')
del argv[argv.index(verbosity)]
except (IndexError, ValueError):
pass
diff --git a/lib/request/basic.py b/lib/request/basic.py
index 1ebf9adf5..80b662c6f 100644
--- a/lib/request/basic.py
+++ b/lib/request/basic.py
@@ -353,7 +353,7 @@ def decodePage(page, contentEncoding, contentType, percentDecode=True):
if (kb.pageEncoding or "").lower() == "utf-8-sig":
kb.pageEncoding = "utf-8"
- if page and page.startswith("\xef\xbb\xbf"): # Reference: https://docs.python.org/2/library/codecs.html (Note: noticed problems when "utf-8-sig" is left to Python for handling)
+ if page and page.startswith(b"\xef\xbb\xbf"): # Reference: https://docs.python.org/2/library/codecs.html (Note: noticed problems when "utf-8-sig" is left to Python for handling)
page = page[3:]
page = getUnicode(page, kb.pageEncoding)
@@ -394,7 +394,7 @@ def processResponse(page, responseHeaders, code=None, status=None):
if msg:
logger.warning("parsed DBMS error message: '%s'" % msg.rstrip('.'))
- if kb.processResponseCounter < IDENTYWAF_PARSE_LIMIT:
+ if not conf.skipWaf and kb.processResponseCounter < IDENTYWAF_PARSE_LIMIT:
rawResponse = "%s %s %s\n%s\n%s" % (_http_client.HTTPConnection._http_vsn_str, code or "", status or "", getUnicode("".join(responseHeaders.headers if responseHeaders else [])), page)
identYwaf.non_blind.clear()
diff --git a/lib/request/connect.py b/lib/request/connect.py
index df0b7f6c9..739d4dd13 100644
--- a/lib/request/connect.py
+++ b/lib/request/connect.py
@@ -501,6 +501,16 @@ class Connect(object):
else:
return None, None, None
+ for function in kb.preprocessFunctions:
+ try:
+ function(req)
+ except Exception as ex:
+ errMsg = "error occurred while running preprocess "
+ errMsg += "function '%s' ('%s')" % (function.__name__, getSafeExString(ex))
+ raise SqlmapGenericException(errMsg)
+ else:
+ post, headers = req.data, req.headers
+
requestHeaders += "\r\n".join(["%s: %s" % (getUnicode(key.capitalize() if hasattr(key, "capitalize") else key), getUnicode(value)) for (key, value) in req.header_items()])
if not getRequestHeader(req, HTTP_HEADER.COOKIE) and conf.cj:
@@ -539,7 +549,7 @@ class Connect(object):
conn = _urllib.request.urlopen(req)
if not kb.authHeader and getRequestHeader(req, HTTP_HEADER.AUTHORIZATION) and (conf.authType or "").lower() == AUTH_TYPE.BASIC.lower():
- kb.authHeader = getRequestHeader(req, HTTP_HEADER.AUTHORIZATION)
+ kb.authHeader = getUnicode(getRequestHeader(req, HTTP_HEADER.AUTHORIZATION))
if not kb.proxyAuthHeader and getRequestHeader(req, HTTP_HEADER.PROXY_AUTHORIZATION):
kb.proxyAuthHeader = getRequestHeader(req, HTTP_HEADER.PROXY_AUTHORIZATION)
@@ -815,11 +825,11 @@ class Connect(object):
else:
page = getUnicode(page)
- for function in kb.preprocessFunctions:
+ for function in kb.postprocessFunctions:
try:
page, responseHeaders, code = function(page, responseHeaders, code)
except Exception as ex:
- errMsg = "error occurred while running preprocess "
+ errMsg = "error occurred while running postprocess "
errMsg += "function '%s' ('%s')" % (function.__name__, getSafeExString(ex))
raise SqlmapGenericException(errMsg)
@@ -1089,6 +1099,9 @@ class Connect(object):
if not match:
match = re.search(r"\b(?P%s)\s*=\s*['\"]?(?P[^;'\"]+)" % conf.csrfToken, page or "", re.I)
+ if not match:
+ match = re.search(r"%s)[\"']?[^>]+\b(value|content)=[\"']?(?P[^>\"']+)" % conf.csrfToken, page or "", re.I)
+
if match:
token.name, token.value = match.group("name"), match.group("value")
@@ -1131,7 +1144,7 @@ class Connect(object):
uri = _adjustParameter(uri, token.name, token.value)
elif candidate == PLACE.GET and get:
get = _adjustParameter(get, token.name, token.value)
- elif candidate in [PLACE.POST, PLACE.CUSTOM_POST] and post:
+ elif candidate in (PLACE.POST, PLACE.CUSTOM_POST) and post:
post = _adjustParameter(post, token.name, token.value)
for i in xrange(len(conf.httpHeaders)):
diff --git a/lib/request/httpshandler.py b/lib/request/httpshandler.py
index 854cbe1a7..841b0069e 100644
--- a/lib/request/httpshandler.py
+++ b/lib/request/httpshandler.py
@@ -11,6 +11,8 @@ import socket
from lib.core.common import filterNone
from lib.core.common import getSafeExString
+from lib.core.compat import xrange
+from lib.core.data import conf
from lib.core.data import kb
from lib.core.data import logger
from lib.core.exception import SqlmapConnectionException
@@ -43,6 +45,8 @@ class HTTPSConnection(_http_client.HTTPSConnection):
_contexts[None] = ssl._create_default_https_context()
kwargs["context"] = _contexts[None]
+ self.retrying = False
+
_http_client.HTTPSConnection.__init__(self, *args, **kwargs)
def connect(self):
@@ -58,7 +62,7 @@ class HTTPSConnection(_http_client.HTTPSConnection):
# Reference(s): https://docs.python.org/2/library/ssl.html#ssl.SSLContext
# https://www.mnot.net/blog/2014/12/27/python_2_and_tls_sni
if re.search(r"\A[\d.]+\Z", self.host) is None and kb.tlsSNI.get(self.host) is not False and hasattr(ssl, "SSLContext"):
- for protocol in [_ for _ in _protocols if _ >= ssl.PROTOCOL_TLSv1]:
+ for protocol in (_ for _ in _protocols if _ >= ssl.PROTOCOL_TLSv1):
try:
sock = create_sock()
if protocol not in _contexts:
@@ -101,7 +105,21 @@ class HTTPSConnection(_http_client.HTTPSConnection):
# Reference: https://docs.python.org/2/library/ssl.html
if distutils.version.LooseVersion(PYVERSION) < distutils.version.LooseVersion("2.7.9"):
errMsg += " (please retry with Python >= 2.7.9)"
+
+ if kb.sslSuccess and not self.retrying:
+ self.retrying = True
+
+ for _ in xrange(conf.retries):
+ try:
+ self.connect()
+ except SqlmapConnectionException:
+ pass
+ else:
+ return
+
raise SqlmapConnectionException(errMsg)
+ else:
+ kb.sslSuccess = True
class HTTPSHandler(_urllib.request.HTTPSHandler):
def https_open(self, req):
diff --git a/lib/request/redirecthandler.py b/lib/request/redirecthandler.py
index b0eaf3784..049108189 100644
--- a/lib/request/redirecthandler.py
+++ b/lib/request/redirecthandler.py
@@ -160,7 +160,7 @@ class SmartRedirectHandler(_urllib.request.HTTPRedirectHandler):
if not hasattr(result, "read"):
def _(self, length=None):
try:
- retVal = getSafeExString(ex)
+ retVal = getSafeExString(ex) # Note: pyflakes mistakenly marks 'ex' as undefined (NOTE: tested in both Python2 and Python3)
except:
retVal = ""
return retVal
diff --git a/lib/utils/hash.py b/lib/utils/hash.py
index 3845391e8..87f347177 100644
--- a/lib/utils/hash.py
+++ b/lib/utils/hash.py
@@ -1147,6 +1147,12 @@ def dictionaryAttack(attack_dict):
warnMsg = "user aborted during dictionary-based attack phase (Ctrl+C was pressed)"
logger.warn(warnMsg)
+ finally:
+ if _multiprocessing:
+ gc.enable()
+
+ # NOTE: https://github.com/sqlmapproject/sqlmap/issues/4367
+ # NOTE: https://dzone.com/articles/python-101-creating-multiple-processes
for process in processes:
try:
process.terminate()
@@ -1154,10 +1160,6 @@ def dictionaryAttack(attack_dict):
except (OSError, AttributeError):
pass
- finally:
- if _multiprocessing:
- gc.enable()
-
if retVal:
if conf.hashDB:
conf.hashDB.beginTransaction()
diff --git a/plugins/generic/databases.py b/plugins/generic/databases.py
index cc8b89730..7a3dfe4dd 100644
--- a/plugins/generic/databases.py
+++ b/plugins/generic/databases.py
@@ -525,6 +525,9 @@ class Databases(object):
else:
return kb.data.cachedColumns
+ if conf.exclude:
+ tblList = [_ for _ in tblList if re.search(conf.exclude, _, re.I) is None]
+
tblList = filterNone(safeSQLIdentificatorNaming(_, True) for _ in tblList)
if bruteForce is None:
diff --git a/plugins/generic/search.py b/plugins/generic/search.py
index 6e4a0e2a4..42afd3cfe 100644
--- a/plugins/generic/search.py
+++ b/plugins/generic/search.py
@@ -410,9 +410,11 @@ class Search(object):
if tblCond:
if conf.tbl:
- _ = conf.tbl.split(',')
- whereTblsQuery = " AND (" + " OR ".join("%s = '%s'" % (tblCond, unsafeSQLIdentificatorNaming(tbl)) for tbl in _) + ")"
- infoMsgTbl = " for table%s '%s'" % ("s" if len(_) > 1 else "", ", ".join(unsafeSQLIdentificatorNaming(tbl) for tbl in _))
+ tbls = conf.tbl.split(',')
+ if conf.exclude:
+ tbls = [_ for _ in tbls if re.search(conf.exclude, _, re.I) is None]
+ whereTblsQuery = " AND (" + " OR ".join("%s = '%s'" % (tblCond, unsafeSQLIdentificatorNaming(tbl)) for tbl in tbls) + ")"
+ infoMsgTbl = " for table%s '%s'" % ("s" if len(tbls) > 1 else "", ", ".join(unsafeSQLIdentificatorNaming(tbl) for tbl in tbls))
if conf.db == CURRENT_DB:
conf.db = self.getCurrentDb()
diff --git a/plugins/generic/users.py b/plugins/generic/users.py
index 64dcebfd6..660aec129 100644
--- a/plugins/generic/users.py
+++ b/plugins/generic/users.py
@@ -617,7 +617,8 @@ class Users(object):
# In Informix we get one letter for the highest privilege
elif Backend.isDbms(DBMS.INFORMIX):
- privileges.add(INFORMIX_PRIVS[privilege.strip()])
+ if privilege.strip() in INFORMIX_PRIVS:
+ privileges.add(INFORMIX_PRIVS[privilege.strip()])
# In DB2 we get Y or G if the privilege is
# True, N otherwise
diff --git a/sqlmap.conf b/sqlmap.conf
index 3d46fb6ba..480cb27c3 100644
--- a/sqlmap.conf
+++ b/sqlmap.conf
@@ -769,9 +769,12 @@ outputDir =
# Valid: True or False
parseErrors = False
-# Use given script(s) for preprocessing of response data.
+# Use given script(s) for preprocessing of request.
preprocess =
+# Use given script(s) for postprocessing of response data.
+postprocess =
+
# Redump entries having unknown character marker (?).
# Valid: True or False
repair = False
diff --git a/sqlmap.py b/sqlmap.py
index 09ae03a43..71681dd39 100755
--- a/sqlmap.py
+++ b/sqlmap.py
@@ -317,7 +317,7 @@ def main():
logger.critical(errMsg)
raise SystemExit
- elif any(_ in excMsg for _ in ("tempfile.mkdtemp", "tempfile.mkstemp")):
+ elif any(_ in excMsg for _ in ("tempfile.mkdtemp", "tempfile.mkstemp", "tempfile.py")):
errMsg = "unable to write to the temporary directory '%s'. " % tempfile.gettempdir()
errMsg += "Please make sure that your disk is not full and "
errMsg += "that you have sufficient write permissions to "
@@ -428,6 +428,12 @@ def main():
logger.critical(errMsg)
raise SystemExit
+ elif all(_ in excMsg for _ in ("HTTPNtlmAuthHandler", "'str' object has no attribute 'decode'")):
+ errMsg = "package 'python-ntlm' has a known compatibility issue with the "
+ errMsg += "Python 3 (Reference: https://github.com/mullender/python-ntlm/pull/61)"
+ logger.critical(errMsg)
+ raise SystemExit
+
elif "'DictObject' object has no attribute '" in excMsg and all(_ in errMsg for _ in ("(fingerprinted)", "(identified)")):
errMsg = "there has been a problem in enumeration. "
errMsg += "Because of a considerable chance of false-positive case "
diff --git a/tamper/0eunion.py b/tamper/0eunion.py
index b46f381e2..44de282dd 100644
--- a/tamper/0eunion.py
+++ b/tamper/0eunion.py
@@ -29,4 +29,4 @@ def tamper(payload, **kwargs):
'1e0UNION ALL SELECT'
"""
- return re.sub("(\d+)\s+(UNION )", r"\g<1>e0\g<2>", payload, re.I) if payload else payload
+ return re.sub(r"(\d+)\s+(UNION )", r"\g<1>e0\g<2>", payload, re.I) if payload else payload
diff --git a/tamper/dunion.py b/tamper/dunion.py
index 70ebd002c..74a2c6c15 100644
--- a/tamper/dunion.py
+++ b/tamper/dunion.py
@@ -31,4 +31,4 @@ def tamper(payload, **kwargs):
'1DUNION ALL SELECT'
"""
- return re.sub("(\d+)\s+(UNION )", r"\g<1>D\g<2>", payload, re.I) if payload else payload
+ return re.sub(r"(\d+)\s+(UNION )", r"\g<1>D\g<2>", payload, re.I) if payload else payload
diff --git a/tamper/hex2char.py b/tamper/hex2char.py
index 5df432650..aae8ea415 100644
--- a/tamper/hex2char.py
+++ b/tamper/hex2char.py
@@ -5,6 +5,7 @@ Copyright (c) 2006-2020 sqlmap developers (http://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""
+import os
import re
from lib.core.common import singleTimeWarnMessage
diff --git a/tamper/misunion.py b/tamper/misunion.py
index baf767abb..68a9e4fb0 100644
--- a/tamper/misunion.py
+++ b/tamper/misunion.py
@@ -33,4 +33,4 @@ def tamper(payload, **kwargs):
'1"-.1UNION ALL SELECT'
"""
- return re.sub("\s+(UNION )", r"-.1\g<1>", payload, re.I) if payload else payload
+ return re.sub(r"\s+(UNION )", r"-.1\g<1>", payload, re.I) if payload else payload
diff --git a/tamper/schemasplit.py b/tamper/schemasplit.py
index 243f14076..91bfb48d2 100644
--- a/tamper/schemasplit.py
+++ b/tamper/schemasplit.py
@@ -16,7 +16,7 @@ def dependencies():
def tamper(payload, **kwargs):
"""
- Replaces instances of UNION with e0UNION
+ Splits FROM schema identifiers (e.g. 'testdb.users') with whitespace (e.g. 'testdb 9.e.users')
Requirement:
* MySQL
@@ -28,4 +28,4 @@ def tamper(payload, **kwargs):
'SELECT id FROM testdb 9.e.users'
"""
- return re.sub("( FROM \w+)\.(\w+)", r"\g<1> 9.e.\g<2>", payload, re.I) if payload else payload
+ return re.sub(r"( FROM \w+)\.(\w+)", r"\g<1> 9.e.\g<2>", payload, re.I) if payload else payload
diff --git a/tamper/sleep2getlock.py b/tamper/sleep2getlock.py
new file mode 100644
index 000000000..204c36b7c
--- /dev/null
+++ b/tamper/sleep2getlock.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python
+
+"""
+Copyright (c) 2006-2020 sqlmap developers (http://sqlmap.org/)
+See the file 'LICENSE' for copying permission
+"""
+
+from lib.core.data import kb
+from lib.core.enums import PRIORITY
+
+__priority__ = PRIORITY.HIGHEST
+
+def dependencies():
+ pass
+
+def tamper(payload, **kwargs):
+ """
+ Replaces instances like 'SLEEP(5)' with (e.g.) "GET_LOCK('ETgP',5)"
+
+ Requirement:
+ * MySQL
+
+ Tested against:
+ * MySQL 5.0 and 5.5
+
+ Notes:
+ * Useful to bypass very weak and bespoke web application firewalls
+ that filter the SLEEP() and BENCHMARK() functions
+
+ * Reference: https://zhuanlan.zhihu.com/p/35245598
+
+ >>> tamper('SLEEP(5)') == "GET_LOCK('%s',5)" % kb.aliasName
+ True
+ """
+
+ if payload:
+ payload = payload.replace("SLEEP(", "GET_LOCK('%s'," % kb.aliasName)
+
+ return payload
diff --git a/thirdparty/six/__init__.py b/thirdparty/six/__init__.py
index 89b2188fd..83f69783d 100644
--- a/thirdparty/six/__init__.py
+++ b/thirdparty/six/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2010-2018 Benjamin Peterson
+# Copyright (c) 2010-2020 Benjamin Peterson
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -29,7 +29,7 @@ import sys
import types
__author__ = "Benjamin Peterson "
-__version__ = "1.12.0"
+__version__ = "1.15.0"
# Useful for very coarse version differentiation.
@@ -255,9 +255,11 @@ _moved_attributes = [
MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"),
MovedModule("builtins", "__builtin__"),
MovedModule("configparser", "ConfigParser"),
+ MovedModule("collections_abc", "collections", "collections.abc" if sys.version_info >= (3, 3) else "collections"),
MovedModule("copyreg", "copy_reg"),
MovedModule("dbm_gnu", "gdbm", "dbm.gnu"),
- MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"),
+ MovedModule("dbm_ndbm", "dbm", "dbm.ndbm"),
+ MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread" if sys.version_info < (3, 9) else "_thread"),
MovedModule("http_cookiejar", "cookielib", "http.cookiejar"),
MovedModule("http_cookies", "Cookie", "http.cookies"),
MovedModule("html_entities", "htmlentitydefs", "html.entities"),
@@ -637,13 +639,16 @@ if PY3:
import io
StringIO = io.StringIO
BytesIO = io.BytesIO
+ del io
_assertCountEqual = "assertCountEqual"
if sys.version_info[1] <= 1:
_assertRaisesRegex = "assertRaisesRegexp"
_assertRegex = "assertRegexpMatches"
+ _assertNotRegex = "assertNotRegexpMatches"
else:
_assertRaisesRegex = "assertRaisesRegex"
_assertRegex = "assertRegex"
+ _assertNotRegex = "assertNotRegex"
else:
def b(s):
return s
@@ -665,6 +670,7 @@ else:
_assertCountEqual = "assertItemsEqual"
_assertRaisesRegex = "assertRaisesRegexp"
_assertRegex = "assertRegexpMatches"
+ _assertNotRegex = "assertNotRegexpMatches"
_add_doc(b, """Byte literal""")
_add_doc(u, """Text literal""")
@@ -681,6 +687,10 @@ def assertRegex(self, *args, **kwargs):
return getattr(self, _assertRegex)(*args, **kwargs)
+def assertNotRegex(self, *args, **kwargs):
+ return getattr(self, _assertNotRegex)(*args, **kwargs)
+
+
if PY3:
exec_ = getattr(moves.builtins, "exec")
@@ -716,16 +726,7 @@ else:
""")
-if sys.version_info[:2] == (3, 2):
- exec_("""def raise_from(value, from_value):
- try:
- if from_value is None:
- raise value
- raise value from from_value
- finally:
- value = None
-""")
-elif sys.version_info[:2] > (3, 2):
+if sys.version_info[:2] > (3,):
exec_("""def raise_from(value, from_value):
try:
raise value from from_value
@@ -805,13 +806,33 @@ if sys.version_info[:2] < (3, 3):
_add_doc(reraise, """Reraise an exception.""")
if sys.version_info[0:2] < (3, 4):
+ # This does exactly the same what the :func:`py3:functools.update_wrapper`
+ # function does on Python versions after 3.2. It sets the ``__wrapped__``
+ # attribute on ``wrapper`` object and it doesn't raise an error if any of
+ # the attributes mentioned in ``assigned`` and ``updated`` are missing on
+ # ``wrapped`` object.
+ def _update_wrapper(wrapper, wrapped,
+ assigned=functools.WRAPPER_ASSIGNMENTS,
+ updated=functools.WRAPPER_UPDATES):
+ for attr in assigned:
+ try:
+ value = getattr(wrapped, attr)
+ except AttributeError:
+ continue
+ else:
+ setattr(wrapper, attr, value)
+ for attr in updated:
+ getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
+ wrapper.__wrapped__ = wrapped
+ return wrapper
+ _update_wrapper.__doc__ = functools.update_wrapper.__doc__
+
def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS,
updated=functools.WRAPPER_UPDATES):
- def wrapper(f):
- f = functools.wraps(wrapped, assigned, updated)(f)
- f.__wrapped__ = wrapped
- return f
- return wrapper
+ return functools.partial(_update_wrapper, wrapped=wrapped,
+ assigned=assigned, updated=updated)
+ wraps.__doc__ = functools.wraps.__doc__
+
else:
wraps = functools.wraps
@@ -824,7 +845,15 @@ def with_metaclass(meta, *bases):
class metaclass(type):
def __new__(cls, name, this_bases, d):
- return meta(name, bases, d)
+ if sys.version_info[:2] >= (3, 7):
+ # This version introduced PEP 560 that requires a bit
+ # of extra care (we mimic what is done by __build_class__).
+ resolved_bases = types.resolve_bases(bases)
+ if resolved_bases is not bases:
+ d['__orig_bases__'] = bases
+ else:
+ resolved_bases = bases
+ return meta(name, resolved_bases, d)
@classmethod
def __prepare__(cls, name, this_bases):
@@ -861,12 +890,11 @@ def ensure_binary(s, encoding='utf-8', errors='strict'):
- `str` -> encoded to `bytes`
- `bytes` -> `bytes`
"""
+ if isinstance(s, binary_type):
+ return s
if isinstance(s, text_type):
return s.encode(encoding, errors)
- elif isinstance(s, binary_type):
- return s
- else:
- raise TypeError("not expecting type '%s'" % type(s))
+ raise TypeError("not expecting type '%s'" % type(s))
def ensure_str(s, encoding='utf-8', errors='strict'):
@@ -880,12 +908,15 @@ def ensure_str(s, encoding='utf-8', errors='strict'):
- `str` -> `str`
- `bytes` -> decoded to `str`
"""
- if not isinstance(s, (text_type, binary_type)):
- raise TypeError("not expecting type '%s'" % type(s))
+ # Optimization: Fast return for the common case.
+ if type(s) is str:
+ return s
if PY2 and isinstance(s, text_type):
- s = s.encode(encoding, errors)
+ return s.encode(encoding, errors)
elif PY3 and isinstance(s, binary_type):
- s = s.decode(encoding, errors)
+ return s.decode(encoding, errors)
+ elif not isinstance(s, (text_type, binary_type)):
+ raise TypeError("not expecting type '%s'" % type(s))
return s
@@ -908,10 +939,9 @@ def ensure_text(s, encoding='utf-8', errors='strict'):
raise TypeError("not expecting type '%s'" % type(s))
-
def python_2_unicode_compatible(klass):
"""
- A decorator that defines __unicode__ and __str__ methods under Python 2.
+ A class decorator that defines __unicode__ and __str__ methods under Python 2.
Under Python 3 it does nothing.
To support Python 2 and 3 with a single code base, define a __str__ method