handle access denied
This commit is contained in:
		| @@ -1,4 +1,3 @@ | ||||
| use reqwest::header::ToStrError; | ||||
| use thiserror::Error; | ||||
|  | ||||
| /// The error type used globally by the library. | ||||
| @@ -12,11 +11,15 @@ pub enum Error { | ||||
|     UnexpectedResponse(String), | ||||
|     #[error("you are not logged in, or perhaps the session has expired")] | ||||
|     NotAuthorized, | ||||
|     #[error("access denied, most likely someone else is already logged in")] | ||||
|     AccessDenied, | ||||
|     #[error("an unexpected redirection has occurred: {0:?}")] | ||||
|     UnexpectedRedirect(String), | ||||
|  | ||||
|     #[error(transparent)] | ||||
|     URLParseError(#[from] url::ParseError), | ||||
|     #[error(transparent)] | ||||
|     InvalidHeaderValue(#[from] ToStrError), | ||||
|     InvalidHeaderValue(#[from] reqwest::header::ToStrError), | ||||
|     #[error(transparent)] | ||||
|     HttpError(#[from] reqwest::Error), | ||||
|     #[error(transparent)] | ||||
|   | ||||
| @@ -6,7 +6,7 @@ pub use error::Error; | ||||
| use reqwest::{ | ||||
|     cookie::{CookieStore, Jar}, | ||||
|     redirect::Policy, | ||||
|     Client, Url, | ||||
|     Client, Url, header::HeaderValue, | ||||
| }; | ||||
| use serde::de::DeserializeOwned; | ||||
|  | ||||
| @@ -15,7 +15,7 @@ mod functions; | ||||
| /// Data structures used by the library. | ||||
| pub mod models; | ||||
|  | ||||
| // A Result type based on the library's Error | ||||
| /// A Result type based on the library's Error | ||||
| pub type Result<T> = std::result::Result<T, error::Error>; | ||||
|  | ||||
| type Field<'a, 'b> = (Cow<'a, str>, Cow<'b, str>); | ||||
| @@ -85,7 +85,7 @@ impl ConnectBox { | ||||
|             if resp.status().is_redirection() { | ||||
|                 if self.auto_reauth && !reauthed { | ||||
|                     reauthed = true; | ||||
|                     tracing::debug!("session has expired, attempting reauth"); | ||||
|                     tracing::info!("session <{}> has expired, attempting reauth", self.cookie("SID")?.as_deref().unwrap_or("unknown")); | ||||
|                     self._login().await?; | ||||
|                     continue; | ||||
|                 } | ||||
| @@ -117,7 +117,7 @@ impl ConnectBox { | ||||
|             if resp.status().is_redirection() { | ||||
|                 if self.auto_reauth && !reauthed { | ||||
|                     reauthed = true; | ||||
|                     tracing::debug!("session has expired, attempting reauth"); | ||||
|                     tracing::info!("session <{}> has expired, attempting reauth", self.cookie("SID")?.as_deref().unwrap_or("unknown")); | ||||
|                     self._login().await?; | ||||
|                     continue; | ||||
|                 } | ||||
| @@ -136,13 +136,25 @@ impl ConnectBox { | ||||
|             ("Password".into(), (&self.code).into()), | ||||
|         ]; | ||||
|         let req = self.http.post(self.setter_url.clone()).form(&form); | ||||
|         let response = req.send().await?.text().await?; | ||||
|         if response == "idloginincorrect" { | ||||
|         let resp = req.send().await?; | ||||
|         if resp.status().is_redirection() { | ||||
|             if let Some(location) = resp.headers().get("Location").map(HeaderValue::to_str) { | ||||
|                 let location = location?; | ||||
|                 return if location == "../common_page/Access-denied.html" { | ||||
|                     Err(Error::AccessDenied) | ||||
|                 } else { | ||||
|                     Err(Error::UnexpectedRedirect(location.to_string())) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         let resp_text = resp.text().await?; | ||||
|         if resp_text == "idloginincorrect" { | ||||
|             return Err(Error::IncorrectCode); | ||||
|         } | ||||
|         let sid = response | ||||
|         let sid = resp_text | ||||
|             .strip_prefix("successful;SID=") | ||||
|             .ok_or_else(|| Error::UnexpectedResponse(response.clone()))?; | ||||
|             .ok_or_else(|| Error::UnexpectedResponse(resp_text.clone()))?; | ||||
|         tracing::info!("session <{sid}>: logged in successfully"); | ||||
|         self.cookie_store | ||||
|             .add_cookie_str(&format!("SID={sid}"), &self.base_url); | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| use std::net::Ipv4Addr; | ||||
| use std::time::Duration; | ||||
|  | ||||
| use serde::de::Error; | ||||
| use serde::de::{Error, self, Unexpected}; | ||||
| use serde::{Deserialize, Deserializer}; | ||||
|  | ||||
| #[derive(Deserialize, Debug)] | ||||
| @@ -19,21 +20,94 @@ pub struct LanUserTable { | ||||
|  | ||||
| #[derive(Deserialize, Debug)] | ||||
| pub struct ClientInfo { | ||||
|     pub index: u32, | ||||
|     pub interface: String, | ||||
|     #[serde(rename = "interfaceid")] | ||||
|     pub interface_id: u32, | ||||
|     #[serde(rename = "IPv4Addr")] | ||||
|     pub ipv4_addr: String, | ||||
|     pub index: u32, | ||||
|     #[serde(rename = "interfaceid")] | ||||
|     pub interface_id: u32, | ||||
|     pub hostname: String, | ||||
|     #[serde(rename = "MACAddr")] | ||||
|     pub mac: String, | ||||
|     pub method: u32, | ||||
|     #[serde(rename = "leaseTime")] | ||||
|     #[serde(deserialize_with = "deserialize_lease_time")] | ||||
|     pub lease_time: Duration, | ||||
|     pub speed: u32, | ||||
| } | ||||
|  | ||||
| #[derive(Deserialize, Debug)] | ||||
| pub struct PortForwards { | ||||
|     #[serde(rename = "LanIP")] | ||||
|     pub lan_ip: Ipv4Addr, | ||||
|     #[serde(rename = "subnetmask")] | ||||
|     pub subnet_mask: Ipv4Addr, | ||||
|     #[serde(rename = "instance")] | ||||
|     #[serde(deserialize_with = "unwrap_xml_list")] | ||||
|     pub entries: Vec<PortForwardEntry>, | ||||
| } | ||||
|  | ||||
| #[derive(Deserialize, Debug)] | ||||
| pub struct PortForwardEntry { | ||||
|     pub id: u32, | ||||
|     #[serde(rename = "local_IP")] | ||||
|     pub local_ip: Ipv4Addr, | ||||
|     pub start_port: u16, | ||||
|     pub end_port: u16, | ||||
|     #[serde(rename = "start_portIn")] | ||||
|     pub start_port_in: u16, | ||||
|     #[serde(rename = "end_portIn")] | ||||
|     pub end_port_in: u16, | ||||
|     pub protocol: PortForwardProtocol, | ||||
|     #[serde(deserialize_with = "bool_from_int")] | ||||
|     pub enable: bool | ||||
| } | ||||
|  | ||||
| #[derive(Debug)] | ||||
| pub enum PortForwardProtocol { | ||||
|     Tcp, | ||||
|     Udp, | ||||
|     Both, | ||||
| } | ||||
|  | ||||
| impl PortForwardProtocol { | ||||
|     fn id(&self) -> u8 { | ||||
|         match self { | ||||
|             PortForwardProtocol::Tcp => 1, | ||||
|             PortForwardProtocol::Udp => 2, | ||||
|             PortForwardProtocol::Both => 3, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'de> Deserialize<'de> for PortForwardProtocol { | ||||
|     fn deserialize<D>(d: D) -> Result<Self, D::Error> | ||||
|     where | ||||
|         D: Deserializer<'de>, | ||||
|     { | ||||
|         match u8::deserialize(d)? { | ||||
|             1 => Ok(Self::Tcp), | ||||
|             2 => Ok(Self::Udp), | ||||
|             3 => Ok(Self::Both), | ||||
|             _ => Err(D::Error::custom("protocol not in range 1..=3")), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn bool_from_int<'de, D>(deserializer: D) -> Result<bool, D::Error> | ||||
| where | ||||
|     D: Deserializer<'de>, | ||||
| { | ||||
|     match u8::deserialize(deserializer)? { | ||||
|         0 => Ok(false), | ||||
|         1 => Ok(true), | ||||
|         other => Err(de::Error::invalid_value( | ||||
|             Unexpected::Unsigned(other as u64), | ||||
|             &"zero or one", | ||||
|         )), | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Deserialize)] | ||||
| struct List<T> { | ||||
|     #[serde(rename = "$value")] | ||||
|   | ||||
		Reference in New Issue
	
	Block a user