Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding ability to dump sortable tables #5821

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions data/txt/sha256sums.txt
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,8 @@ b60c96780cad4a257f91a0611b08cfcc52f242908c5d5ab2bf9034ef07869602 lib/core/conve
55e7d63aae317763afcbdbea1c7731497c93bad14f6d032a0ccfffe72ffc121f lib/core/decorators.py
595c7dfde7c67cdb674fb019a24b07a501a9cdb6321e4f8ce3d3354cd9526eae lib/core/defaults.py
e8f6f1df8814b7b03c3eba22901837555083f66c99ee93b943911de785736bfa lib/core/dicts.py
5fb6ef1772580a701b1b109858163a1c16446928f8c29170d67ad4d0171c0950 lib/core/dump.py
874c8eb7391ef0f82b6e870499daa336a79a6d014a23e7452205f5ef0b6a9744 lib/core/enums.py
2c611c19a3c38b755a6e070a2c96ce6ac9a84123640b66bdb0a1c4a2d4ac6abf lib/core/dump.py
3f438ad1ddfb3e7767837590bd0cedbf2af8a6f21c28603f62236b5623e42d04 lib/core/enums.py
67ab7a8f756b63e75e8b564d647e72362d7245d6b32b2881be02321ceaaca876 lib/core/exception.py
0379d59be9e2400e39abbb99fbceeb22d4c3b69540504a0cb59bf3aaf53d05a9 lib/core/gui.py
99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/core/__init__.py
Expand All @@ -188,7 +188,7 @@ bf77f9fc4296f239687297aee1fd6113b34f855965a6f690b52e26bd348cb353 lib/core/profi
4eff81c639a72b261c8ba1c876a01246e718e6626e8e77ae9cc6298b20a39355 lib/core/replication.py
bbd1dcda835934728efc6d68686e9b0da72b09b3ee38f3c0ab78e8c18b0ba726 lib/core/revision.py
eed6b0a21b3e69c5583133346b0639dc89937bd588887968ee85f8389d7c3c96 lib/core/session.py
5072218a58696b0bd425421022e557da29b32a54ea181686c83b4130b6edf1ee lib/core/settings.py
fbe9bfd62846770aa87133b7eddb278da5959411fc47f5261d227f381492f609 lib/core/settings.py
2bec97d8a950f7b884e31dfe9410467f00d24f21b35672b95f8d68ed59685fd4 lib/core/shell.py
e90a359b37a55c446c60e70ccd533f87276714d0b09e34f69b0740fd729ddbf8 lib/core/subprocessng.py
54f7c70b4c7a9931f7ff3c1c12030180bde38e35a306d5e343ad6052919974cd lib/core/target.py
Expand All @@ -199,7 +199,7 @@ ff39235aee7e33498c66132d17e6e86e7b8a29754e3fdecd880ca8356b17f791 lib/core/unesc
ce65f9e8e1c726de3cec6abf31a2ffdbc16c251f772adcc14f67dee32d0f6b57 lib/core/wordlist.py
99d0e94dd5fe60137abf48bfa051129fb251f5c40f0f7a270c89fbcb07323730 lib/__init__.py
ba16fdd71fba31990dc92ff5a7388fb0ebac21ca905c314be6c8c2b868f94ab7 lib/parse/banner.py
d757343f241b14e23aefb2177b6c2598f1bc06253fd93b0d8a28d4a55c267100 lib/parse/cmdline.py
8347cdcc053b3a08518283b6cf41a1abe72cfe0b34c1bc6de45ce7ca43ec0de5 lib/parse/cmdline.py
d1fa3b9457f0e934600519309cbd3d84f9e6158a620866e7b352078c7c136f01 lib/parse/configfile.py
9af4c86e41e50bd6055573a7b76e380a6658b355320c72dd6d2d5ddab14dc082 lib/parse/handler.py
13b3ab678a2c422ce1dea9558668c05e562c0ec226f36053259a0be7280ebf92 lib/parse/headers.py
Expand Down Expand Up @@ -476,8 +476,8 @@ fff84edc86b7d22dc01148fb10bb43d51cb9638dff21436fb94555db2a664766 plugins/generi
5a473c60853f54f1a4b14d79b8237f659278fe8a6b42e935ed573bf22b6d5b2c README.md
78aafd53980096364f0c995c6283931bff505aed88fed1e7906fb06ee60e9c5b sqlmapapi.py
168309215af7dd5b0b71070e1770e72f1cbb29a3d8025143fb8aa0b88cd56b62 sqlmapapi.yaml
5e172e315524845fe091aa0b7b29303c92ac8f67594c6d50f026d627e415b7ed sqlmap.conf
3a18b78b1aaf7236a35169db20eb21ca7d7fb907cd38dd34650f1da81c010cd6 sqlmap.py
c94e1a4832ae6ef786431acb595428736bf13e2933c147d8bc70ba6913d65cbb sqlmap.conf
56b17e8239000043d375d56f724baa7135f8b106439a7d10024bba746fb26d01 sqlmap.py
adda508966db26c30b11390d6483c1fa25b092942a29730e739e1e50c403a21f tamper/0eunion.py
d38fe5ab97b401810612eae049325aa990c55143504b25cc9924810917511dee tamper/apostrophemask.py
8de713d1534d8cda171db4ceeb9f4324bcc030bbef21ffeaf60396c6bece31e4 tamper/apostrophenullencode.py
Expand Down
5 changes: 5 additions & 0 deletions lib/core/dump.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@
from lib.core.replication import Replication
from lib.core.settings import DUMP_FILE_BUFFER_SIZE
from lib.core.settings import HTML_DUMP_CSS_STYLE
from lib.core.settings import HTML_DUMP_CSS_SORTABLE_STYLE
from lib.core.settings import HTML_DUMP_SORTABLE_JAVASCRIPT
from lib.core.settings import IS_WIN
from lib.core.settings import METADB_SUFFIX
from lib.core.settings import MIN_BINARY_DISK_DUMP_SIZE
Expand Down Expand Up @@ -541,6 +543,9 @@ def dbTableValues(self, tableValues):
dataToDumpFile(dumpFP, "<meta name=\"generator\" content=\"%s\" />\n" % VERSION_STRING)
dataToDumpFile(dumpFP, "<title>%s</title>\n" % ("%s%s" % ("%s." % db if METADB_SUFFIX not in db else "", table)))
dataToDumpFile(dumpFP, HTML_DUMP_CSS_STYLE)
if conf.dumpSortable:
dataToDumpFile(dumpFP, HTML_DUMP_CSS_SORTABLE_STYLE)
dataToDumpFile(dumpFP, HTML_DUMP_SORTABLE_JAVASCRIPT)
dataToDumpFile(dumpFP, "\n</head>\n<body>\n<table>\n<thead>\n<tr>\n")

if count == 1:
Expand Down
1 change: 1 addition & 0 deletions lib/core/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ class REGISTRY_OPERATION(object):
class DUMP_FORMAT(object):
CSV = "CSV"
HTML = "HTML"
SORTABLE_HTML = "SORTABLE_HTML"
SQLITE = "SQLITE"

class HTTP_HEADER(object):
Expand Down
160 changes: 147 additions & 13 deletions lib/core/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -918,29 +918,163 @@

# CSS style used in HTML dump format
HTML_DUMP_CSS_STYLE = """<style>
table{
margin:10;
background-color:#FFFFFF;
font-family:verdana;
font-size:12px;
align:center;
table {
margin: 10px;
background: #fff;
font: 12px verdana;
text-align: center;
}
thead{
font-weight:bold;
background-color:#4F81BD;
color:#FFFFFF;
color: #fff;
}
tr:nth-child(even) {
background-color: #D3DFEE
background-color: #D3DFEE;
}
td{
font-size:12px;
</style>"""

HTML_DUMP_CSS_SORTABLE_STYLE = """
<style>
table thead th {
cursor: pointer;
white-space: nowrap;
position: sticky;
top: 0;
z-index: 1;
}
th{
font-size:12px;
table thead th::after,
table thead th::before {
color: transparent;
}
table thead th::after {
margin-left: 3px;
content: "▸";
}
table thead th:hover::after,
table thead th[aria-sort]::after {
color: inherit;
}
table thead th[aria-sort=descending]::after {
content: "▾";
}
</style>"""
table thead th[aria-sort=ascending]::after {
content: "▴";
}
table thead th.indicator-left::before {
margin-right: 3px;
content: "▸";
}
table thead th.indicator-left[aria-sort=descending]::before {
color: inherit;
content: "▾";
}
table thead th.indicator-left[aria-sort=ascending]::before {
color: inherit;
content: "▴";
}
</style>
"""
HTML_DUMP_SORTABLE_JAVASCRIPT = """<script>
window.addEventListener('DOMContentLoaded', () => {
document.addEventListener('click', event => {
try {
const isAltSort = event.shiftKey || event.altKey;
// Find the clicked table header
const findParentElement = (element, nodeName) =>
element.nodeName === nodeName ? element : findParentElement(element.parentNode, nodeName);
const headerCell = findParentElement(event.target, 'TH');
const headerRow = headerCell.parentNode;
const thead = headerRow.parentNode;
const table = thead.parentNode;
if (thead.nodeName !== 'THEAD') return;
// Reset sort indicators on other headers
Array.from(headerRow.cells).forEach(cell => {
if (cell !== headerCell) cell.removeAttribute('aria-sort');
});
// Toggle sort direction
const currentSort = headerCell.getAttribute('aria-sort');
const isAscending = table.classList.contains('asc') && currentSort !== 'ascending';
const sortDirection = (currentSort === 'descending' || isAscending) ? 'ascending' : 'descending';
headerCell.setAttribute('aria-sort', sortDirection);
// Debounce sort operation
if (table.dataset.timer) clearTimeout(Number(table.dataset.timer));
table.dataset.timer = setTimeout(() => {
sortTable(table, isAltSort);
}, 1).toString();
} catch (error) {
console.error('Sorting error:', error);
}
});
});
function sortTable(table, useAltSort) {
table.dispatchEvent(new CustomEvent('sort-start', { bubbles: true }));
const sortHeader = table.tHead.querySelector('th[aria-sort]');
const headerRow = table.tHead.children[0];
const isAscending = sortHeader.getAttribute('aria-sort') === 'ascending';
const shouldPushEmpty = table.classList.contains('n-last');
const sortColumnIndex = Number(sortHeader.dataset.sortCol ?? sortHeader.cellIndex);
const getCellValue = cell => {
if (useAltSort) return cell.dataset.sortAlt;
return cell.dataset.sort ?? cell.textContent;
};
const compareRows = (row1, row2) => {
const value1 = getCellValue(row1.cells[sortColumnIndex]);
const value2 = getCellValue(row2.cells[sortColumnIndex]);
// Handle empty values
if (shouldPushEmpty) {
if (value1 === '' && value2 !== '') return -1;
if (value2 === '' && value1 !== '') return 1;
}
// Compare numerically if possible, otherwise use string comparison
const numericDiff = Number(value1) - Number(value2);
const comparison = isNaN(numericDiff) ?
value1.localeCompare(value2, undefined, { numeric: true }) :
numericDiff;
// Handle tiebreaker
if (comparison === 0 && headerRow.cells[sortColumnIndex]?.dataset.sortTbr) {
const tiebreakIndex = Number(headerRow.cells[sortColumnIndex].dataset.sortTbr);
return compareRows(row1, row2, tiebreakIndex);
}
return isAscending ? -comparison : comparison;
};
// Sort each tbody
Array.from(table.tBodies).forEach(tbody => {
const rows = Array.from(tbody.rows);
const sortedRows = rows.sort(compareRows);
const newTbody = tbody.cloneNode();
newTbody.append(...sortedRows);
tbody.replaceWith(newTbody);
});
table.dispatchEvent(new CustomEvent('sort-end', { bubbles: true }));
}
</script>"""
# Leaving (dirty) possibility to change values from here (e.g. `export SQLMAP__MAX_NUMBER_OF_THREADS=20`)
for key, value in os.environ.items():
if key.upper().startswith("%s_" % SQLMAP_ENVIRONMENT_PREFIX):
Expand Down
2 changes: 1 addition & 1 deletion lib/parse/cmdline.py
Original file line number Diff line number Diff line change
Expand Up @@ -674,7 +674,7 @@ def cmdLineParser(argv=None):
help="Store dumped data to a custom file")

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

general.add_argument("--encoding", dest="encoding",
help="Character encoding used for data retrieval (e.g. GBK)")
Expand Down
6 changes: 4 additions & 2 deletions sqlmap.conf
Original file line number Diff line number Diff line change
Expand Up @@ -754,8 +754,10 @@ csvDel = ,
dumpFile =

# Format of dumped data
# Valid: CSV, HTML or SQLITE
dumpFormat = CSV
# Valid: CSV, HTML, SORTABLE_HTML or SQLITE
dumpFormat = SORTABLE_HTML

dumpSortable = False

# Force character encoding used for data retrieval.
encoding =
Expand Down
5 changes: 5 additions & 0 deletions sqlmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,11 @@ def main():
if checkPipedInput():
conf.batch = True

if conf.get("dumpFormat") == "SORTABLE_HTML":
conf.dumpFormat = "HTML"
conf.dumpSortable = True
else:
conf.dumpSortable = False
if conf.get("api"):
# heavy imports
from lib.utils.api import StdDbOut
Expand Down