Skip to content

Added ability to add sortable tables to html dump. #5852

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

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
10 changes: 5 additions & 5 deletions data/txt/sha256sums.txt
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,8 @@ e050353f74c0baaf906ffca91dd04591645455ae363ae732a7a23f91ffe2ef1c lib/core/datat
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
bebff48927ffcba57f7d813819a7f6dda527e495f342133d345449a63cef0c4f lib/core/settings.py
c5330eda58fb86adbe1ff2c2562b3b24d61d20758c51fa1ccbb87d7dc30e4693 lib/core/settings.py
a1e4f2860bffc73bbf2e5db293fa49dcb600ea35f950cda43dc953b3160ab3db lib/core/shell.py
841716e87b90a3b598515910841f7cf8d33bb87c24a27fba1a80e36a831cbcd7 lib/core/subprocessng.py
9731092f195e346716929323ea3c93247b23b9b92b0f32d3fd0acc3adf9876cc lib/core/target.py
Expand Down Expand Up @@ -476,8 +476,8 @@ b3d9d0644197ecb864e899c04ee9c7cd63891ecf2a0d3c333aad563eef735294 plugins/generi
5a473c60853f54f1a4b14d79b8237f659278fe8a6b42e935ed573bf22b6d5b2c README.md
8c4fd81d84598535643cf0ef1b2d350cd92977cb55287e23993b76eaa2215c30 sqlmapapi.py
168309215af7dd5b0b71070e1770e72f1cbb29a3d8025143fb8aa0b88cd56b62 sqlmapapi.yaml
4121621b1accd6099eed095e9aa48d6db6a4fdfa3bbc5eb569d54c050132cbbf sqlmap.conf
f84846b8493d809d697a75b3d13d904013bbb03e0edd82b724f4753801609057 sqlmap.py
a6940fa9380fe644d9fae9e41705576fc540f57414f22c7ec89a1017359f1eb7 sqlmap.conf
9f78d6f04525cf2d2978f210d40663df9757450669d9685bf43931e3b11059a8 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 @@ -921,29 +921,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
3 changes: 2 additions & 1 deletion sqlmap.conf
Original file line number Diff line number Diff line change
Expand Up @@ -758,9 +758,10 @@ 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