Skip to content

Commit

Permalink
fix: add extra percent encoding to avoid origin url truncation
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewjl-mux committed Apr 26, 2024
1 parent 47cfaf7 commit 6d0200b
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 13 deletions.
51 changes: 39 additions & 12 deletions Sources/MuxPlayerSwift/ReverseProxyServer/ReverseProxyServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class ReverseProxyServer {
.components(separatedBy: .newlines)
.map { line in self.processPlaylistLine(line, forOriginURL: playlistOriginURL) }
.joined(separator: "\n")
.removingPercentEncoding

return parsedManifest?.data(using: .utf8)
}
Expand Down Expand Up @@ -105,7 +106,17 @@ class ReverseProxyServer {
components.host = "127.0.0.1"
components.port = Int(port)

let originURLQueryItem = URLQueryItem(name: originURLKey, value: originURL.absoluteString)
// Additional encoding step for ampersands is
// required to avoid truncating origin URL
let encodedOriginURLQueryValue = originURL.absoluteString
.addingPercentEncoding(
withAllowedCharacters: CharacterSet.urlQueryAllowed.subtracting(CharacterSet(charactersIn:"&"))
)

let originURLQueryItem = URLQueryItem(
name: originURLKey,
value: encodedOriginURLQueryValue
)
components.queryItems = (components.queryItems ?? []) + [originURLQueryItem]

return components.url
Expand Down Expand Up @@ -362,11 +373,25 @@ class ReverseProxyServer {
) { [weak self] request, completion in

guard let self = self else {
return completion(GCDWebServerDataResponse(statusCode: 500))
return completion(
GCDWebServerDataResponse(
statusCode: 500
)
)
}

guard let originURL = self.originURL(from: request) else {
return completion(GCDWebServerErrorResponse(statusCode: 400))
guard let originURL = self.originURL(
from: request
),
var strippedRequestComponents = URLComponents(
url: originURL,
resolvingAgainstBaseURL: false
) else {
return completion(
GCDWebServerErrorResponse(
statusCode: 400
)
)
}

eventRecorder.didRecord(
Expand All @@ -376,25 +401,27 @@ class ReverseProxyServer {
)
)

var reverseProxyRequest = URLRequest(url: originURL)
reverseProxyRequest.httpMethod = "GET"
var originRequest = URLRequest(
url: originURL
)
originRequest.httpMethod = "GET"

// Construct a modified request that will be the
// same across segment requests
// - Remove query parameters
// - Replace cdn-specific hosts with generic
//
// This request only used as a cache key
var components = URLComponents(url: originURL, resolvingAgainstBaseURL: false)
components?.queryItems = nil
components?.host = "stream.mux.com"
strippedRequestComponents.queryItems = nil
strippedRequestComponents.host = "stream.mux.com"

var strippedRequest = URLRequest(url: components!.url!)
var strippedRequest = URLRequest(

Check warning on line 418 in Sources/MuxPlayerSwift/ReverseProxyServer/ReverseProxyServer.swift

View workflow job for this annotation

GitHub Actions / Run Unit Tests

variable 'strippedRequest' was never mutated; consider changing to 'let' constant
url: strippedRequestComponents.url!
)

if let cachedResponse = self.segmentCache.cachedResponse(
for: strippedRequest
) {

eventRecorder.didRecord(
event: ReverseProxyEvent(
originURL: originURL,
Expand All @@ -420,7 +447,7 @@ class ReverseProxyServer {
)

let task = self.session.dataTask(
with: reverseProxyRequest
with: originRequest
) { [weak self] data, response, error in

guard let self = self else {
Expand Down
76 changes: 75 additions & 1 deletion Tests/MuxPlayerSwift/PlaylistURLLocalMapperTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ class PlaylistLocalURLMapperTests: XCTestCase {
)
}

func testRenditionPlaylistPlaylistLocalURLMapping() throws {
func testTransportStreamRenditionPlaylistPlaylistLocalURLMapping() throws {
let originalRenditionPlaylist = """
#EXTM3U
#EXT-X-VERSION:3
Expand Down Expand Up @@ -119,6 +119,80 @@ class PlaylistLocalURLMapperTests: XCTestCase {

let mapper = ReverseProxyServer.PlaylistLocalURLMapper()

let encodedOriginalPlaylist = try XCTUnwrap(
originalRenditionPlaylist.data(
using: .utf8
),
"Couldn't encode original rendition playlist"
)

let playlistOriginURL = try XCTUnwrap(
URL(string: "https://manifest-gcp-us-east1-vop1.fastly.mux.com/qyHnst9BVpSF4nZpMK8AcilKpgoNrCgNjEPLuepuB5rNKh008j8zOxI00VMlBMfKo7QFnBpHhQ6I8/rendition.m3u8?cdn=fastly&expires=1708059600&skid=default&signature=NjVjZWViZDBfMWNlMjdjZDFlNTg1MGVlNjJmMjVmNDFkMjY0ZTY0M2I2YWJhYzQ0ZjRhMTNlYjQ2YmNiMDMyZjYzNTFmMDI2Ng==&vsid=UxwWoZ023025LmoJn1vaJvtoDTLKPhmpL35e5wQtduTwfFSQyqcThzqR3Tw3fD7Jaq02Uc01nbIQBZg"),
"Couldn't create manifest origin URL"
)

let encodedMappedRenditionPlaylist = try XCTUnwrap(
mapper.processEncodedPlaylist(
encodedOriginalPlaylist,
playlistOriginURL: playlistOriginURL
),
"Couldn't map to local URLs in rendition playlist"
)

let mappedRenditionPlaylist = try XCTUnwrap(
String(
data: encodedMappedRenditionPlaylist,
encoding: .utf8
),
"Couldn't decode rendition playlist"
)

XCTAssertEqual(

Check failure on line 150 in Tests/MuxPlayerSwift/PlaylistURLLocalMapperTests.swift

View workflow job for this annotation

GitHub Actions / Run Unit Tests

testTransportStreamRenditionPlaylistPlaylistLocalURLMapping, XCTAssertEqual failed: ("#EXTM3U
mappedRenditionPlaylist,
expectedMappedRenditionPlaylist
)
}

func testCommonMediaApplicationFormatRenditionPlaylistPlaylistLocalURLMapping() throws {
let originalRenditionPlaylist = """
#EXTM3U
#EXT-X-VERSION:7
#EXT-X-TARGETDURATION:5
#EXT-X-MAP:URI="https://chunk-gcp-us-east4-vop1.fastly.mux.com/v1/chunk/wmtdhXzi16Nm5lo5VajnhCDC001t9KfUz01IsA00zPvgGGh9hMcUQdQdNiP11xMcHyswkOrtMv00xFcmvN01bm7JB9pDy29pPwgjc/18446744073709551615.m4s?skid=default&signature=NjYzNDFhZjBfNWQwNTEzNmIyN2FiNTc2ZTNlMjlmZTEzNjMxNWVmM2ZiMzNjYWFhNGM4ZGQyNDY5ZWU0ZjEwMjJhYzQyYjdhMg==&zone=0&vsid=mU01NBz01DP3bwVXMHa3qDU8JMDYIYhHquGs7W7pRwVmHF01pELH7oW902QAMeC02QNVaoR95C5SkR2Y"
#EXT-X-PLAYLIST-TYPE:VOD
#EXTINF:5,
https://chunk-gcp-us-east4-vop1.fastly.mux.com/v1/chunk/wmtdhXzi16Nm5lo5VajnhCDC001t9KfUz01IsA00zPvgGGh9hMcUQdQdNiP11xMcHyswkOrtMv00xFcmvN01bm7JB9pDy29pPwgjc/0.m4s?skid=default&signature=NjYzNDFhZjBfMjRmYzZmOTEwNDk1ZmI1OGY3NjI1MDdhNjI1NGMwYjliZGQ2ODg4OGYwOWY2ZmVkYjY4ODBkYTg3YzA4NmE5Yw==&zone=0&vsid=mU01NBz01DP3bwVXMHa3qDU8JMDYIYhHquGs7W7pRwVmHF01pELH7oW902QAMeC02QNVaoR95C5SkR2Y
#EXTINF:5,
https://chunk-gcp-us-east4-vop1.fastly.mux.com/v1/chunk/wmtdhXzi16Nm5lo5VajnhCDC001t9KfUz01IsA00zPvgGGh9hMcUQdQdNiP11xMcHyswkOrtMv00xFcmvN01bm7JB9pDy29pPwgjc/1.m4s?skid=default&signature=NjYzNDFhZjBfYjcyYWI0NGQ1Mzk1NGE4OTQyN2UyOTJiNzBmYWVhYzFkYzA1Mjc0YjVjMDUyNjYzZTQ3OTIyZGE1MzY4NWI4Yw==&zone=0&vsid=mU01NBz01DP3bwVXMHa3qDU8JMDYIYhHquGs7W7pRwVmHF01pELH7oW902QAMeC02QNVaoR95C5SkR2Y
#EXTINF:5,
https://chunk-gcp-us-east4-vop1.fastly.mux.com/v1/chunk/Q01hWbKcA7GrHSoCkoferPs581VUPyGEtG026SAJRuoF8XIhKem45uSlYoAFYMJElqcXDgpUMb4p800Wzpc00HS94W2kd9UQSlvc/2.m4s?skid=default&signature=NjYzNDFhZjBfMTNjMTgyMThhZTYyZjRjZWNiNDdhMjhmZmI0NjU2NzM1MDhkOWM0MTA2OWFlZGMxZDllNDc2MWI2YTk3ZjdjMA==&zone=0&vsid=mU01NBz01DP3bwVXMHa3qDU8JMDYIYhHquGs7W7pRwVmHF01pELH7oW902QAMeC02QNVaoR95C5SkR2Y
#EXTINF:5,
https://chunk-gcp-us-east4-vop1.fastly.mux.com/v1/chunk/NVqbqLtA6jes00it1pw8fWaqwyFc8rdk9wLQLJ33AsRizroI9Eaztkf3ZnSDEzxhnnzsZ59PR01Cg4q9XBTSwTqCBWcs3AsTBb/3.m4s?skid=default&signature=NjYzNDFhZjBfMmYzMjRjZDk0MzEwYTFkNWIxYmYxZmRhZjY3M2RlODc5MmJjOTE2ZGY1MTJjODA2MjU1NDk0MDc3M2IyOTdiOQ==&zone=0&vsid=mU01NBz01DP3bwVXMHa3qDU8JMDYIYhHquGs7W7pRwVmHF01pELH7oW902QAMeC02QNVaoR95C5SkR2Y
#EXTINF:3.85717,
https://chunk-gcp-us-east4-vop1.fastly.mux.com/v1/chunk/Z5jllnVxeBd3k5xxj74IfP8CG6XUNH01MMVItjOJBkJvjdylDQaGAuL2UiQqUEWseYm3sY401i2yy3taBbCIUsOQ/4.m4s?skid=default&signature=NjYzNDFhZjBfZGIyZjQ0NmMwNzBkZmYxMmM4M2FiZWNhNzVjN2VlYmUzOWRjMGY4NzIxYjU5ZjgzN2RkMTcyNmFmN2ExNDJhZQ==&zone=0&vsid=mU01NBz01DP3bwVXMHa3qDU8JMDYIYhHquGs7W7pRwVmHF01pELH7oW902QAMeC02QNVaoR95C5SkR2Y
#EXT-X-ENDLIST
"""

let expectedMappedRenditionPlaylist = """
#EXTM3U
#EXT-X-VERSION:7
#EXT-X-TARGETDURATION:5
#EXT-X-MAP:URI="http://127.0.0.1:1234/v1/chunk/wmtdhXzi16Nm5lo5VajnhCDC001t9KfUz01IsA00zPvgGGh9hMcUQdQdNiP11xMcHyswkOrtMv00xFcmvN01bm7JB9pDy29pPwgjc/18446744073709551615.m4s?skid=default&signature=NjYzNDFhZjBfNWQwNTEzNmIyN2FiNTc2ZTNlMjlmZTEzNjMxNWVmM2ZiMzNjYWFhNGM4ZGQyNDY5ZWU0ZjEwMjJhYzQyYjdhMg==&zone=0&vsid=mU01NBz01DP3bwVXMHa3qDU8JMDYIYhHquGs7W7pRwVmHF01pELH7oW902QAMeC02QNVaoR95C5SkR2Y&__hls_origin_url=https://chunk-gcp-us-east4-vop1.fastly.mux.com/v1/chunk/wmtdhXzi16Nm5lo5VajnhCDC001t9KfUz01IsA00zPvgGGh9hMcUQdQdNiP11xMcHyswkOrtMv00xFcmvN01bm7JB9pDy29pPwgjc/18446744073709551615.m4s?skid=default&signature=NjYzNDFhZjBfNWQwNTEzNmIyN2FiNTc2ZTNlMjlmZTEzNjMxNWVmM2ZiMzNjYWFhNGM4ZGQyNDY5ZWU0ZjEwMjJhYzQyYjdhMg==&zone=0&vsid=mU01NBz01DP3bwVXMHa3qDU8JMDYIYhHquGs7W7pRwVmHF01pELH7oW902QAMeC02QNVaoR95C5SkR2Y"
#EXT-X-PLAYLIST-TYPE:VOD
#EXTINF:5,
http://127.0.0.1:1234/v1/chunk/wmtdhXzi16Nm5lo5VajnhCDC001t9KfUz01IsA00zPvgGGh9hMcUQdQdNiP11xMcHyswkOrtMv00xFcmvN01bm7JB9pDy29pPwgjc/0.m4s?skid=default&signature=NjYzNDFhZjBfMjRmYzZmOTEwNDk1ZmI1OGY3NjI1MDdhNjI1NGMwYjliZGQ2ODg4OGYwOWY2ZmVkYjY4ODBkYTg3YzA4NmE5Yw==&zone=0&vsid=mU01NBz01DP3bwVXMHa3qDU8JMDYIYhHquGs7W7pRwVmHF01pELH7oW902QAMeC02QNVaoR95C5SkR2Y&__hls_origin_url=https://chunk-gcp-us-east4-vop1.fastly.mux.com/v1/chunk/wmtdhXzi16Nm5lo5VajnhCDC001t9KfUz01IsA00zPvgGGh9hMcUQdQdNiP11xMcHyswkOrtMv00xFcmvN01bm7JB9pDy29pPwgjc/0.m4s?skid=default&signature=NjYzNDFhZjBfMjRmYzZmOTEwNDk1ZmI1OGY3NjI1MDdhNjI1NGMwYjliZGQ2ODg4OGYwOWY2ZmVkYjY4ODBkYTg3YzA4NmE5Yw==&zone=0&vsid=mU01NBz01DP3bwVXMHa3qDU8JMDYIYhHquGs7W7pRwVmHF01pELH7oW902QAMeC02QNVaoR95C5SkR2Y
#EXTINF:5,
http://127.0.0.1:1234/v1/chunk/wmtdhXzi16Nm5lo5VajnhCDC001t9KfUz01IsA00zPvgGGh9hMcUQdQdNiP11xMcHyswkOrtMv00xFcmvN01bm7JB9pDy29pPwgjc/1.m4s?skid=default&signature=NjYzNDFhZjBfYjcyYWI0NGQ1Mzk1NGE4OTQyN2UyOTJiNzBmYWVhYzFkYzA1Mjc0YjVjMDUyNjYzZTQ3OTIyZGE1MzY4NWI4Yw==&zone=0&vsid=mU01NBz01DP3bwVXMHa3qDU8JMDYIYhHquGs7W7pRwVmHF01pELH7oW902QAMeC02QNVaoR95C5SkR2Y&__hls_origin_url=https://chunk-gcp-us-east4-vop1.fastly.mux.com/v1/chunk/wmtdhXzi16Nm5lo5VajnhCDC001t9KfUz01IsA00zPvgGGh9hMcUQdQdNiP11xMcHyswkOrtMv00xFcmvN01bm7JB9pDy29pPwgjc/1.m4s?skid=default&signature=NjYzNDFhZjBfYjcyYWI0NGQ1Mzk1NGE4OTQyN2UyOTJiNzBmYWVhYzFkYzA1Mjc0YjVjMDUyNjYzZTQ3OTIyZGE1MzY4NWI4Yw==&zone=0&vsid=mU01NBz01DP3bwVXMHa3qDU8JMDYIYhHquGs7W7pRwVmHF01pELH7oW902QAMeC02QNVaoR95C5SkR2Y
#EXTINF:5,
http://127.0.0.1:1234/v1/chunk/Q01hWbKcA7GrHSoCkoferPs581VUPyGEtG026SAJRuoF8XIhKem45uSlYoAFYMJElqcXDgpUMb4p800Wzpc00HS94W2kd9UQSlvc/2.m4s?skid=default&signature=NjYzNDFhZjBfMTNjMTgyMThhZTYyZjRjZWNiNDdhMjhmZmI0NjU2NzM1MDhkOWM0MTA2OWFlZGMxZDllNDc2MWI2YTk3ZjdjMA==&zone=0&vsid=mU01NBz01DP3bwVXMHa3qDU8JMDYIYhHquGs7W7pRwVmHF01pELH7oW902QAMeC02QNVaoR95C5SkR2Y&__hls_origin_url=https://chunk-gcp-us-east4-vop1.fastly.mux.com/v1/chunk/Q01hWbKcA7GrHSoCkoferPs581VUPyGEtG026SAJRuoF8XIhKem45uSlYoAFYMJElqcXDgpUMb4p800Wzpc00HS94W2kd9UQSlvc/2.m4s?skid=default&signature=NjYzNDFhZjBfMTNjMTgyMThhZTYyZjRjZWNiNDdhMjhmZmI0NjU2NzM1MDhkOWM0MTA2OWFlZGMxZDllNDc2MWI2YTk3ZjdjMA==&zone=0&vsid=mU01NBz01DP3bwVXMHa3qDU8JMDYIYhHquGs7W7pRwVmHF01pELH7oW902QAMeC02QNVaoR95C5SkR2Y
#EXTINF:5,
http://127.0.0.1:1234/v1/chunk/NVqbqLtA6jes00it1pw8fWaqwyFc8rdk9wLQLJ33AsRizroI9Eaztkf3ZnSDEzxhnnzsZ59PR01Cg4q9XBTSwTqCBWcs3AsTBb/3.m4s?skid=default&signature=NjYzNDFhZjBfMmYzMjRjZDk0MzEwYTFkNWIxYmYxZmRhZjY3M2RlODc5MmJjOTE2ZGY1MTJjODA2MjU1NDk0MDc3M2IyOTdiOQ==&zone=0&vsid=mU01NBz01DP3bwVXMHa3qDU8JMDYIYhHquGs7W7pRwVmHF01pELH7oW902QAMeC02QNVaoR95C5SkR2Y&__hls_origin_url=https://chunk-gcp-us-east4-vop1.fastly.mux.com/v1/chunk/NVqbqLtA6jes00it1pw8fWaqwyFc8rdk9wLQLJ33AsRizroI9Eaztkf3ZnSDEzxhnnzsZ59PR01Cg4q9XBTSwTqCBWcs3AsTBb/3.m4s?skid=default&signature=NjYzNDFhZjBfMmYzMjRjZDk0MzEwYTFkNWIxYmYxZmRhZjY3M2RlODc5MmJjOTE2ZGY1MTJjODA2MjU1NDk0MDc3M2IyOTdiOQ==&zone=0&vsid=mU01NBz01DP3bwVXMHa3qDU8JMDYIYhHquGs7W7pRwVmHF01pELH7oW902QAMeC02QNVaoR95C5SkR2Y
#EXTINF:3.85717,
http://127.0.0.1:1234/v1/chunk/Z5jllnVxeBd3k5xxj74IfP8CG6XUNH01MMVItjOJBkJvjdylDQaGAuL2UiQqUEWseYm3sY401i2yy3taBbCIUsOQ/4.m4s?skid=default&signature=NjYzNDFhZjBfZGIyZjQ0NmMwNzBkZmYxMmM4M2FiZWNhNzVjN2VlYmUzOWRjMGY4NzIxYjU5ZjgzN2RkMTcyNmFmN2ExNDJhZQ==&zone=0&vsid=mU01NBz01DP3bwVXMHa3qDU8JMDYIYhHquGs7W7pRwVmHF01pELH7oW902QAMeC02QNVaoR95C5SkR2Y&__hls_origin_url=https://chunk-gcp-us-east4-vop1.fastly.mux.com/v1/chunk/Z5jllnVxeBd3k5xxj74IfP8CG6XUNH01MMVItjOJBkJvjdylDQaGAuL2UiQqUEWseYm3sY401i2yy3taBbCIUsOQ/4.m4s?skid=default&signature=NjYzNDFhZjBfZGIyZjQ0NmMwNzBkZmYxMmM4M2FiZWNhNzVjN2VlYmUzOWRjMGY4NzIxYjU5ZjgzN2RkMTcyNmFmN2ExNDJhZQ==&zone=0&vsid=mU01NBz01DP3bwVXMHa3qDU8JMDYIYhHquGs7W7pRwVmHF01pELH7oW902QAMeC02QNVaoR95C5SkR2Y
#EXT-X-ENDLIST
"""

let mapper = ReverseProxyServer.PlaylistLocalURLMapper()

let encodedOriginalPlaylist = try XCTUnwrap(
originalRenditionPlaylist.data(
Expand Down

0 comments on commit 6d0200b

Please sign in to comment.