Skip to content

Commit d01da8e

Browse files
committed
Adding ability to select SORTABLE_HTML as dump format to have sortable tables in HTML dumps.
1 parent cc245a0 commit d01da8e

File tree

7 files changed

+167
-22
lines changed

7 files changed

+167
-22
lines changed

data/txt/sha256sums.txt

+6-6
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,8 @@ b60c96780cad4a257f91a0611b08cfcc52f242908c5d5ab2bf9034ef07869602 lib/core/conve
174174
55e7d63aae317763afcbdbea1c7731497c93bad14f6d032a0ccfffe72ffc121f lib/core/decorators.py
175175
595c7dfde7c67cdb674fb019a24b07a501a9cdb6321e4f8ce3d3354cd9526eae lib/core/defaults.py
176176
e8f6f1df8814b7b03c3eba22901837555083f66c99ee93b943911de785736bfa lib/core/dicts.py
177-
5fb6ef1772580a701b1b109858163a1c16446928f8c29170d67ad4d0171c0950 lib/core/dump.py
178-
874c8eb7391ef0f82b6e870499daa336a79a6d014a23e7452205f5ef0b6a9744 lib/core/enums.py
177+
2c611c19a3c38b755a6e070a2c96ce6ac9a84123640b66bdb0a1c4a2d4ac6abf lib/core/dump.py
178+
3f438ad1ddfb3e7767837590bd0cedbf2af8a6f21c28603f62236b5623e42d04 lib/core/enums.py
179179
67ab7a8f756b63e75e8b564d647e72362d7245d6b32b2881be02321ceaaca876 lib/core/exception.py
180180
0379d59be9e2400e39abbb99fbceeb22d4c3b69540504a0cb59bf3aaf53d05a9 lib/core/gui.py
181181
99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/core/__init__.py
@@ -188,7 +188,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi
188188
4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py
189189
bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py
190190
eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py
191-
5072218a58696b0bd425421022e557da29b32a54ea181686c83b4130b6edf1ee lib/core/settings.py
191+
fbe9bfd62846770aa87133b7eddb278da5959411fc47f5261d227f381492f609 lib/core/settings.py
192192
2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py
193193
e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py
194194
54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py
@@ -199,7 +199,7 @@ ff39235aee7e33498c66132d17e6e86e7b8a29754e3fdecd880ca8356b17f791 lib/core/unesc
199199
ce65f9e8e1c726de3cec6abf31a2ffdbc16c251f772adcc14f67dee32d0f6b57 lib/core/wordlist.py
200200
99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/__init__.py
201201
ba16fdd71fba31990dc92ff5a7388fb0ebac21ca905c314be6c8c2b868f94ab7 lib/parse/banner.py
202-
d757343f241b14e23aefb2177b6c2598f1bc06253fd93b0d8a28d4a55c267100 lib/parse/cmdline.py
202+
8347cdcc053b3a08518283b6cf41a1abe72cfe0b34c1bc6de45ce7ca43ec0de5 lib/parse/cmdline.py
203203
d1fa3b9457f0e934600519309cbd3d84f9e6158a620866e7b352078c7c136f01 lib/parse/configfile.py
204204
9af4c86e41e50bd6055573a7b76e380a6658b355320c72dd6d2d5ddab14dc082 lib/parse/handler.py
205205
13b3ab678a2c422ce1dea9558668c05e562c0ec226f36053259a0be7280ebf92 lib/parse/headers.py
@@ -476,8 +476,8 @@ fff84edc86b7d22dc01148fb10bb43d51cb9638dff21436fb94555db2a664766 plugins/generi
476476
5a473c60853f54f1a4b14d79b8237f659278fe8a6b42e935ed573bf22b6d5b2c README.md
477477
78aafd53980096364f0c995c6283931bff505aed88fed1e7906fb06ee60e9c5b sqlmapapi.py
478478
168309215af7dd5b0b71070e1770e72f1cbb29a3d8025143fb8aa0b88cd56b62 sqlmapapi.yaml
479-
5e172e315524845fe091aa0b7b29303c92ac8f67594c6d50f026d627e415b7ed sqlmap.conf
480-
3a18b78b1aaf7236a35169db20eb21ca7d7fb907cd38dd34650f1da81c010cd6 sqlmap.py
479+
c94e1a4832ae6ef786431acb595428736bf13e2933c147d8bc70ba6913d65cbb sqlmap.conf
480+
96c1f77dee6746e47ee5b5d17940edee5c18901b102c1cbe2df040fb0f84e457 sqlmap.py
481481
adda508966db26c30b11390d6483c1fa25b092942a29730e739e1e50c403a21f tamper/0eunion.py
482482
d38fe5ab97b401810612eae049325aa990c55143504b25cc9924810917511dee tamper/apostrophemask.py
483483
8de713d1534d8cda171db4ceeb9f4324bcc030bbef21ffeaf60396c6bece31e4 tamper/apostrophenullencode.py

lib/core/dump.py

+5
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@
4747
from lib.core.replication import Replication
4848
from lib.core.settings import DUMP_FILE_BUFFER_SIZE
4949
from lib.core.settings import HTML_DUMP_CSS_STYLE
50+
from lib.core.settings import HTML_DUMP_CSS_SORTABLE_STYLE
51+
from lib.core.settings import HTML_DUMP_SORTABLE_JAVASCRIPT
5052
from lib.core.settings import IS_WIN
5153
from lib.core.settings import METADB_SUFFIX
5254
from lib.core.settings import MIN_BINARY_DISK_DUMP_SIZE
@@ -541,6 +543,9 @@ def dbTableValues(self, tableValues):
541543
dataToDumpFile(dumpFP, "<meta name=\"generator\" content=\"%s\" />\n" % VERSION_STRING)
542544
dataToDumpFile(dumpFP, "<title>%s</title>\n" % ("%s%s" % ("%s." % db if METADB_SUFFIX not in db else "", table)))
543545
dataToDumpFile(dumpFP, HTML_DUMP_CSS_STYLE)
546+
if conf.dumpSortable:
547+
dataToDumpFile(dumpFP, HTML_DUMP_CSS_SORTABLE_STYLE)
548+
dataToDumpFile(dumpFP, HTML_DUMP_SORTABLE_JAVASCRIPT)
544549
dataToDumpFile(dumpFP, "\n</head>\n<body>\n<table>\n<thead>\n<tr>\n")
545550

546551
if count == 1:

lib/core/enums.py

+1
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ class REGISTRY_OPERATION(object):
229229
class DUMP_FORMAT(object):
230230
CSV = "CSV"
231231
HTML = "HTML"
232+
SORTABLE_HTML = "SORTABLE_HTML"
232233
SQLITE = "SQLITE"
233234

234235
class HTTP_HEADER(object):

lib/core/settings.py

+147-13
Original file line numberDiff line numberDiff line change
@@ -918,29 +918,163 @@
918918

919919
# CSS style used in HTML dump format
920920
HTML_DUMP_CSS_STYLE = """<style>
921-
table{
922-
margin:10;
923-
background-color:#FFFFFF;
924-
font-family:verdana;
925-
font-size:12px;
926-
align:center;
921+
table {
922+
margin: 10px;
923+
background: #fff;
924+
font: 12px verdana;
925+
text-align: center;
927926
}
928927
thead{
929928
font-weight:bold;
930929
background-color:#4F81BD;
931-
color:#FFFFFF;
930+
color: #fff;
932931
}
933932
tr:nth-child(even) {
934-
background-color: #D3DFEE
933+
background-color: #D3DFEE;
935934
}
936-
td{
937-
font-size:12px;
935+
</style>"""
936+
937+
HTML_DUMP_CSS_SORTABLE_STYLE = """
938+
<style>
939+
table thead th {
940+
cursor: pointer;
941+
white-space: nowrap;
942+
position: sticky;
943+
top: 0;
944+
z-index: 1;
938945
}
939-
th{
940-
font-size:12px;
946+
947+
table thead th::after,
948+
table thead th::before {
949+
color: transparent;
950+
}
951+
952+
table thead th::after {
953+
margin-left: 3px;
954+
content: "▸";
955+
}
956+
957+
table thead th:hover::after,
958+
table thead th[aria-sort]::after {
959+
color: inherit;
960+
}
961+
962+
table thead th[aria-sort=descending]::after {
963+
content: "▾";
941964
}
942-
</style>"""
943965
966+
table thead th[aria-sort=ascending]::after {
967+
content: "▴";
968+
}
969+
970+
table thead th.indicator-left::before {
971+
margin-right: 3px;
972+
content: "▸";
973+
}
974+
975+
table thead th.indicator-left[aria-sort=descending]::before {
976+
color: inherit;
977+
content: "▾";
978+
}
979+
980+
table thead th.indicator-left[aria-sort=ascending]::before {
981+
color: inherit;
982+
content: "▴";
983+
}
984+
</style>
985+
"""
986+
HTML_DUMP_SORTABLE_JAVASCRIPT = """<script>
987+
window.addEventListener('DOMContentLoaded', () => {
988+
document.addEventListener('click', event => {
989+
try {
990+
const isAltSort = event.shiftKey || event.altKey;
991+
992+
// Find the clicked table header
993+
const findParentElement = (element, nodeName) =>
994+
element.nodeName === nodeName ? element : findParentElement(element.parentNode, nodeName);
995+
996+
const headerCell = findParentElement(event.target, 'TH');
997+
const headerRow = headerCell.parentNode;
998+
const thead = headerRow.parentNode;
999+
const table = thead.parentNode;
1000+
1001+
if (thead.nodeName !== 'THEAD') return;
1002+
1003+
// Reset sort indicators on other headers
1004+
Array.from(headerRow.cells).forEach(cell => {
1005+
if (cell !== headerCell) cell.removeAttribute('aria-sort');
1006+
});
1007+
1008+
// Toggle sort direction
1009+
const currentSort = headerCell.getAttribute('aria-sort');
1010+
const isAscending = table.classList.contains('asc') && currentSort !== 'ascending';
1011+
const sortDirection = (currentSort === 'descending' || isAscending) ? 'ascending' : 'descending';
1012+
headerCell.setAttribute('aria-sort', sortDirection);
1013+
1014+
// Debounce sort operation
1015+
if (table.dataset.timer) clearTimeout(Number(table.dataset.timer));
1016+
1017+
table.dataset.timer = setTimeout(() => {
1018+
sortTable(table, isAltSort);
1019+
}, 1).toString();
1020+
} catch (error) {
1021+
console.error('Sorting error:', error);
1022+
}
1023+
});
1024+
});
1025+
1026+
function sortTable(table, useAltSort) {
1027+
table.dispatchEvent(new CustomEvent('sort-start', { bubbles: true }));
1028+
1029+
const sortHeader = table.tHead.querySelector('th[aria-sort]');
1030+
const headerRow = table.tHead.children[0];
1031+
const isAscending = sortHeader.getAttribute('aria-sort') === 'ascending';
1032+
const shouldPushEmpty = table.classList.contains('n-last');
1033+
const sortColumnIndex = Number(sortHeader.dataset.sortCol ?? sortHeader.cellIndex);
1034+
1035+
const getCellValue = cell => {
1036+
if (useAltSort) return cell.dataset.sortAlt;
1037+
return cell.dataset.sort ?? cell.textContent;
1038+
};
1039+
1040+
const compareRows = (row1, row2) => {
1041+
const value1 = getCellValue(row1.cells[sortColumnIndex]);
1042+
const value2 = getCellValue(row2.cells[sortColumnIndex]);
1043+
1044+
// Handle empty values
1045+
if (shouldPushEmpty) {
1046+
if (value1 === '' && value2 !== '') return -1;
1047+
if (value2 === '' && value1 !== '') return 1;
1048+
}
1049+
1050+
// Compare numerically if possible, otherwise use string comparison
1051+
const numericDiff = Number(value1) - Number(value2);
1052+
const comparison = isNaN(numericDiff) ?
1053+
value1.localeCompare(value2, undefined, { numeric: true }) :
1054+
numericDiff;
1055+
1056+
// Handle tiebreaker
1057+
if (comparison === 0 && headerRow.cells[sortColumnIndex]?.dataset.sortTbr) {
1058+
const tiebreakIndex = Number(headerRow.cells[sortColumnIndex].dataset.sortTbr);
1059+
return compareRows(row1, row2, tiebreakIndex);
1060+
}
1061+
1062+
return isAscending ? -comparison : comparison;
1063+
};
1064+
1065+
// Sort each tbody
1066+
Array.from(table.tBodies).forEach(tbody => {
1067+
const rows = Array.from(tbody.rows);
1068+
const sortedRows = rows.sort(compareRows);
1069+
1070+
const newTbody = tbody.cloneNode();
1071+
newTbody.append(...sortedRows);
1072+
tbody.replaceWith(newTbody);
1073+
});
1074+
1075+
table.dispatchEvent(new CustomEvent('sort-end', { bubbles: true }));
1076+
}
1077+
</script>"""
9441078
# Leaving (dirty) possibility to change values from here (e.g. `export SQLMAP__MAX_NUMBER_OF_THREADS=20`)
9451079
for key, value in os.environ.items():
9461080
if key.upper().startswith("%s_" % SQLMAP_ENVIRONMENT_PREFIX):

lib/parse/cmdline.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -674,7 +674,7 @@ def cmdLineParser(argv=None):
674674
help="Store dumped data to a custom file")
675675

676676
general.add_argument("--dump-format", dest="dumpFormat",
677-
help="Format of dumped data (CSV (default), HTML or SQLITE)")
677+
help="Format of dumped data (CSV (default), HTML, SORTABLE_HTML or SQLITE)")
678678

679679
general.add_argument("--encoding", dest="encoding",
680680
help="Character encoding used for data retrieval (e.g. GBK)")

sqlmap.conf

+4-2
Original file line numberDiff line numberDiff line change
@@ -754,8 +754,10 @@ csvDel = ,
754754
dumpFile =
755755

756756
# Format of dumped data
757-
# Valid: CSV, HTML or SQLITE
758-
dumpFormat = CSV
757+
# Valid: CSV, HTML, SORTABLE_HTML or SQLITE
758+
dumpFormat = SORTABLE_HTML
759+
760+
dumpSortable = False
759761

760762
# Force character encoding used for data retrieval.
761763
encoding =

sqlmap.py

+3
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,9 @@ def main():
158158
if checkPipedInput():
159159
conf.batch = True
160160

161+
if conf.get("dumpFormat") == "SORTABLE_HTML":
162+
conf.dumpFormat = "HTML"
163+
conf.dumpSortable = True
161164
if conf.get("api"):
162165
# heavy imports
163166
from lib.utils.api import StdDbOut

0 commit comments

Comments
 (0)