Use content proxy to configure all proxied content requests.

This commit is contained in:
Matthew Chen 2019-02-25 14:19:29 -05:00
parent a2d48aa020
commit 5eaeeff838
4 changed files with 128 additions and 72 deletions

View file

@ -310,6 +310,12 @@ extension GiphyError: LocalizedError {
}
let urlString = "/v1/gifs/search?api_key=\(kGiphyApiKey)&offset=\(kGiphyPageOffset)&limit=\(kGiphyPageSize)&q=\(queryEncoded)"
guard ContentProxy.configureSessionManager(sessionManager: sessionManager, forUrl: urlString) else {
owsFailDebug("Could not configure query: \(query).")
failure(nil)
return
}
sessionManager.get(urlString,
parameters: [String: AnyObject](),
progress: nil,

View file

@ -589,10 +589,10 @@ public class OWSLinkPreview: MTLModel {
}
}
class func downloadLink(url: String,
class func downloadLink(url urlString: String,
remainingRetries: UInt = 3) -> Promise<Data> {
Logger.verbose("url: \(url)")
Logger.verbose("url: \(urlString)")
let sessionConfiguration = ContentProxy.sessionConfiguration()
@ -605,13 +605,13 @@ public class OWSLinkPreview: MTLModel {
sessionManager.requestSerializer = AFHTTPRequestSerializer()
sessionManager.responseSerializer = AFHTTPResponseSerializer()
// Remove all headers from the request.
for headerField in sessionManager.requestSerializer.httpRequestHeaders.keys {
sessionManager.requestSerializer.setValue(nil, forHTTPHeaderField: headerField)
guard ContentProxy.configureSessionManager(sessionManager: sessionManager, forUrl: urlString) else {
owsFailDebug("Could not configure url: \(urlString).")
return Promise(error: LinkPreviewError.assertionFailure)
}
let (promise, resolver) = Promise<Data>.pending()
sessionManager.get(url,
sessionManager.get(urlString,
parameters: [String: AnyObject](),
progress: nil,
success: { task, value in
@ -654,7 +654,7 @@ public class OWSLinkPreview: MTLModel {
resolver.reject(LinkPreviewError.couldNotDownload)
return
}
OWSLinkPreview.downloadLink(url: url, remainingRetries: remainingRetries - 1)
OWSLinkPreview.downloadLink(url: urlString, remainingRetries: remainingRetries - 1)
.done(on: DispatchQueue.global()) { (data) in
resolver.fulfill(data)
}.catch(on: DispatchQueue.global()) { (error) in

View file

@ -51,4 +51,102 @@ public class ContentProxy: NSObject {
sessionManager.responseSerializer = AFJSONResponseSerializer()
return sessionManager
}
}
static let userAgent = "Signal iOS (+https://signal.org/download)"
public class func configureProxiedRequest(request: inout URLRequest) -> Bool {
request.addValue(userAgent, forHTTPHeaderField: "User-Agent")
padRequestSize(request: &request)
guard let url = request.url,
let scheme = url.scheme,
scheme.lowercased() == "https" else {
return false
}
return true
}
@objc
public class func configureSessionManager(sessionManager: AFHTTPSessionManager,
forUrl urlString: String) -> Bool {
guard let url = URL(string: urlString, relativeTo: sessionManager.baseURL) else {
owsFailDebug("Invalid URL query: \(urlString).")
return false
}
var request = URLRequest(url: url)
guard configureProxiedRequest(request: &request) else {
owsFailDebug("Invalid URL query: \(urlString).")
return false
}
// Remove all headers from the request.
for headerField in sessionManager.requestSerializer.httpRequestHeaders.keys {
sessionManager.requestSerializer.setValue(nil, forHTTPHeaderField: headerField)
}
// Honor the request's headers.
if let allHTTPHeaderFields = request.allHTTPHeaderFields {
for (headerField, headerValue) in allHTTPHeaderFields {
sessionManager.requestSerializer.setValue(headerValue, forHTTPHeaderField: headerField)
}
}
return true
}
public class func padRequestSize(request: inout URLRequest) {
guard let sizeEstimate: UInt = estimateRequestSize(request: request) else {
owsFailDebug("Could not estimate request size.")
return
}
// We pad the estimated size to an even multiple of paddingQuantum (plus the
// extra ": " and "\r\n"). The exact size doesn't matter so long as the
// padding is consistent.
let paddingQuantum: UInt = 1024
let paddingSize = paddingQuantum - (sizeEstimate % paddingQuantum)
let padding = String(repeating: ".", count: Int(paddingSize))
request.addValue(padding, forHTTPHeaderField: "X-SignalPadding")
}
private class func estimateRequestSize(request: URLRequest) -> UInt? {
// iOS doesn't offer an exact way to measure request sizes on the wire,
// but we can reliably estimate request sizes using the "knowns", e.g.
// HTTP method, path, querystring, headers. The "unknowns" should be
// consistent between requests.
guard let url = request.url?.absoluteString else {
owsFailDebug("Request missing URL.")
return nil
}
guard let components = URLComponents(string: url) else {
owsFailDebug("Request has invalid URL.")
return nil
}
var result: Int = 0
if let httpMethod = request.httpMethod {
result += httpMethod.count
}
result += components.percentEncodedPath.count
if let percentEncodedQuery = components.percentEncodedQuery {
result += percentEncodedQuery.count
}
if let allHTTPHeaderFields = request.allHTTPHeaderFields {
for (key, value) in allHTTPHeaderFields {
// Each header has 4 extra bytes:
//
// * Two for the key/value separator ": "
// * Two for "\r\n", the line break in the HTTP protocol spec.
result += key.count + value.count + 4
}
} else {
owsFailDebug("Request has no headers.")
}
if let httpBody = request.httpBody {
result += httpBody.count
}
return UInt(result)
}}

View file

@ -657,8 +657,6 @@ open class ProxiedContentDownloader: NSObject, URLSessionTaskDelegate, URLSessio
return
}
let userAgent = "Signal iOS (+https://signal.org/download)"
if assetRequest.state == .waiting {
// If asset request hasn't yet determined the resource size,
// try to do so now, by requesting a small initial segment.
@ -671,8 +669,14 @@ open class ProxiedContentDownloader: NSObject, URLSessionTaskDelegate, URLSessio
request.httpShouldUsePipelining = true
let rangeHeaderValue = "bytes=\(segmentStart)-\(segmentStart + segmentLength - 1)"
request.addValue(rangeHeaderValue, forHTTPHeaderField: "Range")
request.addValue(userAgent, forHTTPHeaderField: "User-Agent")
padRequestSize(request: &request)
guard ContentProxy.configureProxiedRequest(request: &request) else {
assetRequest.state = .failed
assetRequestDidFail(assetRequest: assetRequest)
processRequestQueueSync()
return
}
let task = downloadSession.dataTask(with: request, completionHandler: { data, response, error -> Void in
self.handleAssetSizeResponse(assetRequest: assetRequest, data: data, response: response, error: error)
})
@ -692,8 +696,14 @@ open class ProxiedContentDownloader: NSObject, URLSessionTaskDelegate, URLSessio
request.httpShouldUsePipelining = true
let rangeHeaderValue = "bytes=\(assetSegment.segmentStart)-\(assetSegment.segmentStart + assetSegment.segmentLength - 1)"
request.addValue(rangeHeaderValue, forHTTPHeaderField: "Range")
request.addValue(userAgent, forHTTPHeaderField: "User-Agent")
padRequestSize(request: &request)
guard ContentProxy.configureProxiedRequest(request: &request) else {
assetRequest.state = .failed
assetRequestDidFail(assetRequest: assetRequest)
processRequestQueueSync()
return
}
let task: URLSessionDataTask = downloadSession.dataTask(with: request)
task.assetRequest = assetRequest
task.assetSegment = assetSegment
@ -705,64 +715,6 @@ open class ProxiedContentDownloader: NSObject, URLSessionTaskDelegate, URLSessio
processRequestQueueSync()
}
private func padRequestSize(request: inout URLRequest) {
guard let sizeEstimate: UInt = estimateRequestSize(request: request) else {
owsFailDebug("Could not estimate request size.")
return
}
// We pad the estimated size to an even multiple of paddingQuantum (plus the
// extra ": " and "\r\n"). The exact size doesn't matter so long as the
// padding is consistent.
let paddingQuantum: UInt = 1024
let paddingSize = paddingQuantum - (sizeEstimate % paddingQuantum)
let padding = String(repeating: ".", count: Int(paddingSize))
request.addValue(padding, forHTTPHeaderField: "X-SignalPadding")
}
private func estimateRequestSize(request: URLRequest) -> UInt? {
// iOS doesn't offer an exact way to measure request sizes on the wire,
// but we can reliably estimate request sizes using the "knowns", e.g.
// HTTP method, path, querystring, headers. The "unknowns" should be
// consistent between requests.
guard let url = request.url?.absoluteString else {
owsFailDebug("Request missing URL.")
return nil
}
guard let components = URLComponents(string: url) else {
owsFailDebug("Request has invalid URL.")
return nil
}
var result: Int = 0
if let httpMethod = request.httpMethod {
result += httpMethod.count
}
result += components.percentEncodedPath.count
if let percentEncodedQuery = components.percentEncodedQuery {
result += percentEncodedQuery.count
}
if let allHTTPHeaderFields = request.allHTTPHeaderFields {
if allHTTPHeaderFields.count != 2 {
owsFailDebug("Request has unexpected number of headers.")
}
for (key, value) in allHTTPHeaderFields {
// Each header has 4 extra bytes:
//
// * Two for the key/value separator ": "
// * Two for "\r\n", the line break in the HTTP protocol spec.
result += key.count + value.count + 4
}
} else {
owsFailDebug("Request has no headers.")
}
if let httpBody = request.httpBody {
result += httpBody.count
}
return UInt(result)
}
private func handleAssetSizeResponse(assetRequest: ProxiedContentAssetRequest, data: Data?, response: URLResponse?, error: Error?) {
guard error == nil else {
assetRequest.state = .failed