From 3e9f6d1c090430b0dfa6e32e788679cdcf481dd5 Mon Sep 17 00:00:00 2001 From: upstream Date: Fri, 4 Sep 2026 09:35:35 +0200 Subject: [PATCH] Upstream rust-syndication/rss state of the touched files before 555a0902fa --- src/validation.rs | 265 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 265 insertions(+) create mode 100644 src/validation.rs diff --git a/src/validation.rs b/src/validation.rs new file mode 100644 index 0000000..2f19ab0 --- /dev/null +++ b/src/validation.rs @@ -0,0 +1,265 @@ +// This file is part of rss. +// +// Copyright © 2015-2021 The rust-syndication Developers +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the MIT License and/or Apache 2.0 License. + +use std::collections::HashSet; +use std::error::Error as StdError; +use std::fmt; +use std::num::ParseIntError; + +use chrono::DateTime; +use chrono::ParseError as DateParseError; +use mime::FromStrError as MimeParseError; +use mime::Mime; +use url::ParseError as UrlParseError; +use url::Url; + +use crate::{Category, Channel, Cloud, Enclosure, Image, Item, Source, TextInput}; + +#[derive(Debug)] +/// Errors that occur during validation. +pub enum ValidationError { + /// An error while parsing a string to a date. + DateParsing(DateParseError), + /// An error while parsing a string to an integer. + IntParsing(ParseIntError), + /// An error while parsing a string to a URL. + UrlParsing(UrlParseError), + /// An error while parsing a string to a MIME type. + MimeParsing(MimeParseError), + /// A different validation error. + Validation(String), +} + +impl StdError for ValidationError { + fn source(&self) -> Option<&(dyn StdError + 'static)> { + match *self { + ValidationError::DateParsing(ref err) => Some(err), + ValidationError::IntParsing(ref err) => Some(err), + ValidationError::UrlParsing(ref err) => Some(err), + _ => None, + } + } +} + +impl fmt::Display for ValidationError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + ValidationError::DateParsing(ref err) => err.fmt(f), + ValidationError::IntParsing(ref err) => err.fmt(f), + ValidationError::UrlParsing(ref err) => err.fmt(f), + ValidationError::MimeParsing(_) => write!(f, "Unable to parse MIME type"), + ValidationError::Validation(ref s) => write!(f, "{}", s), + } + } +} + +impl From for ValidationError { + fn from(err: DateParseError) -> Self { + ValidationError::DateParsing(err) + } +} + +impl From for ValidationError { + fn from(err: ParseIntError) -> Self { + ValidationError::IntParsing(err) + } +} + +impl From for ValidationError { + fn from(err: UrlParseError) -> Self { + ValidationError::UrlParsing(err) + } +} + +impl From for ValidationError { + fn from(err: MimeParseError) -> Self { + ValidationError::MimeParsing(err) + } +} + +/// A trait to support data validation. +pub trait Validate { + /// Validate the data against the RSS specification. + fn validate(&self) -> Result<(), ValidationError>; +} + +macro_rules! validate { + ($e: expr, $msg: expr) => {{ + if !($e) { + return Err(ValidationError::Validation(String::from($msg))); + } + }}; +} + +impl Validate for Channel { + fn validate(&self) -> Result<(), ValidationError> { + Url::parse(self.link())?; + + for category in self.categories() { + category.validate()?; + } + + if let Some(cloud) = self.cloud() { + cloud.validate()?; + } + + if let Some(docs) = self.docs() { + Url::parse(docs)?; + } + + if let Some(image) = self.image() { + image.validate()?; + } + + for item in self.items() { + item.validate()?; + } + + if let Some(last_build_date) = self.last_build_date() { + DateTime::parse_from_rfc2822(last_build_date)?; + } + + if let Some(pub_date) = self.pub_date() { + DateTime::parse_from_rfc2822(pub_date)?; + } + + for hour in self.skip_hours() { + let hour = hour.parse::()?; + validate!( + (0..=23).contains(&hour), + "Channel skip hour is not between 0 and 23" + ); + } + + let valid_days = { + let mut set = HashSet::with_capacity(7); + set.insert("Monday"); + set.insert("Tuesday"); + set.insert("Wednesday"); + set.insert("Thursday"); + set.insert("Friday"); + set.insert("Saturday"); + set.insert("Sunday"); + set + }; + + for day in self.skip_days() { + validate!( + valid_days.contains(day.as_str()), + format!("Unknown skip day: {}", day) + ); + } + + if let Some(text_input) = self.text_input() { + text_input.validate()?; + } + + if let Some(ttl) = self.ttl() { + let ttl = ttl.parse::()?; + validate!(ttl > 0, "Channel TTL is not greater than 0"); + } + + Ok(()) + } +} + +impl Validate for Category { + fn validate(&self) -> Result<(), ValidationError> { + if let Some(domain) = self.domain() { + Url::parse(domain)?; + } + Ok(()) + } +} + +impl Validate for Cloud { + fn validate(&self) -> Result<(), ValidationError> { + let port = self.port().parse::()?; + validate!(port > 0, "Cloud port must be greater than 0"); + Url::parse(self.domain())?; + validate!( + ["xml-rpc", "soap", "http-post"].contains(&self.protocol()), + format!("Unknown cloud protocol: {}", self.protocol()) + ); + Ok(()) + } +} + +impl Validate for Enclosure { + fn validate(&self) -> Result<(), ValidationError> { + Url::parse(self.url())?; + self.mime_type().parse::()?; + let length = self.length().parse::()?; + validate!(length > 0, "Enclosure length is not greater than 0"); + Ok(()) + } +} + +impl Validate for TextInput { + fn validate(&self) -> Result<(), ValidationError> { + Url::parse(self.link())?; + Ok(()) + } +} + +impl Validate for Image { + fn validate(&self) -> Result<(), ValidationError> { + Url::parse(self.link())?; + Url::parse(self.url())?; + + if let Some(width) = self.width() { + let width = width.parse::()?; + validate!( + (0..=144).contains(&width), + "Image width is not between 0 and 144" + ); + } + + if let Some(height) = self.height() { + let height = height.parse::()?; + validate!( + (0..=144).contains(&height), + "Image height is not between 0 and 144" + ); + } + + Ok(()) + } +} + +impl Validate for Item { + fn validate(&self) -> Result<(), ValidationError> { + if let Some(link) = self.link() { + Url::parse(link)?; + } + + if let Some(comments) = self.comments() { + Url::parse(comments)?; + } + + if let Some(enclosure) = self.enclosure() { + enclosure.validate()?; + } + + if let Some(pub_date) = self.pub_date() { + DateTime::parse_from_rfc2822(pub_date)?; + } + + if let Some(source) = self.source() { + source.validate()?; + } + + Ok(()) + } +} + +impl Validate for Source { + fn validate(&self) -> Result<(), ValidationError> { + Url::parse(self.url())?; + Ok(()) + } +}