Skip to content

Commit

Permalink
Refactored object constructors and input checks.
Browse files Browse the repository at this point in the history
  • Loading branch information
jaytmiller committed Mar 27, 2024
1 parent a2e4b38 commit 2cca2a1
Showing 1 changed file with 65 additions and 70 deletions.
135 changes: 65 additions & 70 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,18 @@ import { ISettingRegistry } from '@jupyterlab/settingregistry';

// Use the browser's built-in functionality to quickly and safely escape the string
function escapeHtml(html: string): string {
var div = document.createElement('div');
div.appendChild(document.createTextNode(html));
return div.innerHTML;
const div = document.createElement('div');
div.appendChild(document.createTextNode(html));
return div.innerText;
}

function dlog(...args: any[]) {
console.log(...args);
}

function log_and_throw(...args: any[]) {
console.log(...args);
throw new Error(...args);
}

// ===================================================================================
Expand All @@ -25,6 +34,21 @@ class Message {
message: string;

constructor(username: string, timestamp: string, expires: string, level: string, message: string) {
if (!(typeof username === 'string' && username.length > 0 && username.length <= 32 && /^[[email protected]]+$/.test(username))) {
log_and_throw('Bad message username.');
}
if (!(typeof timestamp === 'string' && /^\d\d\d\d-\d\d-\d\dT\d\d:\d\d(:\d\d(.\d\d\d(\d\d\d)?)?)?$/.test(timestamp))) {
log_and_throw('Bad message timestamp.');
}
if (!(typeof expires === 'string' && /^\d+\-\d\d:\d\d:\d\d$/.test(expires))) {
log_and_throw('Bad expires time.');
}
if (!['debug', 'info', 'notice', 'warning', 'error', 'critical'].includes(level)) {
log_and_throw('Bad level setting.');
}
if (!(typeof message === 'string' && message.length > 0 && message.length <= 1024)) {
log_and_throw('Bad message content.');
}
this.username = escapeHtml(username);
this.timestamp = escapeHtml(timestamp);
this.expires = escapeHtml(expires);
Expand All @@ -45,7 +69,7 @@ class Message {
}

toHtml(): string {
return `<tr>${this.fmtTimestamp()} ${this.fmtLevel()} <td class='.announce-dash'> &nbsp; </td> ${this.fmtMessage()}</tr>`;
return `<tr>${this.fmtTimestamp()} ${this.fmtLevel()} <td> &nbsp; </td> ${this.fmtMessage()}</tr>`;
}
}

Expand All @@ -54,6 +78,16 @@ class MessageBlock {
messages: Message[];

constructor(title: string, messages: Message[]) {
if (typeof title !== 'string' || title.length <= 0 || title.length > 128) {
log_and_throw('Bad title.');
}
if (!Array.isArray(messages) || messages.length <= 0 || messages.length > 16) {
// currently 5 on server
log_and_throw('Bad messages array.');
}
if (!messages.every(msg => msg instanceof Message)) {
log_and_throw('Bad messages.');
}
this.title = escapeHtml(title);
this.messages = messages;
}
Expand All @@ -80,8 +114,17 @@ class AnnouncementsData {
blocks: MessageBlock[];

constructor(popup: boolean, timestamp: string, blocks: MessageBlock[]) {
if (typeof popup !== 'boolean') {
log_and_throw('Bad popup type.');
}
if (!Array.isArray(blocks)) {
log_and_throw('Bad blocks array.');
}
if (!blocks.every(block => block instanceof MessageBlock)) {
log_and_throw('Bad blocks.');
}
this.popup = popup;
this.timestamp = timestamp;
this.timestamp = escapeHtml(timestamp);
this.blocks = blocks;
}

Expand All @@ -98,70 +141,27 @@ class AnnouncementsData {
}

function jsonToAnnouncementsData(jsonData: any): AnnouncementsData {
const blocks = jsonData.blocks.map((blockData: MessageBlock) => {
const messages = blockData.messages.map((msg: Message) => {
return new Message(msg.username, msg.timestamp, msg.expires, msg.level, msg.message);
// Given unchecked `jsonData`, create an AnnouncementsData instance
// checking each field as used or as lower level objects use them.
// All parameters should be checked and escaped to make them safe
// for display in the browser.
if (!Array.isArray(jsonData.blocks)) {
log_and_throw('Bad blocks array.');
}
const blocks = jsonData.blocks.map((blockData: MessageBlock) => {
const bdmessages = blockData.messages;
if (!Array.isArray(bdmessages) || bdmessages.length <= 0 || bdmessages.length > 16) {
// currently 5 on server
log_and_throw('Bad messages array.');
}
const messages = bdmessages.map((msg: Message) => {
return new Message(msg.username, msg.timestamp, msg.expires, msg.level, msg.message);
});
return new MessageBlock(blockData.title, messages);
});
return new MessageBlock(blockData.title, messages);
});
return new AnnouncementsData(jsonData.popup, jsonData.timestamp, blocks);
}

function dlog(...args: any[]) {
console.log(...args);
}

function isValidAnnouncementsData(jsonData: any): boolean {
if (typeof jsonData.popup !== 'boolean') {
dlog('Bad jsonData.popup type');
return false;
}
if (!Array.isArray(jsonData.blocks)) {
dlog('Blocks field is not an array.');
return false;
}
return jsonData.blocks.every(isValidBlock);
}

function isValidBlock(block: MessageBlock): boolean {
if (typeof block.title !== 'string' || block.title.length <= 0 || block.title.length > 128) {
dlog('Bad title.');
return false;
}
if (!Array.isArray(block.messages) || block.messages.length <= 0 || block.messages.length > 16) {
// currently 5 on server
dlog('Bad messages array.');
return false;
}
return block.messages.every(isValidMessage);
}

function isValidMessage(msg: Message): boolean {
if (
!(typeof msg.username === 'string' && msg.username.length > 0 && msg.username.length <= 32 && /^[[email protected]]+$/.test(msg.username))
) {
dlog('Bad message username.');
return false;
}
if (!(typeof msg.timestamp === 'string' && /^\d\d\d\d-\d\d-\d\dT\d\d:\d\d(:\d\d(.\d\d\d(\d\d\d)?)?)?$/.test(msg.timestamp))) {
dlog('Bad message timestamp.');
return false;
}
if (!(typeof msg.expires === 'string' && /^\d+\-\d\d:\d\d:\d\d$/.test(msg.expires))) {
dlog('Bad expires time.');
return false;
}
if (!['debug', 'info', 'notice', 'warning', 'error', 'critical'].includes(msg.level)) {
dlog('Bad level setting.');
return false;
}
if (!(typeof msg.message === 'string' && msg.message.length > 0 && msg.message.length <= 1024)) {
dlog('Bad message content.');
return false;
}
return true;
}

// ===================================================================================

// Class that handles all the announcements refresh information and methods
Expand Down Expand Up @@ -203,11 +203,6 @@ class RefreshAnnouncements {
const jsonData = await response.json();
dlog('jsonData:', jsonData);

if (!isValidAnnouncementsData(jsonData)) {
dlog('Invalid announcement data.');
throw new Error('Invalid announcement data');
}

const announcements = jsonToAnnouncementsData(jsonData);
const rendered = announcements.toHtml();
dlog('Rendered:', rendered);
Expand Down

0 comments on commit 2cca2a1

Please sign in to comment.