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
}
}