Skip to content

Commit

Permalink
Added ability to add sortable tables to html dump.
Browse files Browse the repository at this point in the history
  • Loading branch information
tanaydin committed Feb 19, 2025
1 parent fa9dc20 commit d41073c
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 21 deletions.
14 changes: 7 additions & 7 deletions data/txt/sha256sums.txt
Original file line number Diff line number Diff line change
Expand Up @@ -166,16 +166,16 @@ de2b0220db1c79d8720b636d267b11e117151f5f99740567096e9b4cbb7cc9d5 lib/controller
1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/controller/__init__.py
41c7fb7e486c4383a114c851f0c32c81c53c2b4f1d2a0fd99f70885072646387 lib/core/agent.py
f848dcfdacb5143f803f4e9474cf3eef939039c26c522ca09777c425661300f0 lib/core/bigarray.py
eaf9d2d47305764213ada74b7a83721fc5f49578f2d8afa78799855068acb416 lib/core/common.py
fcb9253a42ee420344d147471e00919f0baa5b5e6596edd3f9b743015ea90aed lib/core/common.py
88fbbe7c41511b17d7ef449d675a84eaa80cac6ebf457a18577eadd62f6f1330 lib/core/compat.py
5ce8f2292f99d17d69bfc40ded206bfdfd06e2e3660ff9d1b3c56163793f8d1c lib/core/convert.py
f561310b3cea570cc13d9f0aff16cce8b097d51275f8b947e7fff4876ac65c32 lib/core/data.py
e050353f74c0baaf906ffca91dd04591645455ae363ae732a7a23f91ffe2ef1c lib/core/datatype.py
bdd1b5b3eb42cffdc1be78b8fe4e1bb2ec17cd86440a7aeb08fc599205089e94 lib/core/decorators.py
9219f0bd659e4e22f4238ca67830adcb1e86041ce7fd3a8ae0e842f2593ae043 lib/core/defaults.py
ec8d94fb704c0a40c88f5f283624cda025e2ea0e8b68722fe156c2b5676f53ac lib/core/dicts.py
65fb5a2fc7b3bb502cc2db684370f213ab76bff875f3cf72ef2b9ace774efda9 lib/core/dump.py
0e28c66ea9dfa1b721cfca63c364bdc139f53ebc8f9c57126b0af7dc6b433dcc lib/core/enums.py
2070b406f123e4cc2b0015d125947c48b5f9afcb976ffaba9534841d30325310 lib/core/dump.py
d653ec01dfa47ee93d2ffe53b1ab76b3a4fb649f517f9f6572a38186882e0255 lib/core/enums.py
64bf6a5c2e456306a7b4f4c51f077412daf6c697fed232d8e23b77fd1a4c736e lib/core/exception.py
93c256111dc753967169988e1289a0ea10ec77bfb8e2cbd1f6725e939bfbc235 lib/core/gui.py
1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/core/__init__.py
Expand All @@ -188,7 +188,7 @@ c6a182f6b7d3b0ad6f0888ea2a4de4148f0770549038d7de8bc3267b4c6635f7 lib/core/readl
63ae69713c6ea9abfa10e71dfab8f2dcf42432177a38d2c1e98785bf1468674c lib/core/replication.py
5bad5bc7115051cef7b84efa73fbafbf5e1db46eef32a445056b56cda750b66f lib/core/revision.py
0dcb52c9c76a4b0acf2e9038f7d8f08c14543cef3cf7032831c6c0a99376ad24 lib/core/session.py
167941c1f7c279d31a377a80915de0cae31f06ba39bf802571a9980bb5ffbfff lib/core/settings.py
0b20a25f15a265cfb9a0d24d981925161926bb665addde38270216f67eb5e09a lib/core/settings.py
a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py
841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py
9731092f195e346716929323ea3c93247b23b9b92b0f32d3fd0acc3adf9876cc lib/core/target.py
Expand All @@ -199,7 +199,7 @@ b1071f449a66b4ceacd4b84b33a73d9e0a3197d271d72daaa406ba473a8bb625 lib/core/testi
12cbead4e9e563b970fafb891127927445bd53bada1fac323b9cd27da551ba30 lib/core/wordlist.py
1d6e741e19e467650dce2ca84aa824d6df68ff74aedbe4afa8dbdb0193d94918 lib/__init__.py
a027f4c44811cb74aa367525f353706de3d3fc719e6c6162f7a61dc838acf0c2 lib/parse/banner.py
9c7f95948cb6ee20b2b5bff7b36c23179c44303d3c8ad555247f65f12f30e0a9 lib/parse/cmdline.py
cc2029f30897446849980c5ffba05a2ec18ae7b2360e3829318c91ddc8b9e678 lib/parse/cmdline.py
3907765df08c31f8d59350a287e826bd315a7714dc0e87496f67c8a0879c86ac lib/parse/configfile.py
ced03337edd5a16b56a379c9ac47775895e1053003c25f6ba5bec721b6e3aa64 lib/parse/handler.py
3704a02dcf00b0988b101e30b2e0d48acdd20227e46d8b552e46c55d7e9bf28c lib/parse/headers.py
Expand Down Expand Up @@ -476,8 +476,8 @@ b3d9d0644197ecb864e899c04ee9c7cd63891ecf2a0d3c333aad563eef735294 plugins/generi
5a473c60853f54f1a4b14d79b8237f659278fe8a6b42e935ed573bf22b6d5b2c README.md
8c4fd81d84598535643cf0ef1b2d350cd92977cb55287e23993b76eaa2215c30 sqlmapapi.py
168309215af7dd5b0b71070e1770e72f1cbb29a3d8025143fb8aa0b88cd56b62 sqlmapapi.yaml
6da15963699aa8916118f92c8838013bc02c84e4d7b9f33d971324c2ff348728 sqlmap.conf
3795c6d03bc341a0e3aef3d7990ea8c272d91a4c307e1498e850594375af39f7 sqlmap.py
909a47352f550dd7162e2fc6ef9c5455cc97f8f3cf88423921e8e7f58f510705 sqlmap.conf
da9a10876be84df270aee48a5faa39050603c9c79b317ef469ff49381993f649 sqlmap.py
9d408612a6780f7f50a7f7887f923ff3f40be5bfa09a951c6dc273ded05b56c0 tamper/0eunion.py
c1c2eaa7df016cc7786ccee0ae4f4f363b1dce139c61fb3e658937cb0d18fc54 tamper/apostrophemask.py
19023093ab22aec3bce9523f28e8111e8f6125973e6d9c82adb60da056bdf617 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
4 changes: 3 additions & 1 deletion sqlmap.conf
Original file line number Diff line number Diff line change
Expand Up @@ -754,9 +754,11 @@ csvDel = ,
dumpFile =

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

dumpSortable = False

# Force character encoding used for data retrieval.
encoding =

Expand Down
6 changes: 6 additions & 0 deletions sqlmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,12 @@ 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

0 comments on commit d41073c

Please sign in to comment.