aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortilpner2020-05-29 10:56:09 +0200
committertilpner2020-05-29 10:56:09 +0200
commit32139f8f178a40e3aff155a5bc035157f2cfca42 (patch)
tree55a3054ecc7deb08241e9501c349c93331794ff6
parent6f0beed9c2219268807b9a7302ec374431624043 (diff)
downloadgithub-label-feed-32139f8f178a40e3aff155a5bc035157f2cfca42.tar.gz
github-label-feed-32139f8f178a40e3aff155a5bc035157f2cfca42.tar.xz
github-label-feed-32139f8f178a40e3aff155a5bc035157f2cfca42.zip
atom: entity-escape some user-defined input
-rw-r--r--Cargo.lock1
-rw-r--r--Cargo.toml1
-rw-r--r--src/atom.rs45
3 files changed, 43 insertions, 4 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 57b53a9..e707082 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -530,6 +530,7 @@ dependencies = [
"structopt 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
"tracing 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
"tracing-subscriber 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
diff --git a/Cargo.toml b/Cargo.toml
index 592719d..aea95c6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -10,6 +10,7 @@ serde = "1.0"
reqwest = { version = "0.10", features = [ "json" ] }
structopt = "0.3"
chrono = "0.4"
+url = "2.1"
futures = "0.3"
smol = { version = "0.1", features = [ "tokio02" ] }
diff --git a/src/atom.rs b/src/atom.rs
index e87901d..f7e96ec 100644
--- a/src/atom.rs
+++ b/src/atom.rs
@@ -8,6 +8,7 @@ use atom_syndication::*;
use anyhow::{ Result, Context };
use futures::{ Stream, StreamExt };
use chrono::{ Utc, TimeZone };
+use url::Url;
use tracing::info;
@@ -25,6 +26,24 @@ struct Issue {
updated_at: i64
}
+// Naive implementation of https://www.w3.org/TR/REC-xml/#syntax
+fn entity_escape(from: &str) -> String {
+ let mut escaped = String::with_capacity(from.len());
+
+ for c in from.chars() {
+ match c {
+ '&' => escaped.push_str("&"),
+ '<' => escaped.push_str("&lt;"),
+ '>' => escaped.push_str("&gt;"),
+ '\'' => escaped.push_str("&apos;"),
+ '"' => escaped.push_str("&quot;"),
+ any => escaped.push(any)
+ }
+ }
+
+ escaped
+}
+
async fn query_issues_for_label<'conn>(conn: &'conn mut Conn, repo_id: i64, label: &str) -> impl Stream<Item=sqlx::Result<Issue>> + 'conn {
sqlx::query_as::<_, Issue>(r#"
SELECT issues.number, state, title, body, user_login, html_url, updated_at FROM issues
@@ -58,8 +77,8 @@ async fn issue_to_entry(conn: &mut Conn, repo_id: i64, issue: Issue) -> Result<E
.await;
Ok(EntryBuilder::default()
- .title(issue.title)
- .id(issue.html_url.clone())
+ .title(entity_escape(&issue.title))
+ .id(entity_escape(&issue.html_url))
.updated(Utc.timestamp(issue.updated_at, 0))
.authors(vec![
Person {
@@ -75,7 +94,7 @@ async fn issue_to_entry(conn: &mut Conn, repo_id: i64, issue: Issue) -> Result<E
.expect("Failed to build link")])
.content(ContentBuilder::default()
.content_type(Some(String::from("html")))
- .value(issue.body)
+ .value(entity_escape(&issue.body))
.build()
.expect("Failed to build content"))
.build()
@@ -102,8 +121,26 @@ pub async fn generate(mut conn: &mut Conn, (ref owner, ref name): (String, Strin
for label in labels {
info!("atom for {:?}", label);
+ let label_url = {
+ let mut url = Url::parse("https://github.com")?;
+ url.path_segments_mut()
+ .unwrap()
+ .push(owner).push(name)
+ .push("labels").push(&label);
+ url.into_string()
+ };
+
let mut feed = FeedBuilder::default();
- feed.title(label.clone());
+ feed.title(entity_escape(&label));
+ feed.id(&label_url);
+ feed.updated(Utc::now());
+ feed.links(vec![
+ LinkBuilder::default()
+ .href(&label_url)
+ .rel("alternate")
+ .build()
+ .map_err(anyhow::Error::msg)?
+ ]);
let issues: Vec<Issue> = query_issues_for_label(&mut conn, repo_id, &label).await
.filter_map(|res| async { res.ok() })