1
0
Fork 0
icann-rdap/icann-rdap-client/src/md/entity.rs
Daniel Baumann b06d3acde8
Adding upstream version 0.0.22.
Signed-off-by: Daniel Baumann <daniel@debian.org>
2025-05-08 18:41:54 +02:00

359 lines
13 KiB
Rust

use std::any::TypeId;
use icann_rdap_common::{
contact::{NameParts, PostalAddress},
response::{Entity, EntityRole},
};
use icann_rdap_common::check::{CheckParams, GetChecks, GetSubChecks};
use crate::rdap::registered_redactions::{
are_redactions_registered_for_roles, is_redaction_registered_for_role,
text_or_registered_redaction_for_role, RedactedName,
};
use super::{
redacted::REDACTED_TEXT,
string::StringUtil,
table::{MultiPartTable, ToMpTable},
types::{checks_to_table, public_ids_to_table},
FromMd, MdHeaderText, MdParams, MdUtil, ToMd, HR,
};
impl ToMd for Entity {
fn to_md(&self, params: MdParams) -> String {
let typeid = TypeId::of::<Self>();
let mut md = String::new();
md.push_str(&self.common.to_md(params.from_parent(typeid)));
// header
let header_text = self.get_header_text();
md.push_str(
&header_text
.to_string()
.to_header(params.heading_level, params.options),
);
// A note about the RFC 9537 redactions. A lot of this code is to do RFC 9537 redactions
// that are registered with the IANA. As RFC 9537 is horribly broken, it is likely only
// gTLD registries will use registered redactions, and when they do they will use all
// of them. Therefore, as horribly complicated as this logic is, it attempts to simplify
// things by assuming all the registrations will be used at once, which will be the case
// in the gTLD space.
// check if registrant or tech ids are RFC 9537 redacted
let mut entity_handle = text_or_registered_redaction_for_role(
params.root,
&RedactedName::RegistryRegistrantId,
self,
&EntityRole::Registrant,
&self.object_common.handle,
REDACTED_TEXT,
);
entity_handle = text_or_registered_redaction_for_role(
params.root,
&RedactedName::RegistryTechId,
self,
&EntityRole::Technical,
&entity_handle,
REDACTED_TEXT,
);
// multipart data
let mut table = MultiPartTable::new();
// summary
table = table.summary(header_text);
// identifiers
table = table
.header_ref(&"Identifiers")
.and_nv_ref(&"Handle", &entity_handle)
.and_nv_ul(&"Roles", Some(self.roles().to_vec()));
if let Some(public_ids) = &self.public_ids {
table = public_ids_to_table(public_ids, table);
}
if let Some(contact) = self.contact() {
// nutty RFC 9537 redaction stuff
// check if registrant or tech name are redacted
let mut registrant_name = text_or_registered_redaction_for_role(
params.root,
&RedactedName::RegistrantName,
self,
&EntityRole::Registrant,
&contact.full_name,
REDACTED_TEXT,
);
registrant_name = text_or_registered_redaction_for_role(
params.root,
&RedactedName::TechName,
self,
&EntityRole::Technical,
&registrant_name,
REDACTED_TEXT,
);
// check to see if registrant postal address parts are redacted
let postal_addresses = if are_redactions_registered_for_roles(
params.root,
&[
&RedactedName::RegistrantStreet,
&RedactedName::RegistrantCity,
&RedactedName::RegistrantPostalCode,
],
self,
&[&EntityRole::Registrant],
) {
let mut new_pas = contact.postal_addresses.clone();
if let Some(ref mut new_pas) = new_pas {
new_pas.iter_mut().for_each(|pa| {
pa.street_parts = Some(vec![REDACTED_TEXT.to_string()]);
pa.locality = Some(REDACTED_TEXT.to_string());
pa.postal_code = Some(REDACTED_TEXT.to_string());
})
}
new_pas
} else {
contact.postal_addresses
};
table = table
.header_ref(&"Contact")
.and_nv_ref_maybe(&"Kind", &contact.kind)
.and_nv_ref_maybe(&"Full Name", &registrant_name)
.and_nv_ul(&"Titles", contact.titles)
.and_nv_ul(&"Org Roles", contact.roles)
.and_nv_ul(&"Nicknames", contact.nick_names);
if is_redaction_registered_for_role(
params.root,
&RedactedName::RegistrantOrganization,
self,
&EntityRole::Registrant,
) {
table = table.nv_ref(&"Organization Name", &REDACTED_TEXT.to_string());
} else {
table = table.and_nv_ul(&"Organization Names", contact.organization_names);
}
table = table.and_nv_ul(&"Languages", contact.langs);
if are_redactions_registered_for_roles(
params.root,
&[
&RedactedName::RegistrantPhone,
&RedactedName::RegistrantPhoneExt,
&RedactedName::RegistrantFax,
&RedactedName::RegistrantFaxExt,
&RedactedName::TechPhone,
&RedactedName::TechPhoneExt,
],
self,
&[&EntityRole::Registrant, &EntityRole::Technical],
) {
table = table.nv_ref(&"Phones", &REDACTED_TEXT.to_string());
} else {
table = table.and_nv_ul(&"Phones", contact.phones);
}
if are_redactions_registered_for_roles(
params.root,
&[&RedactedName::TechEmail, &RedactedName::RegistrantEmail],
self,
&[&EntityRole::Registrant, &EntityRole::Technical],
) {
table = table.nv_ref(&"Emails", &REDACTED_TEXT.to_string());
} else {
table = table.and_nv_ul(&"Emails", contact.emails);
}
table = table
.and_nv_ul(&"Web Contact", contact.contact_uris)
.and_nv_ul(&"URLs", contact.urls);
table = postal_addresses.add_to_mptable(table, params);
table = contact.name_parts.add_to_mptable(table, params)
}
// common object stuff
table = self.object_common.add_to_mptable(table, params);
// checks
let check_params = CheckParams::from_md(params, typeid);
let mut checks = self.object_common.get_sub_checks(check_params);
checks.push(self.get_checks(check_params));
table = checks_to_table(checks, table, params);
// render table
md.push_str(&table.to_md(params));
// remarks
md.push_str(&self.object_common.remarks.to_md(params.from_parent(typeid)));
// only other object classes from here
md.push_str(HR);
// entities
md.push_str(
&self
.object_common
.entities
.to_md(params.from_parent(typeid)),
);
// redacted
if let Some(redacted) = &self.object_common.redacted {
md.push_str(&redacted.as_slice().to_md(params.from_parent(typeid)));
}
md.push('\n');
md
}
}
impl ToMd for Option<Vec<Entity>> {
fn to_md(&self, params: MdParams) -> String {
let mut md = String::new();
if let Some(entities) = &self {
entities
.iter()
.for_each(|entity| md.push_str(&entity.to_md(params.next_level())));
}
md
}
}
impl ToMpTable for Option<Vec<PostalAddress>> {
fn add_to_mptable(&self, mut table: MultiPartTable, params: MdParams) -> MultiPartTable {
if let Some(addrs) = self {
for addr in addrs {
table = addr.add_to_mptable(table, params);
}
}
table
}
}
impl ToMpTable for PostalAddress {
fn add_to_mptable(&self, mut table: MultiPartTable, _params: MdParams) -> MultiPartTable {
if self.contexts.is_some() && self.preference.is_some() {
table = table.nv(
&"Address",
format!(
"{} (pref: {})",
self.contexts.as_ref().unwrap().join(" "),
self.preference.unwrap()
),
);
} else if self.contexts.is_some() {
table = table.nv(&"Address", self.contexts.as_ref().unwrap().join(" "));
} else if self.preference.is_some() {
table = table.nv(
&"Address",
format!("preference: {}", self.preference.unwrap()),
);
} else {
table = table.nv(&"Address", "");
}
if let Some(street_parts) = &self.street_parts {
table = table.nv_ul_ref(&"Street", street_parts.iter().collect());
}
if let Some(locality) = &self.locality {
table = table.nv_ref(&"Locality", locality);
}
if self.region_name.is_some() && self.region_code.is_some() {
table = table.nv(
&"Region",
format!(
"{} ({})",
self.region_name.as_ref().unwrap(),
self.region_code.as_ref().unwrap()
),
);
} else if let Some(region_name) = &self.region_name {
table = table.nv_ref(&"Region", region_name);
} else if let Some(region_code) = &self.region_code {
table = table.nv_ref(&"Region", region_code);
}
if self.country_name.is_some() && self.country_code.is_some() {
table = table.nv(
&"Country",
format!(
"{} ({})",
self.country_name.as_ref().unwrap(),
self.country_code.as_ref().unwrap()
),
);
} else if let Some(country_name) = &self.country_name {
table = table.nv_ref(&"Country", country_name);
} else if let Some(country_code) = &self.country_code {
table = table.nv_ref(&"Country", country_code);
}
if let Some(postal_code) = &self.postal_code {
table = table.nv_ref(&"Postal Code", postal_code);
}
if let Some(full_address) = &self.full_address {
let parts = full_address.split('\n').collect::<Vec<&str>>();
for (i, p) in parts.iter().enumerate() {
table = table.nv_ref(&i.to_string(), p);
}
}
table
}
}
impl ToMpTable for Option<NameParts> {
fn add_to_mptable(&self, mut table: MultiPartTable, _params: MdParams) -> MultiPartTable {
if let Some(parts) = self {
if let Some(prefixes) = &parts.prefixes {
table = table.nv(&"Honorifics", prefixes.join(", "));
}
if let Some(given_names) = &parts.given_names {
table = table.nv_ul(&"Given Names", given_names.to_vec());
}
if let Some(middle_names) = &parts.middle_names {
table = table.nv_ul(&"Middle Names", middle_names.to_vec());
}
if let Some(surnames) = &parts.surnames {
table = table.nv_ul(&"Surnames", surnames.to_vec());
}
if let Some(suffixes) = &parts.suffixes {
table = table.nv(&"Suffixes", suffixes.join(", "));
}
}
table
}
}
impl MdUtil for Entity {
fn get_header_text(&self) -> MdHeaderText {
let role = self
.roles()
.first()
.map(|s| s.replace_md_chars().to_title_case());
let header_text = if let Some(handle) = &self.object_common.handle {
if let Some(role) = role {
format!("{} ({})", handle.replace_md_chars(), role)
} else {
format!("Entity {}", handle)
}
} else if let Some(role) = role {
role.to_string()
} else {
"Entity".to_string()
};
let mut header_text = MdHeaderText::builder().header_text(header_text);
if let Some(entities) = &self.object_common.entities {
for entity in entities {
header_text = header_text.children_entry(entity.get_header_text());
}
};
if let Some(networks) = &self.networks {
for network in networks {
header_text = header_text.children_entry(network.get_header_text());
}
};
if let Some(autnums) = &self.autnums {
for autnum in autnums {
header_text = header_text.children_entry(autnum.get_header_text());
}
};
header_text.build()
}
}