Skip to content
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
4 changes: 2 additions & 2 deletions examples/rss-reader/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ function decodeEntities(value: string): string {

return value.replace(/&(#x?[0-9a-fA-F]+|[a-zA-Z]+);/g, (match, entity: string) => {
if (entity.startsWith('#x')) {
const codePoint = Number.parseInt(entity.slice(2), 16);
const codePoint = Number.parseInt(entity.slice(2, 10), 16);
return Number.isFinite(codePoint) ? String.fromCodePoint(codePoint) : match;
}

if (entity.startsWith('#')) {
const codePoint = Number.parseInt(entity.slice(1), 10);
const codePoint = Number.parseInt(entity.slice(1, 10), 10);
Comment on lines +30 to +35

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

node <<'NODE'
for (const value of [0x110000, 1285120]) {
  try {
    String.fromCodePoint(value);
    console.error(`Unexpected success for ${value}`);
    process.exitCode = 1;
  } catch (error) {
    console.log(`Correctly rejected ${value}: ${error.constructor.name}`);
  }
}
NODE

Repository: Karanjot786/TermUI

Length of output: 234


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate file =="
git ls-files | grep -Fx 'examples/rss-reader/src/index.tsx' || true

echo "== relevant source =="
if [ -f examples/rss-reader/src/index.tsx ]; then
  nl -ba examples/rss-reader/src/index.tsx | sed -n '1,80p'
fi

echo "== deterministic parser slice behavior =="
node <<'NODE'
function decodeEntitiesCurrent(body) {
  return body.replace(/&(#?[0-9a-fA-F]{1,10});?/g, (_match, entity) => {
    if (entity.startsWith('`#x`')) {
      const codePoint = Number.parseInt(entity.slice(2, 10), 16);
      return Number.isFinite(codePoint) ? String.fromCodePoint(codePoint) : _match;
    }
    if (entity.startsWith('#')) {
      const codePoint = Number.parseInt(entity.slice(1, 10), 10);
      return Number.isFinite(codePoint) ? String.fromCodePoint(codePoint) : _match;
    }
    return _match;
  });
}

const cases = [
  ['&`#0000000000000041`;', 'hex long'],
  ['&`#0000000000007fff`;', 'hex long near max'],
  ['&`#0000000000010ffff`;', 'unicode max extra digits'],
  ['&`#00000000000110000`;', 'above unicode max extra digits'],
  ['&`#x0000000000000041`;', 'dec long'],
  ['&`#x0000000000007fff`;', 'dec long near max'],
  ['&`#x0000000000010ffff`;', 'unicode max extra digits dec'],
  ['&`#x00000000000110000`;', 'above unicode max extra digits dec'],
  ['&`#1114112`;', '1114112 7 digits exact'],
  ['&`#x110000`;', 'hex above max 7 digits exact'],
  ['&`#1285120`;', '1285120 7 digits exact'],
  ['&`#0000000000000041`;', 'dec long'],
  ['&`#0000000000000001`;', 'one with many leading zeros'],
];

for (const input of cases.flat()) {
  const body = input;
  try {
    console.log(`${input} -> ${JSON.stringify(decodeEntitiesCurrent(body))}`);
  } catch (error) {
    console.log(`${input} -> ERROR ${error.constructor.name}: ${error.message}`);
  }
}
NODE

Repository: Karanjot786/TermUI

Length of output: 270


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file exists/size =="
test -f examples/rss-reader/src/index.tsx && wc -l examples/rss-reader/src/index.tsx

echo "== relevant source =="
awk 'NR>=1 && NR<=90 { printf "%6d  %s\n", NR, $0 }' examples/rss-reader/src/index.tsx

echo "== deterministic parser slice behavior =="
node <<'NODE'
function decodeEntitiesCurrent(body) {
  return body.replace(/&(#?[0-9a-fA-F]{1,10});?/g, (_match, entity) => {
    if (entity.startsWith('`#x`')) {
      const codePoint = Number.parseInt(entity.slice(2, 10), 16);
      return Number.isFinite(codePoint) ? String.fromCodePoint(codePoint) : _match;
    }
    if (entity.startsWith('#')) {
      const codePoint = Number.parseInt(entity.slice(1, 10), 10);
      return Number.isFinite(codePoint) ? String.fromCodePoint(codePoint) : _match;
    }
    return _match;
  });
}

const cases = [
  ['&`#0000000000000041`;', 'dec long 41'],
  ['&`#x0000000000000041`;', 'hex long 41'],
  ['&`#x0000000000007fff`;', 'hex long near max'],
  ['&`#x0000000000010ffff`;', 'unicode max extra digits hex'],
  ['&`#x00000000000110000`;', 'above unicode max extra digits hex'],
  ['&`#x110000`;', 'hex above max exact 7 digits'],
  ['&`#x0110000`;', 'hex above max alternate exact 7 digits'],
  ['&`#1114112`;', '1114112 7 digits exact'],
  ['&`#1285120`;', '1285120 7 digits exact'],
  ['&`#0000000000000001`;', 'one with many leading zeros'],
  ['&`#9999999999999`;', '9 trillion digits slice'],
];

for (const input of cases) {
  const body = input;
  try {
    console.log(`${input} -> ${JSON.stringify(decodeEntitiesCurrent(body))}`);
  } catch (error) {
    console.log(`${input} -> ERROR ${error.constructor.name}: ${error.message}`);
  }
}
NODE

Repository: Karanjot786/TermUI

Length of output: 4684


🏁 Script executed:

#!/bin/bash
set -euo pipefail

node <<'NODE'
const decodeEntitiesCurrent = body =>
  body.replace(/&(`#x`?[0-9a-fA-F]+|[a-zA-Z]+);/g, (match, entity) => {
    if (entity.startsWith('`#x`')) {
      const codePoint = Number.parseInt(entity.slice(2, 10), 16);
      return Number.isFinite(codePoint) ? String.fromCodePoint(codePoint) : match;
    }
    if (entity.startsWith('#')) {
      const codePoint = Number.parseInt(entity.slice(1, 10), 10);
      return Number.isFinite(codePoint) ? String.fromCodePoint(codePoint) : match;
    }
    return match;
  });

const cases = [
  ['&`#0000000000000041`;', 'dec long 41'],
  ['&`#x0000000000000041`;', 'hex long 41'],
  ['&`#x0000000000007fff`;', 'hex long near max'],
  ['&`#x0000000000010ffff`;', 'unicode max extra digits hex'],
  ['&`#x00000000000110000`;', 'above unicode max extra digits hex'],
  ['&`#x110000`;', 'hex above max exact 7 digits'],
  ['&`#x0110000`;', 'hex above max alternate 7 digits'],
  ['&`#1114112`;', '7fff2 7 digits exact'],
  ['&`#1285120`;', '1285120 7 digits exact'],
  ['&`#0000000000000001`;', 'one with many leading zeros'],
  ['&`#9999999999999`;', '9 trillion digits slice'],
  ['&`#1114111`;', 'valid max extra leading zero'],
  ['&`#1114112`;', 'explicit invalid max above'],
];

for (const [input, label] of cases) {
  let decoded = '<not evaluable>';
  let err = null;
  try {
    decoded = decodeEntitiesCurrent(input);
  } catch (error) {
    err = { constructor: error.constructor.name, message: String(error.message) };
  }
  console.log(`${label.padEnd(35)} | ${input.padEnd(28)} => ${decoded}`);
  if (err) console.log(`${''.padEnd(35)} | ${''.padEnd(28)} => ERR ${err.constructor}: ${err.message}`);
}

console.log('Range validation behavior:');
for (const value of [0x110000, 1285120, 0x10ffff, 0x110001]) {
  try {
    const ch = String.fromCodePoint(value);
    console.log(`${value} -> ${JSON.stringify(ch)`);
  } catch (error) {
    console.log(`${value} -> ${error.constructor.name}: ${error.message}`);
  }
}
NODE

Repository: Karanjot786/TermUI

Length of output: 796


🏁 Script executed:

#!/bin/bash
set -euo pipefail

node <<'NODE'
const decodeEntitiesCurrent = body =>
  body.replace(/&(`#x`?[0-9a-fA-F]+|[a-zA-Z]+);/g, (match, entity) => {
    if (entity.startsWith('`#x`')) {
      const codePoint = Number.parseInt(entity.slice(2, 10), 16);
      return Number.isFinite(codePoint) ? String.fromCodePoint(codePoint) : match;
    }
    if (entity.startsWith('#')) {
      const codePoint = Number.parseInt(entity.slice(1, 10), 10);
      return Number.isFinite(codePoint) ? String.fromCodePoint(codePoint) : match;
    }
    return match;
  });

const cases = [
  ['&`#0000000000000041`;', 'dec long 41'],
  ['&`#x0000000000000041`;', 'hex long 41'],
  ['&`#x0000000000007fff`;', 'hex long near max'],
  ['&`#x0000000000010ffff`;', 'unicode max extra digits hex'],
  ['&`#x00000000000110000`;', 'above unicode max extra digits hex'],
  ['&`#x110000`;', 'hex above max exact 7 digits'],
  ['&`#x0110000`;', 'hex above max alternate 7 digits'],
  ['&`#1114112`;', '7fff2 7 digits exact'],
  ['&`#1285120`;', '1285120 7 digits exact'],
  ['&`#0000000000000001`;', 'one with many leading zeros'],
  ['&`#9999999999999`;', '9 trillion digits slice'],
  ['&`#1114111`;', 'valid max extra leading zero'],
  ['&`#1114112`;', 'explicit invalid max above'],
];

for (const [input, label] of cases) {
  let decoded = '<not evaluable>';
  let err = null;
  try {
    decoded = decodeEntitiesCurrent(input);
  } catch (error) {
    err = { constructor: error.constructor.name, message: String(error.message) };
  }
  console.log(`${label.padEnd(35)} | ${input.padEnd(28)} => ${decoded}`);
  if (err) console.log(`${''.padEnd(35)} | ${''.padEnd(28)} => ERR ${err.constructor}: ${err.message}`);
}

console.log('Range validation behavior:');
for (const value of [0x110000, 1285120, 0x10ffff, 0x110001]) {
  try {
    console.log(`${value} -> ${JSON.stringify(String.fromCodePoint(value))}`);
  } catch (error) {
    console.log(`${value} -> ${error.constructor.name}: ${error.message}`);
  }
}
NODE

Repository: Karanjot786/TermUI

Length of output: 2050


Reject invalid or overlong numeric entities instead of slicing or validating too late.

decodeEntities can throw for hex/decimal references over Unicode’s valid range, and longer references are silently truncated to the wrong character. Parse the full numeric digits, reject values outside 0x00000x10FFFF, and return the original match for invalid references.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/rss-reader/src/index.tsx` around lines 30 - 35, Update
decodeEntities numeric entity handling to parse the complete hexadecimal or
decimal digit sequence without truncating it, then accept only values from
0x0000 through 0x10FFFF before calling String.fromCodePoint. Return the original
match for malformed, overlong, or out-of-range references in both the
hexadecimal and decimal branches.

return Number.isFinite(codePoint) ? String.fromCodePoint(codePoint) : match;
}

Expand Down
2 changes: 1 addition & 1 deletion examples/widget-gallery/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ class WidgetGalleryApp extends Widget {
}

// Tab switching: 1-6
const num = parseInt(event.key);
const num = parseInt(event.key, 10);
if (num >= 1 && num <= 6) {
this._switchTab(num - 1);
return true;
Expand Down
Loading