Use WebURL for more lenient parsing of external URLs

Fixes #136
This commit is contained in:
Shadowfacts 2022-02-03 23:11:29 -05:00
parent 6e964ff601
commit 54c01be7ff
13 changed files with 90 additions and 41 deletions

View File

@ -14,7 +14,6 @@ public class Attachment: Codable {
public let url: URL
public let remoteURL: URL?
public let previewURL: URL?
public let textURL: URL?
public let meta: Metadata?
public let description: String?
public let blurHash: String?
@ -33,7 +32,6 @@ public class Attachment: Codable {
self.url = try container.decode(URL.self, forKey: .url)
self.previewURL = try? container.decode(URL?.self, forKey: .previewURL)
self.remoteURL = try? container.decode(URL?.self, forKey: .remoteURL)
self.textURL = try? container.decode(URL?.self, forKey: .textURL)
self.meta = try? container.decode(Metadata?.self, forKey: .meta)
self.description = try? container.decode(String?.self, forKey: .description)
self.blurHash = try? container.decode(String?.self, forKey: .blurHash)
@ -45,7 +43,6 @@ public class Attachment: Codable {
case url
case remoteURL = "remote_url"
case previewURL = "preview_url"
case textURL = "text_url"
case meta
case description
case blurHash = "blurhash"

View File

@ -7,17 +7,18 @@
//
import Foundation
import WebURL
public class Card: Codable {
public let url: URL
public let url: WebURL
public let title: String
public let description: String
public let image: URL?
public let image: WebURL?
public let kind: Kind
public let authorName: String?
public let authorURL: URL?
public let authorURL: WebURL?
public let providerName: String?
public let providerURL: URL?
public let providerURL: WebURL?
public let html: String?
public let width: Int?
public let height: Int?
@ -26,15 +27,15 @@ public class Card: Codable {
public required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.url = try container.decode(URL.self, forKey: .url)
self.url = try container.decode(WebURL.self, forKey: .url)
self.title = try container.decode(String.self, forKey: .title)
self.description = try container.decode(String.self, forKey: .description)
self.kind = try container.decode(Kind.self, forKey: .kind)
self.image = try? container.decodeIfPresent(URL.self, forKey: .image)
self.image = try? container.decodeIfPresent(WebURL.self, forKey: .image)
self.authorName = try? container.decodeIfPresent(String.self, forKey: .authorName)
self.authorURL = try? container.decodeIfPresent(URL.self, forKey: .authorURL)
self.authorURL = try? container.decodeIfPresent(WebURL.self, forKey: .authorURL)
self.providerName = try? container.decodeIfPresent(String.self, forKey: .providerName)
self.providerURL = try? container.decodeIfPresent(URL.self, forKey: .providerURL)
self.providerURL = try? container.decodeIfPresent(WebURL.self, forKey: .providerURL)
self.html = try? container.decodeIfPresent(String.self, forKey: .html)
self.width = try? container.decodeIfPresent(Int.self, forKey: .width)
self.height = try? container.decodeIfPresent(Int.self, forKey: .height)

View File

@ -7,29 +7,22 @@
//
import Foundation
import WebURL
public class Emoji: Codable {
public let shortcode: String
public let url: URL
public let staticURL: URL
// these shouldn't need to be WebURLs as they're not external resources,
// but some instances (pleroma?) has emoji urls that Foundation considers malformed so we use WebURL to be more lenient
public let url: WebURL
public let staticURL: WebURL
public let visibleInPicker: Bool
public required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.shortcode = try container.decode(String.self, forKey: .shortcode)
if let url = try? container.decode(URL.self, forKey: .url) {
self.url = url
} else {
let str = try container.decode(String.self, forKey: .url)
self.url = URL(string: str.replacingOccurrences(of: " ", with: "%20"))!
}
if let url = try? container.decode(URL.self, forKey: .staticURL) {
self.staticURL = url
} else {
let staticStr = try container.decode(String.self, forKey: .staticURL)
self.staticURL = URL(string: staticStr.replacingOccurrences(of: " ", with: "%20"))!
}
self.url = try container.decode(WebURL.self, forKey: .url)
self.staticURL = try container.decode(WebURL.self, forKey: .staticURL)
self.visibleInPicker = try container.decode(Bool.self, forKey: .visibleInPicker)
}

View File

@ -7,9 +7,10 @@
//
import Foundation
import WebURL
public class Mention: Codable {
public let url: URL
public let url: WebURL
public let username: String
public let acct: String
/// The instance-local ID of the user being mentioned.

View File

@ -38,8 +38,7 @@ public final class Status: /*StatusProtocol,*/ Decodable {
public let bookmarked: Bool?
public let card: Card?
public let poll: Poll?
// Hometown only
// TODO: glitch too?
// Hometown, Glitch only
public let localOnly: Bool?
public var applicationName: String? { application?.name }

View File

@ -126,6 +126,7 @@
D6333B372137838300CE884A /* AttributedString+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6333B362137838300CE884A /* AttributedString+Helpers.swift */; };
D6333B792139AEFD00CE884A /* Date+TimeAgo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6333B782139AEFD00CE884A /* Date+TimeAgo.swift */; };
D63569E023908A8D003DD353 /* StatusState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60A4FFB238B726A008AC647 /* StatusState.swift */; };
D635ED5027ACD9260003635B /* WebURL in Frameworks */ = {isa = PBXBuildFile; productRef = D635ED4F27ACD9260003635B /* WebURL */; };
D63661C02381C144004B9E16 /* PreferencesNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63661BF2381C144004B9E16 /* PreferencesNavigationController.swift */; };
D6370B9C24421FF30092A7FF /* Tusker.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = D6370B9A24421FF30092A7FF /* Tusker.xcdatamodeld */; };
D63A8D0B2561C27F00D9DFFF /* ProfileStatusesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63A8D0A2561C27F00D9DFFF /* ProfileStatusesViewController.swift */; };
@ -177,6 +178,8 @@
D663626C21361C6700C9CBA2 /* Account+Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = D663626B21361C6700C9CBA2 /* Account+Preferences.swift */; };
D66362752137068A00C9CBA2 /* Visibility+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D66362742137068A00C9CBA2 /* Visibility+Helpers.swift */; };
D6674AEA23341F7600E8DF94 /* AppShortcutItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6674AE923341F7600E8DF94 /* AppShortcutItems.swift */; };
D6676CA327A8D0020052936B /* WebURL in Frameworks */ = {isa = PBXBuildFile; productRef = D6676CA227A8D0020052936B /* WebURL */; };
D6676CA527A8D0020052936B /* WebURLFoundationExtras in Frameworks */ = {isa = PBXBuildFile; productRef = D6676CA427A8D0020052936B /* WebURLFoundationExtras */; };
D667E5E12134937B0057A976 /* TimelineStatusTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D667E5E02134937B0057A976 /* TimelineStatusTableViewCell.xib */; };
D667E5F12134D5050057A976 /* UIViewController+Delegates.swift in Sources */ = {isa = PBXBuildFile; fileRef = D667E5F02134D5050057A976 /* UIViewController+Delegates.swift */; };
D667E5F52135BCD50057A976 /* ConversationTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D667E5F42135BCD50057A976 /* ConversationTableViewController.swift */; };
@ -766,6 +769,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
D635ED5027ACD9260003635B /* WebURL in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -781,10 +785,12 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
D6676CA327A8D0020052936B /* WebURL in Frameworks */,
D61099C02144B0CC00432DC2 /* Pachyderm.framework in Frameworks */,
D6B0539F23BD2BA300A066FA /* SheetController in Frameworks */,
D69CCBBF249E6EFD000AF167 /* CrashReporter in Frameworks */,
D60CFFDB24A290BA00D00083 /* SwiftSoup in Frameworks */,
D6676CA527A8D0020052936B /* WebURLFoundationExtras in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1720,6 +1726,9 @@
dependencies = (
);
name = Pachyderm;
packageProductDependencies = (
D635ED4F27ACD9260003635B /* WebURL */,
);
productName = Pachyderm;
productReference = D61099AB2144B0CC00432DC2 /* Pachyderm.framework */;
productType = "com.apple.product-type.framework";
@ -1765,6 +1774,8 @@
D6B0539E23BD2BA300A066FA /* SheetController */,
D69CCBBE249E6EFD000AF167 /* CrashReporter */,
D60CFFDA24A290BA00D00083 /* SwiftSoup */,
D6676CA227A8D0020052936B /* WebURL */,
D6676CA427A8D0020052936B /* WebURLFoundationExtras */,
);
productName = Tusker;
productReference = D6D4DDCC212518A000E1C4BB /* Tusker.app */;
@ -1882,6 +1893,7 @@
D6B0539D23BD2BA300A066FA /* XCRemoteSwiftPackageReference "SheetController" */,
D69CCBBD249E6EFD000AF167 /* XCRemoteSwiftPackageReference "plcrashreporter" */,
D60CFFD924A290BA00D00083 /* XCRemoteSwiftPackageReference "SwiftSoup" */,
D6676CA127A8D0020052936B /* XCRemoteSwiftPackageReference "swift-url" */,
);
productRefGroup = D6D4DDCD212518A000E1C4BB /* Products */;
projectDirPath = "";
@ -2892,6 +2904,14 @@
minimumVersion = 2.3.2;
};
};
D6676CA127A8D0020052936B /* XCRemoteSwiftPackageReference "swift-url" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/karwa/swift-url";
requirement = {
branch = main;
kind = branch;
};
};
D69CCBBD249E6EFD000AF167 /* XCRemoteSwiftPackageReference "plcrashreporter" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/microsoft/plcrashreporter";
@ -2916,6 +2936,21 @@
package = D60CFFD924A290BA00D00083 /* XCRemoteSwiftPackageReference "SwiftSoup" */;
productName = SwiftSoup;
};
D635ED4F27ACD9260003635B /* WebURL */ = {
isa = XCSwiftPackageProductDependency;
package = D6676CA127A8D0020052936B /* XCRemoteSwiftPackageReference "swift-url" */;
productName = WebURL;
};
D6676CA227A8D0020052936B /* WebURL */ = {
isa = XCSwiftPackageProductDependency;
package = D6676CA127A8D0020052936B /* XCRemoteSwiftPackageReference "swift-url" */;
productName = WebURL;
};
D6676CA427A8D0020052936B /* WebURLFoundationExtras */ = {
isa = XCSwiftPackageProductDependency;
package = D6676CA127A8D0020052936B /* XCRemoteSwiftPackageReference "swift-url" */;
productName = WebURLFoundationExtras;
};
D69CCBBE249E6EFD000AF167 /* CrashReporter */ = {
isa = XCSwiftPackageProductDependency;
package = D69CCBBD249E6EFD000AF167 /* XCRemoteSwiftPackageReference "plcrashreporter" */;

View File

@ -2,7 +2,7 @@
"object": {
"pins": [
{
"package": "plcrashreporter",
"package": "PLCrashReporter",
"repositoryURL": "https://github.com/microsoft/plcrashreporter",
"state": {
"branch": null,
@ -19,6 +19,24 @@
"version": null
}
},
{
"package": "swift-system",
"repositoryURL": "https://github.com/apple/swift-system.git",
"state": {
"branch": null,
"revision": "836bc4557b74fe6d2660218d56e3ce96aff76574",
"version": "1.1.1"
}
},
{
"package": "swift-url",
"repositoryURL": "https://github.com/karwa/swift-url",
"state": {
"branch": "main",
"revision": "1519936d9813af86e57c06dc7f727cb281de9f16",
"version": null
}
},
{
"package": "SwiftSoup",
"repositoryURL": "https://github.com/scinfu/SwiftSoup.git",

View File

@ -8,6 +8,7 @@
import UIKit
import Pachyderm
import WebURLFoundationExtras
class EmojiCollectionViewCell: UICollectionViewCell {
@ -45,7 +46,7 @@ class EmojiCollectionViewCell: UICollectionViewCell {
func updateUI(emoji: Emoji) {
currentEmojiShortcode = emoji.shortcode
imageRequest = ImageCache.emojis.get(emoji.url) { [weak self] (_, image) in
imageRequest = ImageCache.emojis.get(URL(emoji.url)!) { [weak self] (_, image) in
guard let image = image else { return }
DispatchQueue.main.async { [weak self] in
guard let self = self, self.currentEmojiShortcode == emoji.shortcode else { return }

View File

@ -8,6 +8,7 @@
import SwiftUI
import Pachyderm
import WebURLFoundationExtras
private let emojiRegex = try! NSRegularExpression(pattern: ":(\\w+):", options: [])
@ -48,7 +49,7 @@ struct AccountDisplayNameLabel<Account: AccountProtocol>: View {
}
group.enter()
let request = ImageCache.emojis.get(emoji.url) { (_, image) in
let request = ImageCache.emojis.get(URL(emoji.url)!) { (_, image) in
defer { group.leave() }
guard let image = image else { return }

View File

@ -8,6 +8,7 @@
import UIKit
import Pachyderm
import WebURLFoundationExtras
private let emojiRegex = try! NSRegularExpression(pattern: ":(\\w+):", options: [])
@ -56,7 +57,7 @@ extension BaseEmojiLabel {
foundEmojis = true
if let image = ImageCache.emojis.get(emoji.url)?.image {
if let image = ImageCache.emojis.get(URL(emoji.url)!)?.image {
// if the image is cached, add it immediately
emojiImages[emoji.shortcode] = image
} else {
@ -64,9 +65,9 @@ extension BaseEmojiLabel {
group.enter()
// todo: ImageCache.emojis.get here will re-check the memory and disk caches, there should be another method to force-refetch
let request = ImageCache.emojis.get(emoji.url) { (_, image) in
let request = ImageCache.emojis.get(URL(emoji.url)!) { (_, image) in
guard let image = image,
let transformedImage = ImageGrayscalifier.convertIfNecessary(url: emoji.url, image: image) else {
let transformedImage = ImageGrayscalifier.convertIfNecessary(url: URL(emoji.url)!, image: image) else {
group.leave()
return
}

View File

@ -8,6 +8,7 @@
import SwiftUI
import Pachyderm
import WebURLFoundationExtras
struct CustomEmojiImageView: View {
let emoji: Emoji
@ -33,7 +34,7 @@ struct CustomEmojiImageView: View {
}
private func loadImage() {
request = ImageCache.emojis.get(emoji.url) { (_, image) in
request = ImageCache.emojis.get(URL(emoji.url)!) { (_, image) in
DispatchQueue.main.async {
self.request = nil
if let image = image {

View File

@ -9,6 +9,7 @@
import UIKit
import Pachyderm
import SafariServices
import WebURLFoundationExtras
class StatusCardView: UIView {
@ -141,9 +142,9 @@ class StatusCardView: UIView {
if let imageURL = card.image {
placeholderImageView.isHidden = true
imageRequest = ImageCache.attachments.get(imageURL, completion: { (_, image) in
imageRequest = ImageCache.attachments.get(URL(imageURL)!, completion: { (_, image) in
guard let image = image,
let transformedImage = ImageGrayscalifier.convertIfNecessary(url: imageURL, image: image) else {
let transformedImage = ImageGrayscalifier.convertIfNecessary(url: URL(imageURL)!, image: image) else {
return
}
DispatchQueue.main.async {
@ -200,7 +201,7 @@ class StatusCardView: UIView {
setNeedsDisplay()
if let card = card, let delegate = navigationDelegate {
delegate.selected(url: card.url)
delegate.selected(url: URL(card.url)!)
}
}
@ -219,9 +220,9 @@ extension StatusCardView: UIContextMenuInteractionDelegate {
guard let card = card else { return nil }
return UIContextMenuConfiguration(identifier: nil) {
return SFSafariViewController(url: card.url)
return SFSafariViewController(url: URL(card.url)!)
} actionProvider: { (_) in
let actions = self.actionsForURL(card.url, sourceView: self)
let actions = self.actionsForURL(URL(card.url)!, sourceView: self)
return UIMenu(title: "", image: nil, identifier: nil, options: [], children: actions)
}
}

View File

@ -27,7 +27,7 @@ class StatusContentTextView: ContentTextView {
let status = mastodonController.persistentContainer.status(for: statusID) {
mention = status.mentions.first { (mention) in
// Mastodon and Pleroma include the @ in the <a> text, GNU Social does not
(text.dropFirst() == mention.username || text == mention.username) && url.host == mention.url.host
(text.dropFirst() == mention.username || text == mention.username) && url.host == mention.url.host!.serialized
}
} else {
mention = nil