Skip to content

Commit

Permalink
feat: prevent xss attacks
Browse files Browse the repository at this point in the history
  • Loading branch information
azat-io committed Oct 23, 2024
1 parent 1dc77aa commit 7adc06a
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 14 deletions.
35 changes: 33 additions & 2 deletions preview/elements/note.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,44 @@
export let title = ''
export let content: string[] = []
let processLine = (line: string) => {
let parts: Array<{ bold: boolean; text: string }> = []
let regex = /<b>(.*?)<\/b>/gi
let lastIndex = 0
let match
while ((match = regex.exec(line)) !== null) {
if (match.index > lastIndex) {
parts.push({ text: line.slice(lastIndex, match.index), bold: false })
}
parts.push({ text: match[1], bold: true })
;({ lastIndex } = regex)
}
if (lastIndex < line.length) {
parts.push({ text: line.slice(lastIndex), bold: false })
}
return parts
}
</script>

<div class="note">
<Typography size="l" tag="h3" mbe="s">{title}</Typography>

{#each content as line}
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
<Typography size="m" tag="p" mbe="2xs">{@html line}</Typography>
<Typography size="m" tag="p" mbe="2xs">
{#each processLine(line) as part}
{#if part.bold}
<b>{part.text}</b>
{:else}
{part.text}
{/if}
{/each}
</Typography>
{/each}
</div>

Expand Down
19 changes: 16 additions & 3 deletions preview/elements/table-comment.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,23 @@
export let value = ''
export let kind = ''
let formattedValue = value.replace(new RegExp(kind, 'i'), '<i>$&</i>')
let highlightedParts: Array<{ highlighted: boolean; text: string }> = []
$: {
let regex = new RegExp(`(${kind})`, 'i')
highlightedParts = value.split(regex).map(part => ({
highlighted: regex.test(part),
text: part,
}))
}
</script>

<Typography size="m">
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html formattedValue}
{#each highlightedParts as part}
{#if part.highlighted}
<i>{part.text}</i>
{:else}
{part.text}
{/if}
{/each}
</Typography>
38 changes: 33 additions & 5 deletions preview/stores/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,48 @@ import { readable, writable } from 'svelte/store'

import type { Data } from '~/typings/index.d'

let decodeHtmlEntities = (str: string): string => {
let textArea = document.createElement('textarea')
textArea.innerHTML = str
return textArea.value
}

let traverseAndDecode = <T>(obj: T): T => {
if (typeof obj === 'string') {
return decodeHtmlEntities(obj) as T
}

if (Array.isArray(obj)) {
return obj.map(item => traverseAndDecode(item)) as T
}

if (typeof obj === 'object' && obj !== null) {
let result = {} as { [K in keyof T]: T[K] }
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
result[key] = traverseAndDecode(obj[key])
}
}
return result
}

return obj
}

export let loading = writable(true)

export let data = readable<Partial<Data>>({}, set => {
let fetchData = async () => {
let dataValue: Data
if (import.meta.env.MODE === 'production') {
// @ts-ignore
set(window.data)
loading.set(false)
dataValue = window.data
} else {
let dataResponse = await fetch('/data.json')
let dataJson = await dataResponse.json()
set(dataJson)
loading.set(false)
dataValue = await dataResponse.json()
}
set(traverseAndDecode(dataValue))
loading.set(false)
}

fetchData()
Expand Down
9 changes: 9 additions & 0 deletions src/escape_html.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
pub fn escape_html(s: &str) -> String {
s.replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace("\"", "&quot;")
.replace("'", "&#x27;")
.replace("/", "&#x2F;")
.replace("</script>", "<\\/script>")
}
22 changes: 22 additions & 0 deletions src/escape_json_values.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use crate::escape_html::escape_html;
use serde_json::Value;

pub fn escape_json_values(json_value: &mut Value) {
match json_value {
Value::String(s) => {
let escaped = escape_html(s);
*s = escaped;
}
Value::Array(arr) => {
for item in arr {
escape_json_values(item);
}
}
Value::Object(map) => {
for (_key, value) in map {
escape_json_values(value);
}
}
_ => {}
}
}
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ pub mod add_missing_days;
pub mod blame;
pub mod check_git_repository;
pub mod copy_dir_recursive;
pub mod escape_html;
pub mod escape_json_values;
pub mod exec;
pub mod get_comments;
pub mod get_current_directory;
Expand Down
14 changes: 10 additions & 4 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use todoctor::add_missing_days::add_missing_days;
use todoctor::blame::blame;
use todoctor::check_git_repository::check_git_repository;
use todoctor::copy_dir_recursive::copy_dir_recursive;
use todoctor::escape_json_values::escape_json_values;
use todoctor::exec::exec;
use todoctor::get_comments::get_comments;
use todoctor::get_current_directory::get_current_directory;
Expand Down Expand Up @@ -357,8 +358,11 @@ async fn main() {
"version": version,
});

let json_string: String = serde_json::to_string(&json_data)
.expect("Error: Could not serialize data");
let mut escaped_json_data = json_data.clone();
escape_json_values(&mut escaped_json_data);

let escaped_json_string = serde_json::to_string(&escaped_json_data)
.expect("Error: Could not serializing JSON");

let dist_path: PathBuf =
get_dist_path().expect("Error: Could not get current dist path.");
Expand All @@ -374,8 +378,10 @@ async fn main() {
.expect("Error reading index.html");

if let Some(pos) = index_content.find("</head>") {
let script_tag: String =
format!("<script>window.data = {};</script>", json_string);
let script_tag: String = format!(
"<script>window.data = {};</script>",
escaped_json_string
);
index_content.insert_str(pos, &script_tag);

fs::write(&index_path, index_content)
Expand Down

0 comments on commit 7adc06a

Please sign in to comment.