summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorColin Reeder <vpzomtrrfrt@gmail.com>2020-12-31 11:46:10 -0700
committerColin Reeder <vpzomtrrfrt@gmail.com>2020-12-31 12:15:12 -0700
commit4d4220ad3f429dfa06247d5cf17c62196acd97ae (patch)
tree2abd8be859b021793b8eb7238668ba1bfd322fea
parent2289ae102a460afd6ffffa8cff019f5ddd083401 (diff)
Distinguish between html & text in user & community descriptions
-rw-r--r--Cargo.lock70
-rw-r--r--Cargo.toml1
-rw-r--r--migrations/20201231175007_description-html/down.sql4
-rw-r--r--migrations/20201231175007_description-html/up.sql4
-rw-r--r--src/apub_util/ingest.rs18
-rw-r--r--src/routes/api/communities.rs44
-rw-r--r--src/routes/api/users.rs29
-rw-r--r--src/routes/apub/communities.rs7
-rw-r--r--src/routes/apub/mod.rs9
9 files changed, 150 insertions, 36 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 7d72fd9..3fa161f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -220,6 +220,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "476e9cd489f9e121e02ffa6014a8ef220ecb15c05ed23fc34cca13925dc283fb"
[[package]]
+name = "buf-min"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa17aa1cf56bdd6bb30518767d00e58019d326f3f05d8c3e0730b549d332ea83"
+dependencies = [
+ "bytes",
+]
+
+[[package]]
name = "bumpalo"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -291,7 +300,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b076e143e1d9538dde65da30f8481c2a6c44040edb8e02b9bf1351edb92ce3"
dependencies = [
"lazy_static",
- "nom",
+ "nom 5.1.2",
"serde",
]
@@ -469,7 +478,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc"
dependencies = [
"backtrace",
- "version_check",
+ "version_check 0.9.2",
]
[[package]]
@@ -713,7 +722,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac746a5f3bbfdadd6106868134545e684693d54d9d44f6e9588a7d54af0bf980"
dependencies = [
"typenum",
- "version_check",
+ "version_check 0.9.2",
]
[[package]]
@@ -1063,7 +1072,7 @@ dependencies = [
"idna",
"mime",
"native-tls",
- "nom",
+ "nom 5.1.2",
"once_cell",
"quoted_printable",
"r2d2",
@@ -1176,6 +1185,7 @@ dependencies = [
"unic-langid",
"url",
"uuid",
+ "v_htmlescape",
]
[[package]]
@@ -1397,13 +1407,23 @@ checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
[[package]]
name = "nom"
+version = "4.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6"
+dependencies = [
+ "memchr",
+ "version_check 0.1.5",
+]
+
+[[package]]
+name = "nom"
version = "5.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af"
dependencies = [
"lexical-core",
"memchr",
- "version_check",
+ "version_check 0.9.2",
]
[[package]]
@@ -2417,7 +2437,7 @@ version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
dependencies = [
- "version_check",
+ "version_check 0.9.2",
]
[[package]]
@@ -2478,6 +2498,38 @@ dependencies = [
]
[[package]]
+name = "v_escape"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3e0ab5fab1db278a9413d2ea794cb66f471f898c5b020c3c394f6447625d9d4"
+dependencies = [
+ "buf-min",
+ "v_escape_derive",
+]
+
+[[package]]
+name = "v_escape_derive"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c860ad1273f4eee7006cee05db20c9e60e5d24cba024a32e1094aa8e574f3668"
+dependencies = [
+ "nom 4.2.3",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "v_htmlescape"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f9a8af610ad6f7fc9989c9d2590d9764bc61f294884e9ee93baa58795174572"
+dependencies = [
+ "cfg-if 1.0.0",
+ "v_escape",
+]
+
+[[package]]
name = "vcpkg"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2485,6 +2537,12 @@ checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c"
[[package]]
name = "version_check"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
+
+[[package]]
+name = "version_check"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
diff --git a/Cargo.toml b/Cargo.toml
index 3d965ec..a2ac39a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -54,6 +54,7 @@ migrant_lib = { version = "0.30.0", features = ["d-postgres"] }
pdcm-linkify = "0.1.0"
log = "0.4"
env_logger = "0.8"
+v_htmlescape = "0.12.0"
[dev-dependencies]
rand = "0.7.3"
diff --git a/migrations/20201231175007_description-html/down.sql b/migrations/20201231175007_description-html/down.sql
new file mode 100644
index 0000000..d24bd35
--- /dev/null
+++ b/migrations/20201231175007_description-html/down.sql
@@ -0,0 +1,4 @@
+BEGIN;
+ ALTER TABLE person DROP COLUMN description_html;
+ ALTER TABLE community DROP COLUMN description_html;
+COMMIT;
diff --git a/migrations/20201231175007_description-html/up.sql b/migrations/20201231175007_description-html/up.sql
new file mode 100644
index 0000000..44e9d07
--- /dev/null
+++ b/migrations/20201231175007_description-html/up.sql
@@ -0,0 +1,4 @@
+BEGIN;
+ ALTER TABLE person ADD COLUMN description_html TEXT;
+ ALTER TABLE community ADD COLUMN description_html TEXT;
+COMMIT;
diff --git a/src/apub_util/ingest.rs b/src/apub_util/ingest.rs
index e85cdfd..95cb9c6 100644
--- a/src/apub_util/ingest.rs
+++ b/src/apub_util/ingest.rs
@@ -215,10 +215,9 @@ pub async fn ingest_object(
.and_then(|maybe| maybe.iter().filter_map(|x| x.as_xsd_string()).next())
})
.unwrap_or("");
- let description = group
+ let description_html = group
.summary()
- .and_then(|maybe| maybe.iter().filter_map(|x| x.as_xsd_string()).next())
- .unwrap_or("");
+ .and_then(|maybe| maybe.iter().filter_map(|x| x.as_xsd_string()).next());
let inbox = group.inbox_unchecked().as_str();
let shared_inbox = group
.endpoints_unchecked()
@@ -236,8 +235,8 @@ pub async fn ingest_object(
.and_then(|key| key.signature_algorithm.as_deref());
let id = CommunityLocalID(db.query_one(
- "INSERT INTO community (name, local, ap_id, ap_inbox, ap_shared_inbox, public_key, public_key_sigalg, description) VALUES ($1, FALSE, $2, $3, $4, $5, $6, $7) ON CONFLICT (ap_id) DO UPDATE SET ap_inbox=$3, ap_shared_inbox=$4, public_key=$5, public_key_sigalg=$6, description=$7 RETURNING id",
- &[&name, &ap_id.as_str(), &inbox, &shared_inbox, &public_key, &public_key_sigalg, &description],
+ "INSERT INTO community (name, local, ap_id, ap_inbox, ap_shared_inbox, public_key, public_key_sigalg, description_html) VALUES ($1, FALSE, $2, $3, $4, $5, $6, $7) ON CONFLICT (ap_id) DO UPDATE SET ap_inbox=$3, ap_shared_inbox=$4, public_key=$5, public_key_sigalg=$6, description_html=$7 RETURNING id",
+ &[&name, &ap_id.as_str(), &inbox, &shared_inbox, &public_key, &public_key_sigalg, &description_html],
).await?.get(0));
Ok(Some(IngestResult::Actor(
@@ -291,10 +290,9 @@ pub async fn ingest_object(
.public_key
.as_ref()
.and_then(|key| key.signature_algorithm.as_deref());
- let description = person
+ let description_html = person
.summary()
- .and_then(|maybe| maybe.iter().filter_map(|x| x.as_xsd_string()).next())
- .unwrap_or("");
+ .and_then(|maybe| maybe.iter().filter_map(|x| x.as_xsd_string()).next());
let avatar = person.icon().and_then(|icon| {
icon.iter()
@@ -316,8 +314,8 @@ pub async fn ingest_object(
.map(|x| x.as_str());
let id = UserLocalID(db.query_one(
- "INSERT INTO person (username, local, created_local, ap_id, ap_inbox, ap_shared_inbox, public_key, public_key_sigalg, description, avatar) VALUES ($1, FALSE, localtimestamp, $2, $3, $4, $5, $6, $7, $8) ON CONFLICT (ap_id) DO UPDATE SET ap_inbox=$3, ap_shared_inbox=$4, public_key=$5, public_key_sigalg=$6, description=$7, avatar=$8 RETURNING id",
- &[&username, &ap_id.as_str(), &inbox, &shared_inbox, &public_key, &public_key_sigalg, &description, &avatar],
+ "INSERT INTO person (username, local, created_local, ap_id, ap_inbox, ap_shared_inbox, public_key, public_key_sigalg, description_html, avatar) VALUES ($1, FALSE, localtimestamp, $2, $3, $4, $5, $6, $7, $8) ON CONFLICT (ap_id) DO UPDATE SET ap_inbox=$3, ap_shared_inbox=$4, public_key=$5, public_key_sigalg=$6, description_html=$7, avatar=$8 RETURNING id",
+ &[&username, &ap_id.as_str(), &inbox, &shared_inbox, &public_key, &public_key_sigalg, &description_html, &avatar],
).await?.get(0));
Ok(Some(IngestResult::Actor(super::ActorLocalInfo::User {
diff --git a/src/routes/api/communities.rs b/src/routes/api/communities.rs
index 4392983..d3f79c8 100644
--- a/src/routes/api/communities.rs
+++ b/src/routes/api/communities.rs
@@ -13,6 +13,8 @@ struct RespCommunityInfo<'a> {
base: RespMinimalCommunityInfo<'a>,
description: &'a str,
+ description_html: Option<String>,
+ description_text: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
you_are_moderator: Option<bool>,
@@ -25,6 +27,20 @@ struct RespYourFollowInfo {
accepted: bool,
}
+fn get_community_description_fields<'a>(
+ description_text: &'a str,
+ description_html: Option<&'a str>,
+) -> (&'a str, Option<&'a str>, Option<String>) {
+ match description_html {
+ Some(description_html) => (
+ description_html,
+ None,
+ Some(ammonia::clean(description_html)),
+ ),
+ None => (description_text, Some(description_text), None),
+ }
+}
+
async fn route_unstable_communities_list(
_: (),
ctx: Arc<crate::RouteContext>,
@@ -43,7 +59,7 @@ async fn route_unstable_communities_list(
let query: CommunitiesListQuery = serde_urlencoded::from_str(req.uri().query().unwrap_or(""))?;
- let mut sql = String::from("SELECT id, name, local, ap_id, description");
+ let mut sql = String::from("SELECT id, name, local, ap_id, description, description_html");
let mut values: Vec<&(dyn tokio_postgres::types::ToSql + Sync)> = Vec::new();
let db = ctx.db_pool.get().await?;
@@ -77,7 +93,9 @@ async fn route_unstable_communities_list(
let name = row.get(1);
let local = row.get(2);
let ap_id = row.get(3);
- let description = row.get(4);
+
+ let (description, description_text, description_html) =
+ get_community_description_fields(row.get(4), row.get(5));
let host = crate::get_actor_host_or_unknown(local, ap_id, &ctx.local_hostname);
@@ -91,13 +109,16 @@ async fn route_unstable_communities_list(
},
description,
+ description_html,
+ description_text,
+
you_are_moderator: if query.include_your {
- Some(row.get(6))
+ Some(row.get(7))
} else {
None
},
your_follow: if query.include_your {
- Some(match row.get(5) {
+ Some(match row.get(6) {
Some(accepted) => Some(RespYourFollowInfo { accepted }),
None => None,
})
@@ -204,12 +225,12 @@ async fn route_unstable_communities_get(
(if query.include_your {
let user = crate::require_login(&req, &db).await?;
db.query_opt(
- "SELECT name, local, ap_id, description, (SELECT accepted FROM community_follow WHERE community=community.id AND follower=$2), EXISTS(SELECT 1 FROM community_moderator WHERE community=community.id AND person=$2) FROM community WHERE id=$1",
+ "SELECT name, local, ap_id, description, description_html, (SELECT accepted FROM community_follow WHERE community=community.id AND follower=$2), EXISTS(SELECT 1 FROM community_moderator WHERE community=community.id AND person=$2) FROM community WHERE id=$1",
&[&community_id.raw(), &user.raw()],
).await?
} else {
db.query_opt(
- "SELECT name, local, ap_id, description FROM community WHERE id=$1",
+ "SELECT name, local, ap_id, description, description_html FROM community WHERE id=$1",
&[&community_id.raw()],
).await?
})
@@ -224,6 +245,9 @@ async fn route_unstable_communities_get(
let community_local = row.get(1);
let community_ap_id: Option<&str> = row.get(2);
+ let (description, description_text, description_html) =
+ get_community_description_fields(row.get(3), row.get(4));
+
let info = RespCommunityInfo {
base: RespMinimalCommunityInfo {
id: community_id,
@@ -239,14 +263,16 @@ async fn route_unstable_communities_get(
},
remote_url: community_ap_id,
},
- description: row.get(3),
+ description,
+ description_html,
+ description_text,
you_are_moderator: if query.include_your {
- Some(row.get(5))
+ Some(row.get(6))
} else {
None
},
your_follow: if query.include_your {
- Some(match row.get(4) {
+ Some(match row.get(5) {
Some(accepted) => Some(RespYourFollowInfo { accepted }),
None => None,
})
diff --git a/src/routes/api/users.rs b/src/routes/api/users.rs
index ad1d77d..efe25da 100644
--- a/src/routes/api/users.rs
+++ b/src/routes/api/users.rs
@@ -143,6 +143,8 @@ struct RespUserInfo<'a> {
base: RespMinimalAuthorInfo<'a>,
description: &'a str,
+ description_html: Option<&'a str>,
+ description_text: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
suspended: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
@@ -252,7 +254,8 @@ async fn route_unstable_users_patch(
#[derive(Deserialize)]
struct UsersEditBody<'a> {
- description: Option<Cow<'a, str>>,
+ #[serde(alias = "description")]
+ description_text: Option<Cow<'a, str>>,
email_address: Option<Cow<'a, str>>,
password: Option<String>,
avatar: Option<Cow<'a, str>>,
@@ -266,7 +269,7 @@ async fn route_unstable_users_patch(
let mut changes = Vec::<(&str, &(dyn tokio_postgres::types::ToSql + Sync))>::new();
- if let Some(description) = body.description.as_ref() {
+ if let Some(description) = body.description_text.as_ref() {
changes.push(("description", description));
}
if let Some(email_address) = body.email_address.as_ref() {
@@ -534,7 +537,7 @@ async fn route_unstable_users_get(
let row = db
.query_opt(
- "SELECT username, local, ap_id, description, avatar, suspended FROM person WHERE id=$1",
+ "SELECT username, local, ap_id, description, description_html, avatar, suspended FROM person WHERE id=$1",
&[&user_id],
)
.await?;
@@ -548,7 +551,7 @@ async fn route_unstable_users_get(
let local = row.get(1);
let ap_id = row.get(2);
- let avatar: Option<&str> = row.get(4);
+ let avatar: Option<&str> = row.get(5);
let info = RespMinimalAuthorInfo {
id: user_id,
@@ -561,10 +564,24 @@ async fn route_unstable_users_get(
}),
};
+ let (description, description_text, description_html) = match row.get(4) {
+ Some(description_html) => (
+ description_html,
+ None,
+ Some(ammonia::clean(description_html)),
+ ),
+ None => {
+ let description_text: &str = row.get(3);
+ (description_text, Some(description_text), None)
+ }
+ };
+
let info = RespUserInfo {
base: info,
- description: row.get(3),
- suspended: if local { Some(row.get(5)) } else { None },
+ description,
+ description_html: description_html.as_deref(),
+ description_text,
+ suspended: if local { Some(row.get(6)) } else { None },
your_note,
};
diff --git a/src/routes/apub/communities.rs b/src/routes/apub/communities.rs
index dd249f7..f9fe71b 100644
--- a/src/routes/apub/communities.rs
+++ b/src/routes/apub/communities.rs
@@ -88,7 +88,7 @@ async fn handler_communities_get(
match db
.query_opt(
- "SELECT name, local, public_key, description FROM community WHERE id=$1",
+ "SELECT name, local, public_key, description, description_html FROM community WHERE id=$1",
&[&community_id],
)
.await?
@@ -116,7 +116,10 @@ async fn handler_communities_get(
None
}
});
- let description: &str = row.get(3);
+ let description = match row.get(4) {
+ Some(description_html) => description_html,
+ None => v_htmlescape::escape(row.get(3)).to_string(),
+ };
let community_ap_id =
crate::apub_util::get_local_community_apub_id(community_id, &ctx.host_url_apub);
diff --git a/src/routes/apub/mod.rs b/src/routes/apub/mod.rs
index d6a9583..b352cd4 100644
--- a/src/routes/apub/mod.rs
+++ b/src/routes/apub/mod.rs
@@ -118,7 +118,7 @@ async fn handler_users_get(
match db
.query_opt(
- "SELECT username, local, public_key, description, avatar FROM person WHERE id=$1",
+ "SELECT username, local, public_key, description, description_html, avatar FROM person WHERE id=$1",
&[&user_id.raw()],
)
.await?
@@ -148,9 +148,12 @@ async fn handler_users_get(
}
});
- let description: &str = row.get(3);
+ let description = match row.get(4) {
+ Some(description_html) => description_html,
+ None => v_htmlescape::escape(row.get(3)).to_string(),
+ };
- let avatar: Option<&str> = row.get(4);
+ let avatar: Option<&str> = row.get(5);
let user_ap_id =
crate::apub_util::get_local_person_apub_id(user_id, &ctx.host_url_apub);