Skip to content

Commit 4735308

Browse files
committed
Added JSON start/end detection when parsing
1 parent ac1b5b4 commit 4735308

File tree

4 files changed

+85
-29
lines changed

4 files changed

+85
-29
lines changed

src/Pages/Home/JwtDecoder/JwtDecoder.cshtml

Lines changed: 77 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@
344344
}
345345
else
346346
{
347-
@:await showDecodedJwt(null, null, null, ' ');
347+
@:await showDecodedJwt(null, null, '', '', ' ');
348348
}
349349
}
350350
@@ -356,24 +356,26 @@
356356
};
357357
358358
// Clear previous state before attempting to parse a new JWT
359-
await showDecodedJwt(null, null, null, ' ');
359+
await showDecodedJwt(null, null, '', '', ' ');
360360
361361
const jwt = $(this).text();
362362
if (jwt) {
363363
try {
364364
const parts = jwt.indexOf('.') === -1 ? [jwt] : jwt.split('.');
365-
const header = parseJwtPart(parts, 0);
366-
const payload = parseJwtPart(parts, 1);
365+
const headerInfo = parseJwtPart(parts, 0);
366+
const payloadInfo = parseJwtPart(parts, 1);
367367
const signature = parts.length > 2 ? parts[2] : '';
368368
369369
decodedJwt = {
370-
header: header,
371-
payload: payload,
370+
header: headerInfo.length > 0 ? headerInfo[0] : null,
371+
encodedHeader: headerInfo.length > 1 ? headerInfo[1] : '',
372+
payload: payloadInfo.length > 0 ? payloadInfo[0] : null,
373+
encodedPayload: payloadInfo.length > 1 ? payloadInfo[1] : '',
372374
signature: signature
373375
};
374376
375-
await showDecodedJwt(parts, header, payload, signature);
376-
colorJwtInput($(this), parts);
377+
await showDecodedJwt(decodedJwt.header, decodedJwt.payload, decodedJwt.encodedHeader, decodedJwt.encodedPayload, signature);
378+
colorJwtInput($(this), parts, decodedJwt.encodedHeader, decodedJwt.encodedPayload, decodedJwt.signature);
377379
378380
if (parts.length !== 3) {
379381
showError('Invalid JWT format. A JWT should have three parts separated by dots.');
@@ -382,46 +384,95 @@
382384
showError('Error decoding JWT: ' + e.message);
383385
}
384386
} else {
385-
await showDecodedJwt(null, null, null, ' ');
387+
await showDecodedJwt(null, null, '', '', ' ');
386388
}
387389
}
388390
389391
function parseJwtPart(parts, index) {
390392
if (index < 0 || index >= parts.length) {
391-
return null;
393+
return [];
392394
}
393395
394396
const part = parts[index];
395397
if (!part) {
396-
return null;
398+
return [];
399+
}
400+
401+
// Find the base64 marker for a JSON object to start parsing
402+
let jsonStartPos = 0;
403+
while (jsonStartPos < part.length && part.substring(jsonStartPos, jsonStartPos + 3) !== 'eyJ') {
404+
jsonStartPos++;
405+
}
406+
if (jsonStartPos >= part.length) {
407+
return []; // No JSON object found
408+
}
409+
410+
// Find the base64 marker for a JSON object to end parsing ('In0' for '"}' or 'fQ' for '}')
411+
let jsonEndPos = part.length;
412+
while (jsonEndPos > jsonStartPos && (part.substring(jsonEndPos - 3, jsonEndPos) !== 'In0') && (part.substring(jsonEndPos - 2, jsonEndPos) !== 'fQ')) {
413+
jsonEndPos--;
414+
}
415+
if (jsonEndPos <= jsonStartPos) {
416+
return []; // No valid JSON object found
397417
}
398418
399419
try {
400-
const decodedPart = decodeBase64UrlSafe(part);
401-
return JSON.parse(decodedPart);
420+
const encodedPart = part.substring(jsonStartPos, jsonEndPos);
421+
const decodedPart = decodeBase64UrlSafe(encodedPart);
422+
return [JSON.parse(decodedPart), encodedPart];
402423
}
403424
catch {
404425
405426
}
406427
407-
return null;
428+
return [];
408429
}
409430
410-
function colorJwtInput(target, parts) {
431+
function colorJwtInput(target, originalParts, encodedHeader, encodedPayload, signature) {
411432
let html = '';
412-
if (parts.length > 0) {
413-
html += `<span class="text-danger">${parts[0] || ''}</span>`;
433+
if (encodedHeader) {
434+
const originalHeader = originalParts[0];
435+
if (originalHeader === encodedHeader) {
436+
html += `<span class="text-danger">${encodedHeader}</span>`;
437+
}
438+
else {
439+
// Show the additional characters before and after the "encodedHeader" part that are found in originalHeader
440+
const start = originalHeader.indexOf(encodedHeader);
441+
const end = start + encodedHeader.length;
442+
if (start > 0) {
443+
html += `<span class="skipped">${originalHeader.substring(0, start)}</span>`;
444+
}
445+
html += `<span class="text-danger">${encodedHeader}</span>`;
446+
if (end < originalHeader.length) {
447+
html += `<span class="skipped">${originalHeader.substring(end)}</span>`;
448+
}
449+
}
414450
}
415-
if (parts.length > 1) {
416-
html += `<span class="text-success">.${parts[1] || ''}</span>`;
451+
if (encodedPayload) {
452+
const originalPayload = originalParts[1];
453+
if (originalPayload === encodedPayload) {
454+
html += '<span class="jwt-divider">.</span>' + `<span class="text-success">${encodedPayload}</span>`;
455+
}
456+
else {
457+
// Show the additional characters before and after the "encodedPayload" part that are found in originalPayload
458+
const start = originalPayload.indexOf(encodedPayload);
459+
const end = start + encodedPayload.length;
460+
if (start > 0) {
461+
html += `<span class="skipped">${originalPayload.substring(0, start)}</span>`;
462+
}
463+
html += '<span class="jwt-divider">.</span>' + `<span class="text-success">${encodedPayload}</span>`;
464+
if (end < originalPayload.length) {
465+
html += `<span class="skipped">${originalPayload.substring(end)}</span>`;
466+
}
467+
}
417468
}
418-
if (parts.length > 2) {
419-
html += `<span class="text-warning">.${parts.slice(2).join('.') || ''}</span>`;
469+
if (signature) {
470+
html += '<span class="jwt-divider">.</span>' + `<span class="text-warning">${signature}</span>`;
420471
}
421472
target.html(html);
422473
}
423474
424-
async function showDecodedJwt(jwtParts, header, payload, signature) {
475+
async function showDecodedJwt(header, payload, encodedHeader, encodedPayload, signature) {
425476
clearError();
426477
hideSignatureValidationResults();
427478
@@ -430,8 +481,8 @@
430481
431482
$('#jwt-signature').text(signature || ' ');
432483
433-
if (jwtParts && Array.isArray(jwtParts) && jwtParts.length === 3) {
434-
await attemptSignatureValidation(header, payload, jwtParts);
484+
if (encodedHeader && encodedPayload && signature) {
485+
await attemptSignatureValidation(header, payload, encodedHeader, encodedPayload, signature);
435486
}
436487
}
437488
@@ -548,7 +599,7 @@
548599
return contents;
549600
}
550601
551-
async function attemptSignatureValidation(header, payload, jwtParts) {
602+
async function attemptSignatureValidation(header, payload, encodedHeader, encodedPayload, signature) {
552603
const jwksUrlField = $('#jwks-url');
553604
554605
const isPristine = jwksUrlField.data('pristine') !== false;
@@ -562,9 +613,7 @@
562613
if (jwksUrl) {
563614
await loadJwks(jwksUrl);
564615
565-
const headerAndPayload = jwtParts[0] + '.' + jwtParts[1];
566-
const signature = jwtParts[2];
567-
616+
const headerAndPayload = encodedHeader + '.' + encodedPayload;
568617
if (jwks.keys.length === 0) {
569618
if (signature) {
570619
showSignatureValidationResult('warning', 'The JWT has a signature, but no JWKs could be loaded to verify whether the signature is valid.');

src/wwwroot/css/site.css

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,9 @@ a.navbar-brand .icon-banner {
138138
.jwt-decoder-container .jwt-input-editable .text-warning {
139139
color: #86c0c5 !important;
140140
}
141+
.jwt-decoder-container .jwt-input-editable .skipped {
142+
text-decoration: line-through;
143+
}
141144
.jwt-decoder-container .json-content, .jwt-decoder-container .jwt-input-editable {
142145
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
143146
font-size: 1em;

src/wwwroot/css/site.min.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/wwwroot/css/site.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,10 @@ a.navbar-brand {
158158
.text-warning {
159159
color: #86c0c5 !important;
160160
}
161+
162+
.skipped {
163+
text-decoration: line-through;
164+
}
161165
}
162166

163167
.json-content, .jwt-input-editable {

0 commit comments

Comments
 (0)