Add comment sanitization and markdown rendering

main
Elnu 2 years ago
parent 1bea87f47f
commit 42d980f4cb

57
Cargo.lock generated

@ -701,6 +701,20 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "html5ever"
version = "0.25.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5c13fb08e5d4dfc151ee5e88bae63f7773d61852f3bdc73c9f4b9e1bde03148"
dependencies = [
"log",
"mac",
"markup5ever 0.10.1",
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "html5ever" name = "html5ever"
version = "0.26.0" version = "0.26.0"
@ -709,7 +723,7 @@ checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7"
dependencies = [ dependencies = [
"log", "log",
"mac", "mac",
"markup5ever", "markup5ever 0.11.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn",
@ -858,6 +872,18 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "kuchiki"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ea8e9c6e031377cff82ee3001dc8026cdf431ed4e2e6b51f98ab8c73484a358"
dependencies = [
"cssparser",
"html5ever 0.25.2",
"matches",
"selectors",
]
[[package]] [[package]]
name = "language-tags" name = "language-tags"
version = "0.3.2" version = "0.3.2"
@ -929,6 +955,20 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
[[package]]
name = "markup5ever"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a24f40fb03852d1cdd84330cddcaf98e9ec08a7b7768e952fad3b4cf048ec8fd"
dependencies = [
"log",
"phf 0.8.0",
"phf_codegen 0.8.0",
"string_cache",
"string_cache_codegen",
"tendril",
]
[[package]] [[package]]
name = "markup5ever" name = "markup5ever"
version = "0.11.0" version = "0.11.0"
@ -1496,6 +1536,18 @@ version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695"
[[package]]
name = "sanitize_html"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cd7ea46188a5c6961a44b4c25ed280ea85a1e0b6a129f9bbd81bbf87261c52b"
dependencies = [
"html5ever 0.25.2",
"kuchiki",
"lazy_static",
"regex",
]
[[package]] [[package]]
name = "schannel" name = "schannel"
version = "0.1.20" version = "0.1.20"
@ -1521,7 +1573,7 @@ dependencies = [
"cssparser", "cssparser",
"ego-tree", "ego-tree",
"getopts", "getopts",
"html5ever", "html5ever 0.26.0",
"matches", "matches",
"selectors", "selectors",
"smallvec", "smallvec",
@ -1688,6 +1740,7 @@ dependencies = [
"md5", "md5",
"reqwest", "reqwest",
"rusqlite", "rusqlite",
"sanitize_html",
"scraper", "scraper",
"serde", "serde",
"serde_json", "serde_json",

@ -21,3 +21,4 @@ md5 = "0.7.0"
chrono = { version = "0.4.19", features = ["serde"] } chrono = { version = "0.4.19", features = ["serde"] }
reqwest = "0.11.11" reqwest = "0.11.11"
scraper = "0.13.0" scraper = "0.13.0"
sanitize_html = "0.7.0"

@ -6,4 +6,5 @@
<p>This is Page A.</p> <p>This is Page A.</p>
<div id="soudan"></div> <div id="soudan"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.4/moment.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.4/moment.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/markdown-it/13.0.1/markdown-it.min.js" integrity="sha512-SYfDUYPg5xspsG6OOpXU366G8SZsdHOhqk/icdrYJ2E/WKZxPxze7d2HD3AyXpT7U22PZ5y74xRpqZ6A2bJ+kQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="/soudan.js"></script> <script src="/soudan.js"></script>

@ -6,4 +6,5 @@
<p>This is Page B.</h2> <p>This is Page B.</h2>
<div id="soudan"></div> <div id="soudan"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.4/moment.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.4/moment.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/markdown-it/13.0.1/markdown-it.min.js" integrity="sha512-SYfDUYPg5xspsG6OOpXU366G8SZsdHOhqk/icdrYJ2E/WKZxPxze7d2HD3AyXpT7U22PZ5y74xRpqZ6A2bJ+kQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="/soudan.js"></script> <script src="/soudan.js"></script>

@ -9,7 +9,7 @@ document.getElementById("soudan").innerHTML = `<h3>Make a comment</h3>
</form> </form>
<h3 id="soudan-comments-header">Comments</h3> <h3 id="soudan-comments-header">Comments</h3>
<div id="soudan-comments"></div>`; <div id="soudan-comments"></div>`;
document.write(`<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.4/moment.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>`); const md = window.markdownit().disable("image");
const url = "http://127.0.0.1:8080"; const url = "http://127.0.0.1:8080";
const form = document.getElementById("soudan-comment-form"); const form = document.getElementById("soudan-comment-form");
const commentContainer = document.getElementById("soudan-comments"); const commentContainer = document.getElementById("soudan-comments");
@ -52,7 +52,7 @@ function reloadComments() {
html = "<p>No comments yet! Be the first to make one.</p>"; html = "<p>No comments yet! Be the first to make one.</p>";
} else { } else {
comments.forEach(comment => { comments.forEach(comment => {
html += `<div><img class="soudan-avatar" src="https://www.gravatar.com/avatar/${comment.gravatar}"><div><b>${comment.author ? comment.author : "Anonymous"}</b> commented ${moment(new Date(comment.timestamp * 1000)).fromNow()}:<br><div>${comment.text}</div></div></div>`; html += `<div><img class="soudan-avatar" src="https://www.gravatar.com/avatar/${comment.gravatar}"><div><b>${comment.author ? comment.author : "Anonymous"}</b> commented ${moment(new Date(comment.timestamp * 1000)).fromNow()}:<br><div>${md.render(comment.text)}</div></div></div>`;
}); });
} }
commentContainer.innerHTML = html; commentContainer.innerHTML = html;

@ -14,6 +14,7 @@ use std::{
sync::{Mutex, MutexGuard}, sync::{Mutex, MutexGuard},
}; };
use validator::Validate; use validator::Validate;
use sanitize_html::{sanitize_str, rules::predefined::DEFAULT, errors::SanitizeError};
struct AppState { struct AppState {
databases: HashMap<String, Mutex<Database>>, databases: HashMap<String, Mutex<Database>>,
@ -66,8 +67,21 @@ async fn post_comment(
) -> impl Responder { ) -> impl Responder {
match String::from_utf8(bytes.to_vec()) { match String::from_utf8(bytes.to_vec()) {
Ok(text) => { Ok(text) => {
let PostCommentsRequest { url, comment } = match serde_json::from_str(&text) { let PostCommentsRequest { url, comment } = match serde_json::from_str::<PostCommentsRequest>(&text) {
Ok(req) => req, Ok(mut req) => {
let mut sanitize_req = || -> Result<(), SanitizeError> {
req.comment.text = sanitize_str(&DEFAULT, &req.comment.text)?
.replace("&gt;", ">"); // required for markdown quotes
if let Some(ref mut author) = req.comment.author {
*author = sanitize_str(&DEFAULT, &author)?;
}
Ok(())
};
if let Err(_) = sanitize_req() {
return HttpResponse::InternalServerError().reason("failed to sanitize request").finish();
}
req
}
Err(_) => return HttpResponse::BadRequest().reason("invalid request body").finish(), Err(_) => return HttpResponse::BadRequest().reason("invalid request body").finish(),
}; };
if comment.validate().is_err() { if comment.validate().is_err() {

Loading…
Cancel
Save