///|
pub(all) enum FeedFormat {
JsonFeed
Rss
Atom
} derive(Eq, @debug.Debug)
///|
pub fn FeedFormat::equal(self : FeedFormat, other : FeedFormat) -> Bool {
Eq::equal(self, other)
}
///|
pub fn FeedFormat::not_equal(self : FeedFormat, other : FeedFormat) -> Bool {
!Eq::equal(self, other)
}
///|
pub fn FeedFormat::to_repr(self : FeedFormat) -> @debug.Repr {
@debug.Debug::to_repr(self)
}
///|
pub(all) suberror DetectError {
EmptyInput
UnknownFormat
MalformedPrefix(String)
} derive(Eq, @debug.Debug)
///|
pub fn DetectError::equal(self : DetectError, other : DetectError) -> Bool {
Eq::equal(self, other)
}
///|
pub fn DetectError::not_equal(self : DetectError, other : DetectError) -> Bool {
!Eq::equal(self, other)
}
///|
pub fn DetectError::to_repr(self : DetectError) -> @debug.Repr {
@debug.Debug::to_repr(self)
}
///|
fn starts(chars : Array[Char], pos : Int, token : String) -> Bool {
let value = token.to_array()
if pos < 0 || pos + value.length() > chars.length() {
false
} else {
let mut matched = true
for index, ch in value {
if chars[pos + index] != ch {
matched = false
}
}
matched
}
}
///|
fn xml_space(ch : Char) -> Bool {
ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n'
}
///|
fn skip_space(chars : Array[Char], pos : Int) -> Int {
let mut at = pos
while at < chars.length() && xml_space(chars[at]) {
at += 1
}
at
}
///|
fn find(chars : Array[Char], start : Int, token : String) -> Int? {
let mut at = start
while at + token.to_array().length() <= chars.length() {
if starts(chars, at, token) {
return Some(at)
}
at += 1
}
None
}
///|
fn xml_declaration(chars : Array[Char], pos : Int) -> Bool {
if !starts(chars, pos, " (String, Map[String, String]) raise DetectError {
if pos >= chars.length() || chars[pos] != '<' {
raise MalformedPrefix("expected XML root start tag")
}
let mut at = pos + 1
let name_start = at
while at < chars.length() &&
!xml_space(chars[at]) &&
chars[at] != '/' &&
chars[at] != '>' {
at += 1
}
if at == name_start {
raise MalformedPrefix("missing XML root name")
}
let name_builder = StringBuilder()
for index = name_start; index < at; index = index + 1 {
name_builder.write_char(chars[index])
}
let name = name_builder.to_string()
let attributes : Map[String, String] = Map([])
while true {
at = skip_space(chars, at)
if at >= chars.length() {
raise MalformedPrefix("unterminated XML root start tag")
}
if chars[at] == '>' {
return (name, attributes)
}
if chars[at] == '/' {
if at + 1 < chars.length() && chars[at + 1] == '>' {
return (name, attributes)
}
raise MalformedPrefix("invalid XML root close")
}
let key_start = at
while at < chars.length() &&
!xml_space(chars[at]) &&
chars[at] != '=' &&
chars[at] != '/' &&
chars[at] != '>' {
at += 1
}
if at == key_start {
raise MalformedPrefix("invalid XML root attribute")
}
let key_builder = StringBuilder()
for index = key_start; index < at; index = index + 1 {
key_builder.write_char(chars[index])
}
let key = key_builder.to_string()
at = skip_space(chars, at)
if at >= chars.length() || chars[at] != '=' {
raise MalformedPrefix("expected '=' after XML root attribute")
}
at = skip_space(chars, at + 1)
if at >= chars.length() || (chars[at] != '"' && chars[at] != '\'') {
raise MalformedPrefix("XML root attribute must be quoted")
}
let quote = chars[at]
at += 1
let value_start = at
while at < chars.length() && chars[at] != quote {
at += 1
}
if at >= chars.length() {
raise MalformedPrefix("unterminated XML root attribute")
}
let value_builder = StringBuilder()
for index = value_start; index < at; index = index + 1 {
value_builder.write_char(chars[index])
}
attributes.set(key, value_builder.to_string())
at += 1
} nobreak {
("", attributes)
}
}
///|
/// Lightly classify the input prefix. Full syntax and format validation is left to each parser.
pub fn detect_format(input : String) -> FeedFormat raise DetectError {
let chars = input.to_array()
let mut pos = 0
if !chars.is_empty() && chars[0].to_int() == 0xFEFF {
pos = 1
}
pos = skip_space(chars, pos)
if pos >= chars.length() {
raise EmptyInput
}
if chars[pos] == '{' {
return JsonFeed
}
if chars[pos] != '<' {
raise UnknownFormat
}
while true {
pos = skip_space(chars, pos)
if starts(chars, pos, "") {
Some(end) => pos = end + 3
None => raise MalformedPrefix("unterminated XML comment before root")
}
} else if xml_declaration(chars, pos) {
match find(chars, pos + 5, "?>") {
Some(end) => pos = end + 2
None => raise MalformedPrefix("unterminated XML declaration")
}
} else {
break
}
}
pos = skip_space(chars, pos)
if pos >= chars.length() {
raise MalformedPrefix("missing XML root element")
}
let (root, attributes) = parse_root(chars, pos)
match root {
"rss" => Rss
"feed" =>
match attributes.get("xmlns") {
Some(uri) if uri != "http://www.w3.org/2005/Atom" => raise UnknownFormat
_ => Atom
}
"atom:feed" =>
match attributes.get("xmlns:atom") {
Some("http://www.w3.org/2005/Atom") => Atom
_ => raise UnknownFormat
}
_ => raise UnknownFormat
}
}