Added IDN support

This commit is contained in:
Slatian 2023-02-23 00:58:38 +01:00
parent e3054e0158
commit d202ebb14e
5 changed files with 104 additions and 8 deletions

1
Cargo.lock generated
View File

@ -1190,6 +1190,7 @@ dependencies = [
"axum",
"axum-client-ip",
"clap",
"idna 0.3.0",
"lazy_static",
"maxminddb",
"regex",

View File

@ -10,6 +10,7 @@ authors = ["Slatian <baschdel@disroot.org>"]
axum = { version = "0.6", features = ["macros", "headers"] }
axum-client-ip = "0.4"
clap = { version = "4", features = ["derive"] }
idna = "0.3"
lazy_static = "1.4.0"
regex = "1.7"
serde = { version = "1", features = ["derive"] }

76
src/idna.rs Normal file
View File

@ -0,0 +1,76 @@
/*
* The prupose of this module is to analyse a given string
* for being a IDNA domain name and decodes it,
* it is also able to return an encoded form if given unicode.
* The result is supposed to be used in a template
*/
use serde::{Deserialize, Serialize};
use ::idna;
#[derive(Deserialize, Serialize, Copy, Default, Clone, PartialEq)]
#[serde(rename_all="lowercase")]
pub enum NameType {
Ascii,
#[default]
Unicode,
IDNA,
}
// Note, that the
#[derive(Deserialize, Serialize, Default, Clone)]
pub struct IdnaName {
pub unicode: String,
// if null the unicode version only contains ascii range chars,
// not neccessary to encode
pub idna: Option<String>,
pub original_was: NameType,
#[serde(skip_serializing_if = "Option::is_none")]
pub decoder_error: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub encoder_error: Option<String>,
}
impl IdnaName {
pub fn from_string(s: &String) -> Self {
let mut original_was = NameType::Unicode;
let unicode: String;
let decoder_error;
if s.starts_with("xn--") && s.is_ascii() {
original_was = NameType::IDNA;
let (uc, ures) = idna::domain_to_unicode(s);
unicode = uc;
decoder_error = ures.map_or_else(|e| Some(e.to_string()), |_| None);
} else {
unicode = s.clone();
decoder_error = None;
};
let (idna, encoder_error) = match idna::domain_to_ascii_strict(s) {
Ok(idna) => {
if &idna != s || original_was == NameType::IDNA{
(Some(idna), None)
} else {
original_was = NameType::Ascii;
(None, None)
}
},
Err(e) => {
(None, Some(e.to_string()))
}
};
IdnaName {
unicode: unicode,
idna: idna,
original_was: original_was,
decoder_error: decoder_error,
encoder_error: encoder_error,
}
}
pub fn was_ascii(&self) -> bool {
return self.original_was == NameType::Ascii;
}
}

View File

@ -35,11 +35,15 @@ mod geoip;
mod ipinfo;
mod simple_dns;
mod templating_engine;
mod idna;
use crate::geoip::QueryAsn;
use crate::geoip::QueryLocation;
use geoip::AsnResult;
use geoip::LocationResult;
use crate::geoip::{
QueryAsn,
QueryLocation,
AsnResult,
LocationResult,
};
use crate::idna::IdnaName;
use crate::templating_engine::{
View,
@ -69,6 +73,13 @@ pub struct IpResult {
ip_info: AddressInfo,
}
#[derive(serde::Deserialize, serde::Serialize, Default, Clone)]
pub struct DigResult {
records: simple_dns::DnsLookupResult,
#[serde(skip_serializing_if = "IdnaName::was_ascii")]
idna: IdnaName,
}
struct ServiceSharedState {
templating_engine: templating_engine::Engine,
dns_resolver: TokioAsyncResolver,
@ -467,11 +478,18 @@ async fn handle_dig_request(
async fn get_dig_result(
dig_query: &String,
state: &ServiceSharedState,
) -> simple_dns::DnsLookupResult {
) -> DigResult {
let name = &dig_query.trim().trim_end_matches(".").to_string();
if match_domain_hidden_list(&name, &state.config.dns.hidden_suffixes) {
Default::default()
} else {
simple_dns::lookup(&state.dns_resolver, name, true).await
let idna_name = IdnaName::from_string(&name);
DigResult {
records: simple_dns::lookup(
&state.dns_resolver,
&(idna_name.idna.clone().unwrap_or(name.to_owned())+"."),
true).await,
idna: idna_name,
}
}
}

View File

@ -13,7 +13,7 @@ use axum::{
use tera::Tera;
use toml::Table;
use crate::simple_dns;
use crate::DigResult;
use crate::IpResult;
/* Response format */
@ -52,7 +52,7 @@ pub struct TemplateSettings {
#[serde(untagged)]
pub enum View {
Asn { asn: u32 },
Dig { query: String, result: simple_dns::DnsLookupResult },
Dig { query: String, result: DigResult },
Index { result: IpResult, user_agent: Option<String> },
Ip { result: IpResult },
Message{ title: String, message: String },