forked from spacetelescope/stsci-announce
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactored object constructors and input checks.
- Loading branch information
1 parent
a2e4b38
commit 2cca2a1
Showing
1 changed file
with
65 additions
and
70 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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); | ||
} | ||
|
||
// =================================================================================== | ||
|
@@ -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); | ||
|
@@ -45,7 +69,7 @@ class Message { | |
} | ||
|
||
toHtml(): string { | ||
return `<tr>${this.fmtTimestamp()} ${this.fmtLevel()} <td class='.announce-dash'> </td> ${this.fmtMessage()}</tr>`; | ||
return `<tr>${this.fmtTimestamp()} ${this.fmtLevel()} <td> </td> ${this.fmtMessage()}</tr>`; | ||
} | ||
} | ||
|
||
|
@@ -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; | ||
} | ||
|
@@ -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; | ||
} | ||
|
||
|
@@ -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 | ||
|
@@ -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); | ||
|