pacsea/util/srcinfo.rs
1//! .SRCINFO fetching utilities for AUR packages.
2//!
3//! This module provides functions for fetching .SRCINFO files from the AUR,
4//! with support for both synchronous (curl) and asynchronous (reqwest) fetching.
5
6use crate::util::{curl, percent_encode};
7
8/// What: Fetch .SRCINFO content for an AUR package synchronously using curl.
9///
10/// Inputs:
11/// - `name`: AUR package name.
12/// - `timeout_seconds`: Optional timeout in seconds (None = no timeout).
13///
14/// Output:
15/// - Returns .SRCINFO content as a string, or an error if fetch fails.
16///
17/// # Errors
18/// - Returns `Err` when network request fails (curl execution error)
19/// - Returns `Err` when .SRCINFO cannot be fetched from AUR
20/// - Returns `Err` when response is empty or contains HTML error page
21/// - Returns `Err` when response does not appear to be valid .SRCINFO format
22///
23/// Details:
24/// - Downloads .SRCINFO from AUR cgit repository.
25/// - Validates that the response is not empty, not HTML, and contains .SRCINFO format markers.
26pub fn fetch_srcinfo(name: &str, timeout_seconds: Option<u64>) -> Result<String, String> {
27 let url = format!(
28 "https://aur.archlinux.org/cgit/aur.git/plain/.SRCINFO?h={}",
29 percent_encode(name)
30 );
31 tracing::debug!("Fetching .SRCINFO from: {}", url);
32
33 let text = if let Some(timeout) = timeout_seconds {
34 let timeout_str = timeout.to_string();
35 curl::curl_text_with_args(&url, &["--max-time", &timeout_str])
36 .map_err(|e| format!("curl failed: {e}"))?
37 } else {
38 curl::curl_text(&url).map_err(|e| format!("curl failed: {e}"))?
39 };
40
41 if text.trim().is_empty() {
42 return Err("Empty .SRCINFO content".to_string());
43 }
44
45 // Check if we got an HTML error page instead of .SRCINFO content
46 if text.trim_start().starts_with("<html") || text.trim_start().starts_with("<!DOCTYPE") {
47 return Err("Received HTML error page instead of .SRCINFO".to_string());
48 }
49
50 // Validate that it looks like .SRCINFO format (should have pkgbase or pkgname)
51 if !text.contains("pkgbase =") && !text.contains("pkgname =") {
52 return Err("Response does not appear to be valid .SRCINFO format".to_string());
53 }
54
55 Ok(text)
56}
57
58/// What: Fetch .SRCINFO content for an AUR package using async HTTP.
59///
60/// Inputs:
61/// - `client`: Reqwest HTTP client.
62/// - `name`: AUR package name.
63///
64/// Output:
65/// - Returns .SRCINFO content as a string, or an error if fetch fails.
66///
67/// # Errors
68/// - Returns `Err` when HTTP request fails (network error or client error)
69/// - Returns `Err` when HTTP response status is not successful
70/// - Returns `Err` when response body cannot be read
71/// - Returns `Err` when response is empty or contains HTML error page
72///
73/// Details:
74/// - Uses reqwest for async fetching with built-in timeout handling.
75/// - Validates that the response is not empty and not HTML.
76pub async fn fetch_srcinfo_async(client: &reqwest::Client, name: &str) -> Result<String, String> {
77 let url = format!(
78 "https://aur.archlinux.org/cgit/aur.git/plain/.SRCINFO?h={}",
79 percent_encode(name)
80 );
81 tracing::debug!("Fetching .SRCINFO from: {}", url);
82
83 let response = client
84 .get(&url)
85 .send()
86 .await
87 .map_err(|e| format!("HTTP request failed: {e}"))?;
88
89 if !response.status().is_success() {
90 return Err(format!(
91 "HTTP request failed with status: {}",
92 response.status()
93 ));
94 }
95
96 let text = response
97 .text()
98 .await
99 .map_err(|e| format!("Failed to read response body: {e}"))?;
100
101 if text.trim().is_empty() {
102 return Err("Empty .SRCINFO content".to_string());
103 }
104
105 // Check if we got an HTML error page instead of .SRCINFO content
106 if text.trim_start().starts_with("<html") || text.trim_start().starts_with("<!DOCTYPE") {
107 return Err("Received HTML error page instead of .SRCINFO".to_string());
108 }
109
110 Ok(text)
111}