authorgravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-10-05 00:49:02-07:00
committergravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-10-05 15:36:02-07:00
logbc6dafcf6a1a26d0c997fae4d9aa887de5a7085c
treef2f76bf04a1fffff7629f8c87a201920e47bc1ec
parent3fe6530e54cd7a6121d486f6e88f2ca4ee46c9f4
signature Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU

Support verified Shale r1616 plain issue headings without relaxing identity checks


1 files changed, 21 insertions(+), 7 deletions(-)

dashboard/src/shale.rs+21-7
...@@ -288,16 +288,21 @@ fn issue(html: &str, repository: &str, id: Option<u64>) -> Result<Value> {...@@ -288,16 +288,21 @@ fn issue(html: &str, repository: &str, id: Option<u64>) -> Result<Value> {
288 let spans: Vec<_> = document288 let spans: Vec<_> = document
289 .select(&Selector::parse("h1 > span").unwrap())289 .select(&Selector::parse("h1 > span").unwrap())
290 .collect();290 .collect();
291 let issue_id = spans.first().and_then(|s| {291 let (issue_id, title) = if spans.len() == 2 {
292 text(*s)292 (text(spans[0]).strip_prefix('#').and_then(|id| id.parse::<u64>().ok()), Some(text(spans[1])))
293 .strip_prefix('#')293 } else if spans.is_empty() && tokenless_r1616(&document) {
294 .and_then(|s| s.parse::<u64>().ok())294 let headings: Vec<_> = document.select(&Selector::parse("h1").unwrap()).collect();
295 });295 if let [heading] = headings.as_slice() {
296 text(*heading).strip_prefix("Issue #").and_then(|text| text.split_once(": "))
297 .map(|(id, title)| (id.parse::<u64>().ok(), Some(title.to_owned())))
298 .unwrap_or((None, None))
299 } else { (None, None) }
300 } else { (None, None) };
296 let status = document301 let status = document
297 .select(&Selector::parse("select[name=status] option[selected]").unwrap())302 .select(&Selector::parse("select[name=status] option[selected]").unwrap())
298 .next()303 .next()
299 .and_then(|e| e.attr("value"));304 .and_then(|e| e.attr("value"));
300 if spans.len() != 2305 if title.as_ref().is_none_or(|title| title.is_empty())
301 || issue_id.is_none_or(|n| n == 0 || id.is_some_and(|id| n != id))306 || issue_id.is_none_or(|n| n == 0 || id.is_some_and(|id| n != id))
302 || status.is_none()307 || status.is_none()
303 {308 {
...@@ -334,7 +339,7 @@ fn issue(html: &str, repository: &str, id: Option<u64>) -> Result<Value> {...@@ -334,7 +339,7 @@ fn issue(html: &str, repository: &str, id: Option<u64>) -> Result<Value> {
334 .map(text)339 .map(text)
335 .collect();340 .collect();
336 Ok(341 Ok(
337 json!({"repository":repository,"id":issue_id,"title":text(spans[1]),"status":status,"labels":labels,"comments":comments}),342 json!({"repository":repository,"id":issue_id,"title":title.unwrap(),"status":status,"labels":labels,"comments":comments}),
338 )343 )
339}344}
340fn current(app: &App, grant: &Value, credential: &str) -> Result<()> {345fn current(app: &App, grant: &Value, credential: &str) -> Result<()> {
...@@ -921,6 +926,15 @@ mod tests {...@@ -921,6 +926,15 @@ mod tests {
921 assert!(issue(&page.replace("selected", ""), "owned", Some(3)).is_err());926 assert!(issue(&page.replace("selected", ""), "owned", Some(3)).is_err());
922 }927 }
923 #[test]928 #[test]
929 fn verified_r1616_issue_heading_keeps_identity_checks() {
930 let page = "<head><meta name='astheno.shale.repo.name' content='owned'></head><body id=page-issue><h1>Issue #3: Snow &amp; ☃</h1><select name=status><option selected value=done>Done</option></select><footer class=usa-footer><div class=usa-footer__secondary-section><a href='https://astheno.software/shale/'>shale r1616-ga87d2f5.zig.0.16.0</a></div></footer></body>";
931 assert_eq!(issue(page, "owned", Some(3)).unwrap()["title"], "Snow & ☃");
932 assert!(issue(page, "other", Some(3)).is_err());
933 assert!(issue(page, "owned", Some(4)).is_err());
934 assert!(issue(&page.replace("r1616-ga87d2f5.zig.0.16.0", "r1758-new"), "owned", Some(3)).is_err());
935 assert!(issue(&page.replace("Issue #3", "Issue #0"), "owned", None).is_err());
936 }
937 #[test]
924 fn account_identity_uses_html_text_and_rejects_login_or_changed_markup() {938 fn account_identity_uses_html_text_and_rejects_login_or_changed_markup() {
925 assert_eq!(939 assert_eq!(
926 username("<body id=page-user-settings><kbd>snow&amp;flake☃</kbd>").unwrap(),940 username("<body id=page-user-settings><kbd>snow&amp;flake☃</kbd>").unwrap(),