From e95f91558b02cdc7440109d56095b06089313454 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 3 Feb 2020 14:15:35 +1100 Subject: [PATCH] Integrate RSS feed proxy --- .../Loki/Messaging/LokiRSSFeedPoller.swift | 73 ++++++++++--------- .../src/Loki/API/LokiFileServerProxy.swift | 33 +++++++-- .../src/Loki/API/LokiRSSFeedProxy.swift | 12 +-- .../src/Loki/API/LokiSnodeProxy.swift | 2 +- 4 files changed, 71 insertions(+), 49 deletions(-) diff --git a/Signal/src/Loki/Messaging/LokiRSSFeedPoller.swift b/Signal/src/Loki/Messaging/LokiRSSFeedPoller.swift index c630a4a7d..62a0d0341 100644 --- a/Signal/src/Loki/Messaging/LokiRSSFeedPoller.swift +++ b/Signal/src/Loki/Messaging/LokiRSSFeedPoller.swift @@ -28,41 +28,44 @@ public final class LokiRSSFeedPoller : NSObject { private func poll() { let feed = self.feed - let url = URL(string: feed.server)! - FeedParser(URL: url).parseAsync { wrapper in - guard case .rss(let x) = wrapper, let items = x.items else { return print("[Loki] Failed to parse RSS feed for: \(feed.server).") } - items.reversed().forEach { item in - guard let title = item.title, let description = item.description, let date = item.pubDate else { return } - let timestamp = UInt64(date.timeIntervalSince1970 * 1000) - let urlRegex = try! NSRegularExpression(pattern: "]*?\\s+)?href=\"([^\"]*)\".*?>(.*?)<.*?\\/a>") - var bodyAsHTML = "\(title)

\(description)".replacingOccurrences(of: "

", with: "


") - while true { - guard let match = urlRegex.firstMatch(in: bodyAsHTML, options: [], range: NSRange(location: 0, length: bodyAsHTML.utf16.count)) else { break } - let matchRange = match.range(at: 0) - let urlRange = match.range(at: 1) - let descriptionRange = match.range(at: 2) - let url = (bodyAsHTML as NSString).substring(with: urlRange) - let description = (bodyAsHTML as NSString).substring(with: descriptionRange) - bodyAsHTML = (bodyAsHTML as NSString).replacingCharacters(in: matchRange, with: "\(description) (\(url))") as String - } - guard let bodyAsData = bodyAsHTML.data(using: String.Encoding.unicode) else { return } - let options = [ NSAttributedString.DocumentReadingOptionKey.documentType : NSAttributedString.DocumentType.html ] - guard let body = try? NSAttributedString(data: bodyAsData, options: options, documentAttributes: nil).string else { return } - let id = LKGroupUtilities.getEncodedRSSFeedIDAsData(feed.id) - let groupContext = SSKProtoGroupContext.builder(id: id, type: .deliver) - groupContext.setName(feed.displayName) - let dataMessage = SSKProtoDataMessage.builder() - dataMessage.setTimestamp(timestamp) - dataMessage.setGroup(try! groupContext.build()) - dataMessage.setBody(body) - let content = SSKProtoContent.builder() - content.setDataMessage(try! dataMessage.build()) - let envelope = SSKProtoEnvelope.builder(type: .ciphertext, timestamp: timestamp) - envelope.setSource(NSLocalizedString("Loki", comment: "")) - envelope.setSourceDevice(OWSDevicePrimaryDeviceId) - envelope.setContent(try! content.build().serializedData()) - OWSPrimaryStorage.shared().dbReadWriteConnection.readWrite { transaction in - SSKEnvironment.shared.messageManager.throws_processEnvelope(try! envelope.build(), plaintextData: try! content.build().serializedData(), wasReceivedByUD: false, transaction: transaction, serverID: 0) + let url = feed.server + let _ = LokiRSSFeedProxy.fetchContent(for: url).done { xml in + guard let data = xml.data(using: String.Encoding.utf8) else { return print("[Loki] Failed to parse RSS feed for: \(feed.server).") } + FeedParser(data: data).parseAsync { wrapper in + guard case .rss(let x) = wrapper, let items = x.items else { return print("[Loki] Failed to parse RSS feed for: \(feed.server).") } + items.reversed().forEach { item in + guard let title = item.title, let description = item.description, let date = item.pubDate else { return } + let timestamp = UInt64(date.timeIntervalSince1970 * 1000) + let urlRegex = try! NSRegularExpression(pattern: "]*?\\s+)?href=\"([^\"]*)\".*?>(.*?)<.*?\\/a>") + var bodyAsHTML = "\(title)

\(description)".replacingOccurrences(of: "

", with: "


") + while true { + guard let match = urlRegex.firstMatch(in: bodyAsHTML, options: [], range: NSRange(location: 0, length: bodyAsHTML.utf16.count)) else { break } + let matchRange = match.range(at: 0) + let urlRange = match.range(at: 1) + let descriptionRange = match.range(at: 2) + let url = (bodyAsHTML as NSString).substring(with: urlRange) + let description = (bodyAsHTML as NSString).substring(with: descriptionRange) + bodyAsHTML = (bodyAsHTML as NSString).replacingCharacters(in: matchRange, with: "\(description) (\(url))") as String + } + guard let bodyAsData = bodyAsHTML.data(using: String.Encoding.unicode) else { return } + let options = [ NSAttributedString.DocumentReadingOptionKey.documentType : NSAttributedString.DocumentType.html ] + guard let body = try? NSAttributedString(data: bodyAsData, options: options, documentAttributes: nil).string else { return } + let id = LKGroupUtilities.getEncodedRSSFeedIDAsData(feed.id) + let groupContext = SSKProtoGroupContext.builder(id: id, type: .deliver) + groupContext.setName(feed.displayName) + let dataMessage = SSKProtoDataMessage.builder() + dataMessage.setTimestamp(timestamp) + dataMessage.setGroup(try! groupContext.build()) + dataMessage.setBody(body) + let content = SSKProtoContent.builder() + content.setDataMessage(try! dataMessage.build()) + let envelope = SSKProtoEnvelope.builder(type: .ciphertext, timestamp: timestamp) + envelope.setSource(NSLocalizedString("Loki", comment: "")) + envelope.setSourceDevice(OWSDevicePrimaryDeviceId) + envelope.setContent(try! content.build().serializedData()) + OWSPrimaryStorage.shared().dbReadWriteConnection.readWrite { transaction in + SSKEnvironment.shared.messageManager.throws_processEnvelope(try! envelope.build(), plaintextData: try! content.build().serializedData(), wasReceivedByUD: false, transaction: transaction, serverID: 0) + } } } } diff --git a/SignalServiceKit/src/Loki/API/LokiFileServerProxy.swift b/SignalServiceKit/src/Loki/API/LokiFileServerProxy.swift index 127847955..986c65e89 100644 --- a/SignalServiceKit/src/Loki/API/LokiFileServerProxy.swift +++ b/SignalServiceKit/src/Loki/API/LokiFileServerProxy.swift @@ -87,23 +87,42 @@ internal class LokiFileServerProxy : LokiHTTPClient { task.resume() return promise }.map { rawResponse in - guard let data = rawResponse as? Data, !data.isEmpty, let cipherText = Data(base64Encoded: data) else { - print("[Loki] Received a non-string encoded response.") + guard let data = rawResponse as? Data, !data.isEmpty else { + print("[Loki] Received an empty response.") return rawResponse } + var uncheckedStatusCode: Int? = nil + let uncheckedCipherText: Data? + if let json = try? JSONSerialization.jsonObject(with: data, options: []) as? JSON, let base64EncodedData = json["data"] as? String { + if let meta = json["meta"] as? JSON { uncheckedStatusCode = meta["code"] as? Int } + uncheckedCipherText = Data(base64Encoded: base64EncodedData) + } else { + uncheckedCipherText = data + } + guard let cipherText = uncheckedCipherText else { + print("[Loki] Received an invalid response.") + throw Error.proxyResponseParsingFailed + } let response = try DiffieHellman.decrypt(cipherText, using: symmetricKey) let uncheckedJSON = try? JSONSerialization.jsonObject(with: response, options: .allowFragments) as? JSON - guard let json = uncheckedJSON, let httpStatusCode = json["status"] as? Int else { throw HTTPError.networkError(code: -1, response: nil, underlyingError: Error.proxyResponseParsingFailed) } - let isSuccess = (200..<300).contains(httpStatusCode) + guard let json = uncheckedJSON else { throw HTTPError.networkError(code: -1, response: nil, underlyingError: Error.proxyResponseParsingFailed) } + if uncheckedStatusCode == nil { + uncheckedStatusCode = json["status"] as? Int + } + guard let statusCode = uncheckedStatusCode else { + print("[Loki] Received an invalid response.") + throw Error.proxyResponseParsingFailed + } + let isSuccess = (200..<300).contains(statusCode) var body: Any? = nil if let bodyAsString = json["body"] as? String { body = bodyAsString - if let bodyAsJSON = try? JSONSerialization.jsonObject(with: bodyAsString.data(using: .utf8)!, options: .allowFragments) as? [String: Any] { + if let bodyAsJSON = try? JSONSerialization.jsonObject(with: bodyAsString.data(using: .utf8)!, options: .allowFragments) as? JSON { body = bodyAsJSON } } - guard isSuccess else { throw HTTPError.networkError(code: httpStatusCode, response: body, underlyingError: Error.fileServerHTTPError(code: httpStatusCode, message: body)) } - return body + guard isSuccess else { throw HTTPError.networkError(code: statusCode, response: body, underlyingError: Error.fileServerHTTPError(code: statusCode, message: body)) } + return body ?? response }.recover { error -> Promise in print("[Loki] File server proxy request failed with error: \(error.localizedDescription).") throw HTTPError.from(error: error) ?? error diff --git a/SignalServiceKit/src/Loki/API/LokiRSSFeedProxy.swift b/SignalServiceKit/src/Loki/API/LokiRSSFeedProxy.swift index 3cf43e27c..9c28f352f 100644 --- a/SignalServiceKit/src/Loki/API/LokiRSSFeedProxy.swift +++ b/SignalServiceKit/src/Loki/API/LokiRSSFeedProxy.swift @@ -1,26 +1,26 @@ import PromiseKit -internal enum LokiRSSFeedProxy { +public enum LokiRSSFeedProxy { - internal enum Error : LocalizedError { + public enum Error : LocalizedError { case proxyResponseParsingFailed - internal var errorDescription: String? { + public var errorDescription: String? { switch self { case .proxyResponseParsingFailed: return "Couldn't parse proxy response." } } } - internal static func fetchContent(for url: String) -> Promise { + public static func fetchContent(for url: String) -> Promise { let server = LokiStorageAPI.server let endpoints = [ "messenger-updates/feed" : "loki/v1/rss/messenger", "loki.network/feed" : "loki/v1/rss/loki" ] let endpoint = endpoints.first { url.lowercased().contains($0.key) }!.value let url = URL(string: server + "/" + endpoint)! let request = TSRequest(url: url) return LokiFileServerProxy(for: server).perform(request).map { response -> String in - guard let json = response as? JSON, let data = json["data"] as? String else { throw Error.proxyResponseParsingFailed } - return data + guard let data = response as? Data, let json = try? JSONSerialization.jsonObject(with: data, options: []) as? JSON, let xml = json["data"] as? String else { throw Error.proxyResponseParsingFailed } + return xml } } } diff --git a/SignalServiceKit/src/Loki/API/LokiSnodeProxy.swift b/SignalServiceKit/src/Loki/API/LokiSnodeProxy.swift index 8fa9b1179..c32747566 100644 --- a/SignalServiceKit/src/Loki/API/LokiSnodeProxy.swift +++ b/SignalServiceKit/src/Loki/API/LokiSnodeProxy.swift @@ -78,7 +78,7 @@ internal class LokiSnodeProxy : LokiHTTPClient { var body: Any? = nil if let bodyAsString = json["body"] as? String { body = bodyAsString - if let bodyAsJSON = try? JSONSerialization.jsonObject(with: bodyAsString.data(using: .utf8)!, options: .allowFragments) as? [String: Any] { + if let bodyAsJSON = try? JSONSerialization.jsonObject(with: bodyAsString.data(using: .utf8)!, options: .allowFragments) as? JSON { body = bodyAsJSON } }