metatext/ServiceLayer/Sources/ServiceLayer/Services/InstanceURLService.swift

124 lines
6 KiB
Swift
Raw Normal View History

2020-09-07 04:56:18 +00:00
// Copyright © 2020 Metabolist. All rights reserved.
import CodableBloomFilter
import Combine
import Foundation
import HTTP
2020-09-10 09:38:21 +00:00
import Mastodon
import MastodonAPI
2020-09-07 04:56:18 +00:00
2021-01-17 20:24:50 +00:00
public enum InstanceURLError: Error {
case instanceNotSupported
}
2020-09-10 04:51:31 +00:00
public struct InstanceURLService {
2020-09-07 04:56:18 +00:00
private let httpClient: HTTPClient
2020-10-15 07:44:01 +00:00
private var appPreferences: AppPreferences
2020-09-07 04:56:18 +00:00
2020-09-09 06:17:35 +00:00
public init(environment: AppEnvironment) {
2020-09-10 09:38:21 +00:00
httpClient = HTTPClient(session: environment.session, decoder: MastodonDecoder())
2020-10-15 07:44:01 +00:00
appPreferences = AppPreferences(environment: environment)
2020-09-07 04:56:18 +00:00
}
}
2020-09-10 04:51:31 +00:00
public extension InstanceURLService {
2020-09-13 01:12:33 +00:00
func url(text: String) -> Result<URL, Error> {
guard text.count >= Self.shortestPossibleURLLength else {
return .failure(URLError(.badURL))
}
2020-09-10 09:38:21 +00:00
2020-09-11 00:24:12 +00:00
let url: URL
if text.hasPrefix(Self.httpsPrefix), let prefixedURL = URL(string: text) {
url = prefixedURL
2020-12-03 22:40:33 +00:00
} else if let unprefixedURL = URL(string: Self.httpsPrefix.appending(text)) {
2020-09-11 00:24:12 +00:00
url = unprefixedURL
} else {
2020-09-13 01:12:33 +00:00
return .failure(URLError(.badURL))
2020-09-11 00:24:12 +00:00
}
if isFiltered(url: url) {
2021-01-17 20:24:50 +00:00
return .failure(InstanceURLError.instanceNotSupported)
2020-09-10 09:38:21 +00:00
}
2020-09-13 01:12:33 +00:00
return .success(url)
2020-09-10 09:38:21 +00:00
}
2020-09-12 19:09:54 +00:00
func instance(url: URL) -> AnyPublisher<Instance, Error> {
2020-09-10 09:38:21 +00:00
httpClient.request(
MastodonAPITarget(
baseURL: url,
endpoint: InstanceEndpoint.instance,
accessToken: nil))
.eraseToAnyPublisher()
}
2020-09-12 19:58:23 +00:00
func isPublicTimelineAvailable(url: URL) -> AnyPublisher<Bool, Error> {
2020-09-10 09:38:21 +00:00
httpClient.request(
MastodonAPITarget(
baseURL: url,
2020-09-18 00:16:41 +00:00
endpoint: StatusesEndpoint.timelinesPublic(local: true),
2020-09-10 09:38:21 +00:00
accessToken: nil))
.map { _ in true }
.eraseToAnyPublisher()
}
2020-09-07 04:56:18 +00:00
}
2020-09-10 04:51:31 +00:00
private extension InstanceURLService {
2020-09-10 09:38:21 +00:00
static let httpsPrefix = "https://"
static let shortestPossibleURLLength = 4
2021-01-29 01:14:34 +00:00
static let filter = BloomFilter<String>(
2020-10-15 10:03:39 +00:00
hashes: [.djb232, .djb2a32, .sdbm32, .fnv132, .fnv1a32],
data: Data([
2021-01-17 20:24:50 +00:00
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 2, 0, 8, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
128, 0, 0, 0, 0, 128, 0, 0, 0, 4, 0, 0, 0, 0, 0, 16, 0, 0, 32, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 64, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 8, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32,
0, 0, 0, 0, 64, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0,
0, 0, 0, 8, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0,
0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 32, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0
2020-10-15 10:03:39 +00:00
]))
2020-09-11 00:24:12 +00:00
private func isFiltered(url: URL) -> Bool {
guard let host = url.host else { return true }
let allHostComponents = host.components(separatedBy: ".")
var hostComponents = [String]()
for component in allHostComponents.reversed() {
hostComponents.insert(component, at: 0)
2021-01-29 01:14:34 +00:00
if Self.filter.contains(hostComponents.joined(separator: ".")) {
2020-09-11 00:24:12 +00:00
return true
}
}
return false
}
2020-09-07 04:56:18 +00:00
}