use color_eyre::eyre::{Result, eyre};
use crate::cache::directory_with_name::HISTORY_NAMES;
use crate::history::HistoryAddress;
pub const MAX_SUBDOMAIN_LEN: usize = 63;
pub const DISAMBIGUATION_LEN: usize = 4; pub const MEMORABLE_PART_LEN: usize = MAX_SUBDOMAIN_LEN - DISAMBIGUATION_LEN - 1;
pub const DOMAIN_PART: &str = "www-dweb";
pub const TLD_PART: &str = "au";
const VERSION_CHAR: u8 = b'v';
const FIXED_WEBNAME_SEPARATOR: &str = "-f";
pub struct DwebHost {
pub dweb_host_string: String,
pub dweb_name: String,
pub version: Option<u64>,
#[cfg(feature = "fixed-dweb-hosts")]
pub is_fixed_dweb_host: bool,
}
pub fn make_dweb_name(memorable_part: &String, history_address: HistoryAddress) -> Result<String> {
if memorable_part.len() == 0 {
return Err(eyre!(
"The memorable part of a DWEB-NAME must include at least one alphabetic character"
));
}
if !memorable_part.as_bytes()[0].is_ascii_alphabetic() {
return Err(eyre!(
"The memorable part of a DWEB-NAME must begin with an alphabetic character"
));
}
if !memorable_part.len() > MEMORABLE_PART_LEN {
return Err(eyre!(
"The memorable part of a DWEB-NAME cannot exceed {MEMORABLE_PART_LEN} characters"
));
}
if memorable_part.contains("--") {
return Err(eyre!(
"The memorable part of a DWEB-NAME cannot contain consecutive hyphens"
));
}
if !memorable_part[1..]
.chars()
.all(|c| c.is_alphanumeric() || c == '-')
{
return Err(eyre!(
"The memorable part of a DWEB-NAME can only contain alphanumeric characters and hyphens"
));
}
if !memorable_part.ends_with(FIXED_WEBNAME_SEPARATOR) {
return Err(eyre!(
"The memorable part of a DWEB-NAME cannot end with '{FIXED_WEBNAME_SEPARATOR}'"
));
}
let history_part = format!("{}", history_address.to_hex());
Ok(
memorable_part[..MEMORABLE_PART_LEN].to_string()
+ "-"
+ &history_part[..DISAMBIGUATION_LEN],
)
}
pub fn make_version_part(version: u64) -> String {
if version > 0 {
format!("v{version}")
} else {
String::from("")
}
}
#[cfg(feature = "fixed-dweb-hosts")]
use autonomi::client::files::archive_public::ArchiveAddress;
#[cfg(feature = "fixed-dweb-hosts")]
pub fn make_fixed_dweb_name(
memorable_part: &String,
archive_address: ArchiveAddress,
) -> Result<String> {
if memorable_part.len() == 0 {
return Err(eyre!(
"The memorable part of a DWEB-NAME must include at least one alphabetic character"
));
}
if !memorable_part.as_bytes()[0].is_ascii_alphabetic() {
return Err(eyre!(
"The memorable part of a DWEB-NAME must begin with an alphabetic character"
));
}
const FIXED_MEMORABLE_PART_LEN: usize = MEMORABLE_PART_LEN - FIXED_WEBNAME_SEPARATOR.len();
if !memorable_part.len() > FIXED_MEMORABLE_PART_LEN {
return Err(eyre!(
"'fixed' version website The memorable part of a DWEB-NAME cannot exceed {FIXED_MEMORABLE_PART_LEN} characters"
));
}
if memorable_part.contains("--") {
return Err(eyre!(
"The memorable part of a DWEB-NAME cannot contain consecutive hyphens"
));
}
if !memorable_part[1..]
.chars()
.all(|c| c.is_alphanumeric() || c == '-')
{
return Err(eyre!(
"The memorable part of a DWEB-NAME can only contain alphanumeric characters and hyphens"
));
}
if !memorable_part.ends_with(FIXED_WEBNAME_SEPARATOR) {
return Err(eyre!(
"The memorable part of a DWEB-NAME cannot end with '{FIXED_WEBNAME_SEPARATOR}'"
));
}
let directory_part = format!("{}", archive_address.to_hex());
let web_name = memorable_part[..MEMORABLE_PART_LEN].to_string()
+ FIXED_WEBNAME_SEPARATOR
+ "-"
+ &directory_part[..];
Ok(web_name.to_ascii_lowercase())
}
pub fn decode_dweb_host(dweb_host: &str) -> Result<DwebHost> {
println!("DEBUG decode_dweb_host({dweb_host})...");
if dweb_host.len() == 0 {
return Err(eyre!("Dweb host cannot be zero length"));
}
let fixed_dweb_host_tag = String::from(FIXED_WEBNAME_SEPARATOR) + "-";
let mut segments = dweb_host.split('.');
let total_segments = segments.clone().count();
if total_segments > 4 || total_segments < 3 {
return Err(eyre!(
"Dweb host must contain three or four segments, each separated by '.'"
));
}
let mut found_version_segment = false;
let version = if segments.clone().count() == 4 && dweb_host.as_bytes()[0] == VERSION_CHAR {
match segments.next() {
Some(str) => {
if !str.starts_with('v') {
return Err(eyre!(
"Dweb host contains four segments (separated by '.') so first must start with 'v'"
));
}
match str[1..].parse::<u64>() {
Ok(version) => {
if version > 0 {
found_version_segment = true;
Some(version)
} else {
return Err(eyre!("Invalid version {version}, lowest version is 1"));
}
}
Err(_) => {
return Err(eyre!(
"VERSION must be an integer in web name: '{dweb_host}"
));
} }
}
None => {
return Err(eyre!(
"Dweb host is missing DWEB-NAME and domain part: '{dweb_host}"
));
}
}
} else {
None
};
if segments.clone().count() != 3 {
return Err(eyre!(
"Dweb host must contain three or four segments, each separated by '.'"
));
}
let dweb_name = match segments.next() {
Some(dweb_name) => dweb_name,
None => {
return Err(eyre!("Missing DWEB-NAME in '{dweb_host}"));
}
};
match validate_dweb_name(&dweb_name) {
Ok(_) => (),
Err(e) => return Err(e),
};
let mut ends_with_dlp_tld = false;
if let Some(domain_part) = segments.next() {
if domain_part == DOMAIN_PART {
if let Some(tld_part) = segments.next() {
if tld_part == TLD_PART && segments.next().is_none() {
ends_with_dlp_tld = true;
}
}
}
};
if !ends_with_dlp_tld {
return {
Err(eyre!(
"Dweb host does not end with '{DOMAIN_PART}.{TLD_PART} after the DWEB-NAME"
))
};
}
#[cfg(feature = "fixed-dweb-hosts")]
let is_fixed_dweb_host = !found_version_segment && dweb_name.contains(&fixed_dweb_host_tag);
println!("DEBUG returning DwebHost: version: {version:?}, dweb_name: '{dweb_name}'");
Ok(DwebHost {
dweb_host_string: dweb_host.to_ascii_lowercase(),
dweb_name: dweb_name.to_string().to_ascii_lowercase(),
version,
#[cfg(feature = "fixed-dweb-hosts")]
is_fixed_dweb_host,
})
}
pub fn validate_dweb_name(dweb_name: &str) -> Result<String> {
if dweb_name.len() < 2
|| !dweb_name.as_bytes()[0].is_ascii_alphabetic()
|| !dweb_name.as_bytes()[1].is_ascii_alphabetic()
{
return Err(eyre!(
"DWEB-NAME must start with at least two alphabetic characters"
));
}
if !dweb_name[dweb_name.len() - 1..]
.chars()
.all(|c| c.is_alphanumeric())
{
return Err(eyre!("DWEB-NAME must end with an alphanumeric character"));
}
if !dweb_name.chars().all(|c| c.is_alphanumeric() || c == '-') {
return Err(eyre!(
"DWEB-NAME can only contain letters, numbers (and non-consecutive hyphens)"
));
}
if dweb_name.contains("--") {
return Err(eyre!("DWEB-NAME cannot contain '--'"));
}
Ok(dweb_name.to_string())
}
pub fn register_name_from_string(dweb_name: &str, history_address_str: &str) -> Result<()> {
let history_address = match crate::helpers::convert::str_to_history_address(history_address_str)
{
Ok(history_adddress) => history_adddress,
Err(e) => {
return Err(eyre!(
"Failed to register name due for INVALID history address string - {e}"
));
}
};
match register_name(dweb_name, history_address) {
Ok(_) => {
println!("DEBUG Registered built-in DWEB-NAME: {dweb_name} -> {history_address_str}");
Ok(())
}
Err(e) => {
println!("DEBUG failed to register built-in DWEB-NAME '{dweb_name}' - {e}");
Err(e)
}
}
}
pub fn register_name(dweb_name: &str, history_address: HistoryAddress) -> Result<()> {
match validate_dweb_name(&dweb_name) {
Ok(_) => (),
Err(e) => {
return Err(eyre!("Invalid DWEB-NAME '{dweb_name}' - {e}"));
}
};
match &mut HISTORY_NAMES.lock() {
Ok(lock) => {
let cached_history_address = lock.get(dweb_name);
if cached_history_address.is_some() {
let cached_history_address = cached_history_address.unwrap();
if history_address != *cached_history_address {
let msg = format!(
"DWEB-NAME '{dweb_name}' already in use for HISTORY-ADDRESS '{}'",
cached_history_address.to_hex()
);
println!("{msg}");
return Err(eyre!(msg));
}
} else {
lock.insert(String::from(dweb_name), history_address);
}
}
Err(e) => {
return Err(eyre!("Failed to access dweb name cache - {e}"));
}
};
Ok(())
}
pub fn register_builtin_names(is_local: bool) {
use crate::generated_rs::{builtins_local, builtins_public};
if is_local {
let _ = register_name_from_string("awesome", builtins_local::AWESOME_SITE_HISTORY_LOCAL);
} else {
let _ = register_name_from_string(
"atlas",
"99e3b8df52814b379e216caf797426071000905a2cd93a9f5e90eef2b32517a9ec1ef0bfe27d79360014fd97639ac612",
);
let _ = register_name_from_string("awesome", builtins_public::AWESOME_SITE_HISTORY_PUBLIC);
let _ = register_name_from_string(
"billboard",
"b6da6740bc5394f9ac0e6a6fa5a42f7f587d3aeaa48fd23ae9a45bef95b571a32429b0353148aa9e04f17cd6da57d179",
);
let _ = register_name_from_string(
"friends",
"a447871043968be2be1628584026cad30b824009a30eab43db3ee6dd8c0990051c27160cc8d1662da763d57c41c091f6",
);
let _ = register_name_from_string(
"scratchchat",
"94592af349cafd579d2ed77da1679306caca7aff9564ecf0f0a5b2eccb76aeb94925a1c9939f392e891378f8c492f4a0",
);
let _ = register_name_from_string(
"toast",
"95be239165b7016b7f6dada20134438e038d0456bff04ec37943e95742726854225aa03faeed4e7bbd96f5383a8f9448",
);
}
}
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, utoipa::ToSchema)]
pub struct RecognisedName {
pub key: String,
pub history_address: String,
}
pub fn recognised_dwebnames() -> Result<Vec<RecognisedName>> {
let mut names_vec = Vec::<RecognisedName>::new();
match &mut HISTORY_NAMES.lock() {
Ok(lock) => {
for cached_item in lock.iter() {
names_vec.push(RecognisedName {
key: cached_item.0.to_string(),
history_address: cached_item.1.to_hex(),
});
}
}
Err(e) => {
return Err(eyre!("Failed to access dweb name cache - {e}"));
}
}
Ok(names_vec)
}
#[test]
fn check_malformed_web_name() {
use crate::web::name;
assert!(name::decode_dweb_host("awe=some-f834.www-dweb.au").is_err());
assert!(name::decode_dweb_host("awe@some-f834.www-dweb.au").is_err());
assert!(name::decode_dweb_host("awe--some-f834.www-dweb.au").is_err());
assert!(name::decode_dweb_host("awesom-f-e-f834.www-dweb.ant").is_err());
assert!(name::decode_dweb_host("awesome-f834-.www-dweb.au").is_err());
assert!(name::decode_dweb_host("awesome-f834.ww-dweb.au").is_err());
assert!(name::decode_dweb_host("awesome-f834.www-dweb.ant").is_err());
assert!(name::decode_dweb_host("awesome-f834.www-dweb.au.com").is_err());
assert!(name::decode_dweb_host("v2.9awesome-f834.www-dweb.au").is_err());
assert!(name::decode_dweb_host("v0.awesome-f834.www-dweb.au").is_err());
assert!(name::decode_dweb_host("v2nd.awesome-f834.www-dweb.au").is_err());
}