diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2025-05-23 10:01:29 +0100 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2025-05-23 10:01:29 +0100 |
| commit | 686604665395385600ef8f1b5238a775249552a1 (patch) | |
| tree | 56a4c7a956e14dfbcdd4a518096968abc69583a6 /src/lib/repo_ref.rs | |
| parent | cc9a3a1d8526373625246504f72f338fd89c8d8b (diff) | |
feat: only try http(s) for ngit-relays
otherwise it tries all the protocols and reprots on each
Diffstat (limited to 'src/lib/repo_ref.rs')
| -rw-r--r-- | src/lib/repo_ref.rs | 180 |
1 files changed, 180 insertions, 0 deletions
diff --git a/src/lib/repo_ref.rs b/src/lib/repo_ref.rs index df1427a..aa36bbb 100644 --- a/src/lib/repo_ref.rs +++ b/src/lib/repo_ref.rs | |||
| @@ -290,6 +290,10 @@ impl RepoRef { | |||
| 290 | user: None, | 290 | user: None, |
| 291 | } | 291 | } |
| 292 | } | 292 | } |
| 293 | |||
| 294 | pub fn ngit_relays(&self) -> Vec<String> { | ||
| 295 | detect_existing_ngit_relays(Some(self), &[], &[], &[], &self.identifier) | ||
| 296 | } | ||
| 293 | } | 297 | } |
| 294 | 298 | ||
| 295 | pub async fn get_repo_coordinates_when_remote_unknown( | 299 | pub async fn get_repo_coordinates_when_remote_unknown( |
| @@ -565,6 +569,145 @@ pub fn save_repo_config_to_yaml( | |||
| 565 | .context("failed to write maintainers to maintainers.yaml file serde_yaml") | 569 | .context("failed to write maintainers to maintainers.yaml file serde_yaml") |
| 566 | } | 570 | } |
| 567 | 571 | ||
| 572 | pub fn detect_existing_ngit_relays( | ||
| 573 | repo_ref: Option<&RepoRef>, | ||
| 574 | args_relays: &[String], | ||
| 575 | args_clone_url: &[String], | ||
| 576 | args_blossoms: &[String], | ||
| 577 | identifier: &str, | ||
| 578 | ) -> Vec<String> { | ||
| 579 | // Collect clone URLs from arguments or repo_ref | ||
| 580 | let clone_urls: Vec<String> = if !args_clone_url.is_empty() { | ||
| 581 | args_clone_url.to_vec() | ||
| 582 | } else if let Some(repo) = repo_ref { | ||
| 583 | repo.git_server.clone() | ||
| 584 | } else { | ||
| 585 | Vec::new() | ||
| 586 | }; | ||
| 587 | |||
| 588 | // Collect relays from arguments or repo_ref | ||
| 589 | let relays: Vec<RelayUrl> = if !args_relays.is_empty() { | ||
| 590 | args_relays | ||
| 591 | .iter() | ||
| 592 | .filter_map(|r| RelayUrl::parse(r).ok()) | ||
| 593 | .collect() | ||
| 594 | } else if let Some(repo) = repo_ref { | ||
| 595 | repo.relays.clone() | ||
| 596 | } else { | ||
| 597 | Vec::new() | ||
| 598 | }; | ||
| 599 | |||
| 600 | // Collect blossom server URLs from arguments or repo_ref | ||
| 601 | let blossoms: Vec<Url> = if !args_blossoms.is_empty() { | ||
| 602 | args_blossoms | ||
| 603 | .iter() | ||
| 604 | .filter_map(|r| Url::parse(r).ok()) | ||
| 605 | .collect() | ||
| 606 | } else if let Some(repo) = repo_ref { | ||
| 607 | repo.blossoms.clone() | ||
| 608 | } else { | ||
| 609 | Vec::new() | ||
| 610 | }; | ||
| 611 | |||
| 612 | let mut existing_ngit_relays = Vec::new(); | ||
| 613 | for url in &clone_urls { | ||
| 614 | let Ok(formatted_as_ngit_relay_url) = normalize_ngit_relay_url(url) else { | ||
| 615 | continue; | ||
| 616 | }; | ||
| 617 | if existing_ngit_relays.contains(&formatted_as_ngit_relay_url) { | ||
| 618 | continue; | ||
| 619 | } | ||
| 620 | |||
| 621 | let clone_url_is_ngit_relay_format = if let Ok(npub) = extract_npub(url) { | ||
| 622 | url.contains(&format!("/{npub}/{identifier}.git")) | ||
| 623 | } else { | ||
| 624 | false | ||
| 625 | }; | ||
| 626 | if !clone_url_is_ngit_relay_format { | ||
| 627 | continue; | ||
| 628 | } | ||
| 629 | |||
| 630 | let matches_relay = relays.iter().any(|r| { | ||
| 631 | normalize_ngit_relay_url(&r.to_string()) | ||
| 632 | .is_ok_and(|r| r.eq(&formatted_as_ngit_relay_url)) | ||
| 633 | }); | ||
| 634 | if !matches_relay { | ||
| 635 | continue; | ||
| 636 | } | ||
| 637 | |||
| 638 | let matches_blossoms = blossoms.iter().any(|r| { | ||
| 639 | normalize_ngit_relay_url(r.as_str()).is_ok_and(|r| r.eq(&formatted_as_ngit_relay_url)) | ||
| 640 | }); | ||
| 641 | if !matches_blossoms { | ||
| 642 | continue; | ||
| 643 | } | ||
| 644 | |||
| 645 | existing_ngit_relays.push(formatted_as_ngit_relay_url); | ||
| 646 | } | ||
| 647 | existing_ngit_relays | ||
| 648 | } | ||
| 649 | |||
| 650 | pub fn normalize_ngit_relay_url(url: &str) -> Result<String> { | ||
| 651 | // Parse the URL and handle errors | ||
| 652 | let mut parsed = Url::parse(url) | ||
| 653 | .or_else(|_| Url::parse(&format!("https://{url}"))) | ||
| 654 | .context(format!("{url} not a valid ngit relay URL"))?; | ||
| 655 | if parsed.host_str().is_none() { | ||
| 656 | // so sub.domain.org gets identifier as host in "sub.domain.org" | ||
| 657 | parsed = Url::parse(&format!("https://{url}"))?; | ||
| 658 | } | ||
| 659 | |||
| 660 | // Extract the scheme, host, port, and path | ||
| 661 | let scheme = parsed.scheme(); | ||
| 662 | let host = parsed.host_str().context(format!( | ||
| 663 | "{url} not a ngit relay url reference: missing host in URL {parsed}" | ||
| 664 | ))?; | ||
| 665 | let port = parsed.port().map(|p| format!(":{p}")).unwrap_or_default(); | ||
| 666 | let path = parsed.path(); | ||
| 667 | |||
| 668 | // Normalize the URL based on the scheme and path | ||
| 669 | let mut normalized_url = match scheme { | ||
| 670 | "ws" | "http" => format!("http://{host}{port}{path}"), | ||
| 671 | _ => format!("{host}{port}{path}"), | ||
| 672 | }; | ||
| 673 | |||
| 674 | // If the normalized URL contains "npub1", remove "npub1" and everything after | ||
| 675 | // it | ||
| 676 | if let Some(pos) = normalized_url.find("npub1") { | ||
| 677 | normalized_url.truncate(pos); // Keep everything before "npub1" | ||
| 678 | } | ||
| 679 | // Return the normalized URL | ||
| 680 | Ok(normalized_url.trim_end_matches('/').to_string()) | ||
| 681 | } | ||
| 682 | |||
| 683 | pub fn extract_npub(s: &str) -> Result<&str> { | ||
| 684 | // Find the starting index of "npub1" | ||
| 685 | if let Some(start) = s.find("npub1") { | ||
| 686 | let mut end = start + 5; // Start after "npub1" | ||
| 687 | |||
| 688 | // Move the end index to include valid characters (0-9, a-z) | ||
| 689 | while end < s.len() && s[end..=end].chars().all(|c| c.is_ascii_alphanumeric()) { | ||
| 690 | end += 1; | ||
| 691 | } | ||
| 692 | // Extract the npub substring | ||
| 693 | let npub = &s[start..end]; | ||
| 694 | // Attempt to create a PublicKey from the extracted npub | ||
| 695 | PublicKey::from_bech32(npub).context("invalid npub")?; | ||
| 696 | Ok(npub) | ||
| 697 | } else { | ||
| 698 | bail!("No npub found") | ||
| 699 | } | ||
| 700 | } | ||
| 701 | |||
| 702 | pub fn is_ngit_relay(url: &str, ngit_relays: &[String]) -> bool { | ||
| 703 | if !ngit_relays.is_empty() { | ||
| 704 | if let Ok(n) = normalize_ngit_relay_url(url) { | ||
| 705 | return ngit_relays.contains(&n); | ||
| 706 | } | ||
| 707 | } | ||
| 708 | false | ||
| 709 | } | ||
| 710 | |||
| 568 | #[cfg(test)] | 711 | #[cfg(test)] |
| 569 | mod tests { | 712 | mod tests { |
| 570 | use test_utils::*; | 713 | use test_utils::*; |
| @@ -842,4 +985,41 @@ mod tests { | |||
| 842 | } | 985 | } |
| 843 | } | 986 | } |
| 844 | } | 987 | } |
| 988 | |||
| 989 | #[test] | ||
| 990 | fn normalize_ngit_relay_url_all_checks() -> Result<()> { | ||
| 991 | let test_cases = vec![ | ||
| 992 | ("https://sub.domain.org", "sub.domain.org"), | ||
| 993 | ("wss://sub.domain.org", "sub.domain.org"), | ||
| 994 | ("sub.domain.org", "sub.domain.org"), | ||
| 995 | ("http://sub.domain.org", "http://sub.domain.org"), | ||
| 996 | ("ws://sub.domain.org", "http://sub.domain.org"), | ||
| 997 | ("http://localhost", "http://localhost"), | ||
| 998 | ("localhost", "localhost"), | ||
| 999 | ("https://sub.domain.org:8080", "sub.domain.org:8080"), | ||
| 1000 | ("http://sub.domain.org:8080", "http://sub.domain.org:8080"), | ||
| 1001 | ("sub.domain.org:8080", "sub.domain.org:8080"), | ||
| 1002 | ("https://sub.domain.org/path/to", "sub.domain.org/path/to"), | ||
| 1003 | ( | ||
| 1004 | "https://sub.domain.org:8080/path/to", | ||
| 1005 | "sub.domain.org:8080/path/to", | ||
| 1006 | ), | ||
| 1007 | ( | ||
| 1008 | "https://sub.domain.org/npub143675782648/to.git", | ||
| 1009 | "sub.domain.org", | ||
| 1010 | ), | ||
| 1011 | ( | ||
| 1012 | "https://sub.domain.org/path/npub143675782648/to.git", | ||
| 1013 | "sub.domain.org/path", | ||
| 1014 | ), | ||
| 1015 | ("https://sub.domain.org/", "sub.domain.org"), | ||
| 1016 | ("http://sub.domain.org/", "http://sub.domain.org"), | ||
| 1017 | ]; | ||
| 1018 | |||
| 1019 | for (input, expected) in test_cases { | ||
| 1020 | let normalized = normalize_ngit_relay_url(input)?; | ||
| 1021 | assert_eq!(normalized, expected); | ||
| 1022 | } | ||
| 1023 | Ok(()) | ||
| 1024 | } | ||
| 845 | } | 1025 | } |