fetch.rs 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. use crate::core::{ProfileExtra, ProfileResponse};
  2. use std::{
  3. str::FromStr,
  4. time::{SystemTime, UNIX_EPOCH},
  5. };
  6. /// parse the string
  7. fn parse_string<T: FromStr>(target: &str, key: &str) -> Option<T> {
  8. match target.find(key) {
  9. Some(idx) => {
  10. let idx = idx + key.len();
  11. let value = &target[idx..];
  12. match match value.split(';').nth(0) {
  13. Some(value) => value.trim().parse(),
  14. None => value.trim().parse(),
  15. } {
  16. Ok(r) => Some(r),
  17. Err(_) => None,
  18. }
  19. }
  20. None => None,
  21. }
  22. }
  23. /// fetch and parse the profile
  24. pub async fn fetch_profile(url: &str, with_proxy: bool) -> Option<ProfileResponse> {
  25. let builder = reqwest::ClientBuilder::new();
  26. let client = match with_proxy {
  27. true => builder.build(),
  28. false => builder.no_proxy().build(),
  29. };
  30. let resp = match client {
  31. Ok(client) => match client.get(url).send().await {
  32. Ok(res) => res,
  33. Err(_) => return None,
  34. },
  35. Err(_) => return None,
  36. };
  37. let header = resp.headers();
  38. // parse the Subscription Userinfo
  39. let extra = {
  40. let sub_info = match header.get("Subscription-Userinfo") {
  41. Some(value) => value.to_str().unwrap_or(""),
  42. None => "",
  43. };
  44. ProfileExtra {
  45. upload: parse_string(sub_info, "upload=").unwrap_or(0),
  46. download: parse_string(sub_info, "download=").unwrap_or(0),
  47. total: parse_string(sub_info, "total=").unwrap_or(0),
  48. expire: parse_string(sub_info, "expire=").unwrap_or(0),
  49. }
  50. };
  51. // parse the `name` and `file`
  52. let (name, file) = {
  53. let now = SystemTime::now()
  54. .duration_since(UNIX_EPOCH)
  55. .unwrap()
  56. .as_secs();
  57. let file = format!("{}.yaml", now);
  58. let name = header.get("Content-Disposition").unwrap().to_str().unwrap();
  59. let name = parse_string::<String>(name, "filename=");
  60. match name {
  61. Some(f) => (f, file),
  62. None => (file.clone(), file),
  63. }
  64. };
  65. // get the data
  66. let data = match resp.text_with_charset("utf-8").await {
  67. Ok(d) => d,
  68. Err(_) => return None,
  69. };
  70. Some(ProfileResponse {
  71. file,
  72. name,
  73. data,
  74. extra,
  75. })
  76. }
  77. #[test]
  78. fn test_parse_value() {
  79. let test_1 = "upload=111; download=2222; total=3333; expire=444";
  80. let test_2 = "attachment; filename=Clash.yaml";
  81. assert_eq!(parse_string::<usize>(test_1, "upload=").unwrap(), 111);
  82. assert_eq!(parse_string::<usize>(test_1, "download=").unwrap(), 2222);
  83. assert_eq!(parse_string::<usize>(test_1, "total=").unwrap(), 3333);
  84. assert_eq!(parse_string::<usize>(test_1, "expire=").unwrap(), 444);
  85. assert_eq!(
  86. parse_string::<String>(test_2, "filename=").unwrap(),
  87. format!("Clash.yaml")
  88. );
  89. assert_eq!(parse_string::<usize>(test_1, "aaa="), None);
  90. assert_eq!(parse_string::<usize>(test_1, "upload1="), None);
  91. assert_eq!(parse_string::<usize>(test_1, "expire1="), None);
  92. assert_eq!(parse_string::<usize>(test_2, "attachment="), None);
  93. }