From 117348617988288ad11bc08af576598df71e4961 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Sun, 22 Sep 2024 22:13:06 -0700 Subject: [PATCH] Fix errors in HTMLMetadata. --- .../Sources/HTMLParser/HTMLMetadata.swift | 104 +++++++++++++----- 1 file changed, 75 insertions(+), 29 deletions(-) diff --git a/Modules/Parser/Sources/HTMLParser/HTMLMetadata.swift b/Modules/Parser/Sources/HTMLParser/HTMLMetadata.swift index 0e36b93d7..bf804d760 100644 --- a/Modules/Parser/Sources/HTMLParser/HTMLMetadata.swift +++ b/Modules/Parser/Sources/HTMLParser/HTMLMetadata.swift @@ -49,10 +49,13 @@ public final class HTMLMetadata { static func resolvedFaviconLinks(_ baseURLString: String, _ tags: [HTMLTag]) -> [HTMLMetadataFavicon]? { - let linkTags = linkTagsWithMatchingRel("icon") + guard let linkTags = linkTagsWithMatchingRel("icon", tags) else { + return nil + } + var seenHrefs = [String]() - let favicons = linkTags.compactMap { htmlTag in + let favicons: [HTMLMetadataFavicon] = linkTags.compactMap { htmlTag in let favicon = HTMLMetadataFavicon(baseURLString, htmlTag) guard let urlString = favicon.urlString else { @@ -74,13 +77,15 @@ public final class HTMLMetadata { return nil } - let appleTouchIconTags = tagsMatchingRelValues(["apple-touch-icon", "apple-touch-icon-precomposed"], tags) + guard let appleTouchIconTags = tagsMatchingRelValues(["apple-touch-icon", "apple-touch-icon-precomposed"], linkTags) else { + return nil + } return appleTouchIconTags.isEmpty ? nil : appleTouchIconTags } static func feedLinkTags(_ tags: [HTMLTag]) -> [HTMLTag]? { - let alternateLinkTags = linkTagsWithMatchingRel("alternate", tags) else { + guard let alternateLinkTags = linkTagsWithMatchingRel("alternate", tags) else { return nil } @@ -120,7 +125,10 @@ public final class HTMLMetadata { } let tagsWithURLString = linkTags.filter { tag in - guard let urlString = urlStringFromDictionary(tag.attributes), !urlString.isEmpty else { + guard let attributes = tag.attributes else { + return false + } + guard let urlString = urlString(from: attributes), !urlString.isEmpty else { return false } return true @@ -129,7 +137,9 @@ public final class HTMLMetadata { return nil } - let matchingTags = tagsMatchingRelValues([valueToMatch], tagsWithURLString) + guard let matchingTags = tagsMatchingRelValues([valueToMatch], tagsWithURLString) else { + return nil + } return matchingTags.isEmpty ? nil : matchingTags } @@ -141,11 +151,14 @@ public final class HTMLMetadata { tags.filter { tag in - guard let relValue = relValue(tag.attributes) else { + guard let attributes = tag.attributes else { + return false + } + guard let relValue = relValue(from: attributes) else { return false } - let relValues = relValue.componentsSeparatedByCharactersInSet(.whitespacesAndNewlines) + let relValues = relValue.components(separatedBy: .whitespacesAndNewlines) for oneRelValue in relValues { let oneLowerRelValue = oneRelValue.lowercased() @@ -158,7 +171,7 @@ public final class HTMLMetadata { return false } - } + }() return matchingTags.isEmpty ? nil : matchingTags } @@ -182,7 +195,7 @@ public final class HTMLMetadataAppleTouchIcon { } self.rel = attributes.object(forCaseInsensitiveKey: "rel") - self.urlString = absoluteURLStringWithDictionary(attributes) + self.urlString = absoluteURLString(from: attributes, baseURL: urlString) guard let sizes = attributes.object(forCaseInsensitiveKey: "sizes") else { self.sizes = nil @@ -191,17 +204,13 @@ public final class HTMLMetadataAppleTouchIcon { } self.sizes = sizes - let size: CGSize? = { - let sizeComponents = sizes.components(separatedBy: CharacterSet(charactersIn: "x")) - guard sizeComponents.count == 2 else { - return nil - } - let width = Double(sizeComponents[0]) - let height = Double(sizeComponents[1]) - return CGSize(width: width, height: height) - }() - - self.size = size + let sizeComponents = sizes.components(separatedBy: CharacterSet(charactersIn: "x")) + if sizeComponents.count == 2, let width = Double(sizeComponents[0]), let height = Double(sizeComponents[1]) { + self.size = CGSize(width: width, height: height) + } + else { + self.size = nil + } } } @@ -220,7 +229,7 @@ public final class HTMLMetadataFeedLink { return } - self.urlString = absoluteURLStringWithDictionary(attributes, baseURLString) + self.urlString = absoluteURLString(from: attributes, baseURL: urlString) self.title = attributes.object(forCaseInsensitiveKey: "title") self.type = attributes.object(forCaseInsensitiveKey: "type") } @@ -239,7 +248,7 @@ public final class HTMLMetadataFavicon { return } - self.urlString = absoluteURLStringWithDictionary(attributes, baseURLString) + self.urlString = absoluteURLString(from: attributes, baseURL: urlString) self.type = attributes.object(forCaseInsensitiveKey: "type") } } @@ -276,7 +285,7 @@ private extension HTMLOpenGraphProperties { static let ogImageHeight = "og:image:height" } - static func parse(_ tags: [HTMLTag]) -> [HTMLOpenGraphImage]? { + static func parse(_ tags: [HTMLTag]) -> HTMLOpenGraphImage? { let metaTags = tags.filter { $0.tagType == .meta } if metaTags.isEmpty { @@ -319,10 +328,14 @@ private extension HTMLOpenGraphProperties { altText = content } else if propertyName == OGValue.ogImageWidth { - width = CGFloat(content) + if let value = Double(content) { + width = CGFloat(value) + } } else if propertyName == OGValue.ogImageHeight { - height = CGFloat(content) + if let value = Double(content) { + height = CGFloat(value) + } } } @@ -343,8 +356,8 @@ public final class HTMLOpenGraphImage { public let height: CGFloat? public let altText: String? - init(url: String?, secureURL: String?, mimeType: String, width: CGFloat?, height: CGFloat?, altText: String?) { - + init(url: String?, secureURL: String?, mimeType: String?, width: CGFloat?, height: CGFloat?, altText: String?) { + self.url = url self.secureURL = secureURL self.mimeType = mimeType @@ -369,7 +382,7 @@ public final class HTMLTwitterProperties { init(_ urlString: String, _ tags: [HTMLTag]) { - let imageURL: String = { + let imageURL: String? = { for tag in tags { guard tag.tagType == .meta else { continue @@ -390,3 +403,36 @@ public final class HTMLTwitterProperties { } } +private func urlString(from attributes: HTMLTagAttributes) -> String? { + + if let urlString = attributes.object(forCaseInsensitiveKey: "href") { + return urlString + } + return attributes.object(forCaseInsensitiveKey: "src") +} + +private func relValue(from attributes: HTMLTagAttributes) -> String? { + + attributes.object(forCaseInsensitiveKey: "rel") +} + +private func absoluteURLString(from attributes: HTMLTagAttributes, baseURL: String) -> String? { + + guard let urlString = urlString(from: attributes), !urlString.isEmpty else { + return nil + } + + return absoluteURLStringWithRelativeURLString(urlString, baseURLString: baseURL) +} + +private func absoluteURLStringWithRelativeURLString(_ relativeURLString: String, baseURLString: String) -> String? { + + guard let baseURL = URL(string: baseURLString) else { + return nil + } + guard let absoluteURL = URL(string: relativeURLString, relativeTo: baseURL) else { + return nil + } + return absoluteURL.absoluteURL.standardized.absoluteString +} +