Browse Source

Add rewriting of tunneled URL proxy requests

There's a new URL proxy path: `/tunneled-rewrite/`. Responses to requests to that path can be rewritten/modified. For example, URLs within a fetched file can be altered so that they go through the URL proxy.
Adam Pritchard 8 years ago
parent
commit
4012d18661

+ 192 - 14
psiphon/httpProxy.go

@@ -20,16 +20,21 @@
 package psiphon
 
 import (
+	"bytes"
 	"errors"
 	"fmt"
 	"io"
+	"io/ioutil"
 	"net"
 	"net/http"
 	"net/url"
+	"path/filepath"
+	"strconv"
 	"strings"
 	"sync"
 	"time"
 
+	"github.com/Psiphon-Inc/m3u8"
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
 )
 
@@ -54,6 +59,12 @@ import (
 // "http://127.0.0.1:<proxy-port>/direct/<origin URL>".
 // Again, the <origin URL> must be escaped in such a way that it can be used inside a URL query.
 //
+// An example use case for tunneled relaying with rewriting (/tunneled-rewrite/) is when the
+// content of retrieved files contains URLs that also need to be modified to be tunneled.
+// For example, in iOS 10 the UIWebView media player does not put requests through the
+// NSURLProtocol, so they are no tunneled. Instead, we rewrite those URLs to use the URL
+// proxy, and rewrite retrieved playlist files so they also contain proxied URLs.
+//
 // Origin URLs must include the scheme prefix ("http://" or "https://") and must be
 // URL encoded.
 //
@@ -240,28 +251,34 @@ func (proxy *HttpProxy) httpConnectHandler(localConn net.Conn, target string) (e
 }
 
 func (proxy *HttpProxy) httpProxyHandler(responseWriter http.ResponseWriter, request *http.Request) {
-	relayHttpRequest(nil, proxy.httpProxyTunneledRelay, request, responseWriter)
+	proxy.relayHTTPRequest(nil, proxy.httpProxyTunneledRelay, request, responseWriter, nil)
 }
 
 const (
 	URL_PROXY_TUNNELED_REQUEST_PATH = "/tunneled/"
+	URL_PROXY_REWRITE_REQUEST_PATH  = "/tunneled-rewrite/"
 	URL_PROXY_DIRECT_REQUEST_PATH   = "/direct/"
 )
 
 func (proxy *HttpProxy) urlProxyHandler(responseWriter http.ResponseWriter, request *http.Request) {
 
 	var client *http.Client
-	var originUrl string
+	var originURLString string
 	var err error
+	var rewrites url.Values
 
 	// Request URL should be "/tunneled/<origin URL>" or  "/direct/<origin URL>" and the
 	// origin URL must be URL encoded.
 	switch {
 	case strings.HasPrefix(request.URL.RawPath, URL_PROXY_TUNNELED_REQUEST_PATH):
-		originUrl, err = url.QueryUnescape(request.URL.RawPath[len(URL_PROXY_TUNNELED_REQUEST_PATH):])
+		originURLString, err = url.QueryUnescape(request.URL.RawPath[len(URL_PROXY_TUNNELED_REQUEST_PATH):])
+		client = proxy.urlProxyTunneledClient
+	case strings.HasPrefix(request.URL.RawPath, URL_PROXY_REWRITE_REQUEST_PATH):
+		originURLString, err = url.QueryUnescape(request.URL.RawPath[len(URL_PROXY_REWRITE_REQUEST_PATH):])
 		client = proxy.urlProxyTunneledClient
+		rewrites = request.URL.Query()
 	case strings.HasPrefix(request.URL.RawPath, URL_PROXY_DIRECT_REQUEST_PATH):
-		originUrl, err = url.QueryUnescape(request.URL.RawPath[len(URL_PROXY_DIRECT_REQUEST_PATH):])
+		originURLString, err = url.QueryUnescape(request.URL.RawPath[len(URL_PROXY_DIRECT_REQUEST_PATH):])
 		client = proxy.urlProxyDirectClient
 	default:
 		err = errors.New("missing origin URL")
@@ -272,31 +289,32 @@ func (proxy *HttpProxy) urlProxyHandler(responseWriter http.ResponseWriter, requ
 		return
 	}
 
-	// Origin URL must be well-formed, absolute, and have a scheme of  "http" or "https"
-	url, err := url.ParseRequestURI(originUrl)
+	// Origin URL must be well-formed, absolute, and have a scheme of "http" or "https"
+	originURL, err := url.ParseRequestURI(originURLString)
 	if err != nil {
 		NoticeAlert("%s", common.ContextError(FilterUrlError(err)))
 		forceClose(responseWriter)
 		return
 	}
-	if !url.IsAbs() || (url.Scheme != "http" && url.Scheme != "https") {
+	if !originURL.IsAbs() || (originURL.Scheme != "http" && originURL.Scheme != "https") {
 		NoticeAlert("invalid origin URL")
 		forceClose(responseWriter)
 		return
 	}
 
 	// Transform received request to directly reference the origin URL
-	request.Host = url.Host
-	request.URL = url
+	request.Host = originURL.Host
+	request.URL = originURL
 
-	relayHttpRequest(client, nil, request, responseWriter)
+	proxy.relayHTTPRequest(client, nil, request, responseWriter, rewrites)
 }
 
-func relayHttpRequest(
+func (proxy *HttpProxy) relayHTTPRequest(
 	client *http.Client,
 	transport *http.Transport,
 	request *http.Request,
-	responseWriter http.ResponseWriter) {
+	responseWriter http.ResponseWriter,
+	rewrites url.Values) {
 
 	// Transform received request struct before using as input to relayed request
 	request.Close = false
@@ -321,13 +339,33 @@ func relayHttpRequest(
 		forceClose(responseWriter)
 		return
 	}
-	defer response.Body.Close()
+
+	if rewrites != nil {
+		// NOTE: Rewrite functions are responsible for leaving response.Body in
+		// a valid, readable state if there's no error.
+
+		for key := range rewrites {
+			var err error
+
+			switch key {
+			case "m3u8":
+				err = rewriteM3U8(proxy.listener.Addr().(*net.TCPAddr).Port, response)
+			}
+
+			if err != nil {
+				NoticeAlert("URL proxy rewrite failed for %s: %s", key, common.ContextError(err))
+				forceClose(responseWriter)
+				response.Body.Close()
+				return
+			}
+		}
+	}
 
 	// Relay the remote response headers
 	for _, key := range hopHeaders {
 		response.Header.Del(key)
 	}
-	for key, _ := range responseWriter.Header() {
+	for key := range responseWriter.Header() {
 		responseWriter.Header().Del(key)
 	}
 	for key, values := range response.Header {
@@ -336,6 +374,8 @@ func relayHttpRequest(
 		}
 	}
 
+	defer response.Body.Close()
+
 	// Relay the response code and body
 	responseWriter.WriteHeader(response.StatusCode)
 	_, err = io.Copy(responseWriter, response.Body)
@@ -411,3 +451,141 @@ func (proxy *HttpProxy) serve() {
 	}
 	NoticeInfo("HTTP proxy stopped")
 }
+
+//
+// Rewrite functions
+//
+
+// toAbsoluteURL takes a base URL and a relative URL and constructs an appropriate absolute URL.
+func toAbsoluteURL(baseURL *url.URL, relativeURLString string) string {
+	relativeURL, err := url.Parse(relativeURLString)
+
+	if err != nil {
+		return ""
+	}
+
+	if relativeURL.IsAbs() {
+		return relativeURL.String()
+	}
+
+	return baseURL.ResolveReference(relativeURL).String()
+}
+
+// proxifyURL takes an absolute URL and rewrites it to go through the local URL proxy.
+// urlProxy port is the local HTTP proxy port.
+// If rewriteParams is nil, then no rewriting will be done. Otherwise, it should contain
+// supported rewriting flags (like "m3u8").
+func proxifyURL(urlProxyPort int, urlString string, rewriteParams []string) string {
+	// Note that we need to use the "opaque" form of URL so that it doesn't double-escape the path. See: https://github.com/golang/go/issues/10887
+
+	opaqueFormat := "//127.0.0.1:%d/tunneled/%s"
+	if rewriteParams != nil {
+		opaqueFormat = "//127.0.0.1:%d/tunneled-rewrite/%s"
+	}
+
+	var proxifiedURL url.URL
+
+	proxifiedURL.Scheme = "http"
+	proxifiedURL.Opaque = fmt.Sprintf(opaqueFormat, urlProxyPort, url.QueryEscape(urlString))
+
+	qp := proxifiedURL.Query()
+	for _, rewrite := range rewriteParams {
+		qp.Set(rewrite, "")
+	}
+	proxifiedURL.RawQuery = qp.Encode()
+
+	return proxifiedURL.String()
+}
+
+// Rewrite the contents of the M3U8 file in body to be compatible with URL proxying.
+// If error is returned, response body may not be valid.
+func rewriteM3U8(httpProxyPort int, response *http.Response) error {
+	// Check URL path extension
+	extension := filepath.Ext(response.Request.URL.Path)
+	var shouldHandle = (extension == ".m3u8")
+
+	// If not .m3u8 then check content type
+	if !shouldHandle {
+		contentType := response.Header.Get("Content-Type")
+		shouldHandle = (contentType == "application/x-mpegURL" || contentType == "vnd.apple.mpegURL")
+	}
+
+	if !shouldHandle {
+		return nil
+	}
+
+	bodyBytes, err := ioutil.ReadAll(response.Body)
+	response.Body.Close()
+	if err != nil {
+		return common.ContextError(err)
+	}
+
+	p, listType, err := m3u8.Decode(*bytes.NewBuffer(bodyBytes), true)
+	if err != nil {
+		// Don't pass this error up. Just don't change anything.
+		response.Body = ioutil.NopCloser(bytes.NewReader(bodyBytes))
+		return nil
+	}
+
+	var newBody string
+
+	switch listType {
+	case m3u8.MEDIA:
+		mediapl := p.(*m3u8.MediaPlaylist)
+		for _, segment := range mediapl.Segments {
+			if segment == nil {
+				break
+			}
+
+			if segment.URI != "" {
+				segment.URI = proxifyURL(httpProxyPort, toAbsoluteURL(response.Request.URL, segment.URI), nil)
+			}
+
+			if segment.Key != nil && segment.Key.URI != "" {
+				segment.Key.URI = proxifyURL(httpProxyPort, toAbsoluteURL(response.Request.URL, segment.Key.URI), nil)
+			}
+
+			if segment.Map != nil && segment.Map.URI != "" {
+				segment.Map.URI = proxifyURL(httpProxyPort, toAbsoluteURL(response.Request.URL, segment.Map.URI), nil)
+			}
+		}
+		newBody = mediapl.String()
+	case m3u8.MASTER:
+		masterpl := p.(*m3u8.MasterPlaylist)
+		for _, variant := range masterpl.Variants {
+			if variant == nil {
+				break
+			}
+
+			if variant.URI != "" {
+				variant.URI = proxifyURL(httpProxyPort, toAbsoluteURL(response.Request.URL, variant.URI), []string{"m3u8"})
+			}
+
+			for _, alternative := range variant.Alternatives {
+				if alternative == nil {
+					break
+				}
+
+				if alternative.URI != "" {
+					alternative.URI = proxifyURL(httpProxyPort, toAbsoluteURL(response.Request.URL, alternative.URI), []string{"m3u8"})
+				}
+			}
+		}
+		newBody = masterpl.String()
+	}
+
+	if newBody == "" {
+		// Unknown playlist type. Leave the response unaltered.
+		response.Body = ioutil.NopCloser(bytes.NewReader(bodyBytes))
+		return nil
+	}
+
+	response.Body = ioutil.NopCloser(strings.NewReader(newBody))
+	response.Header.Set("Content-Length", strconv.FormatInt(int64(len(newBody)), 10))
+
+	// When rewriting the original URL so that it was URL-proxied, we lost the
+	// file extension of it. That means we'd better make sure the Content-Type is set.
+	response.Header.Set("Content-Type", "application/x-mpegURL")
+
+	return nil
+}

+ 141 - 0
psiphon/httpProxy_test.go

@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2017, Psiphon Inc.
+ * All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package psiphon
+
+import (
+	"bytes"
+	"io/ioutil"
+	"net/http"
+	"net/url"
+	"os"
+	"strconv"
+	"testing"
+)
+
+func TestToAbsoluteURL(t *testing.T) {
+	var urlTests = []struct {
+		base     string
+		relative string
+		expected string
+	}{
+		{"http://example.com/path1?q=p#hash", "relative/path", "http://example.com/relative/path"},
+		{"http://example.com/path1?q=p#hash", "relative/path?a=b", "http://example.com/relative/path?a=b"},
+		{"http://example.com/path1?q=p#hash", "relative/path#c", "http://example.com/relative/path#c"},
+		{"http://example.com/path1?q=p#hash", "relative/path?a=b#c", "http://example.com/relative/path?a=b#c"},
+		{"http://example.com/path1/path2?q=p#hash", "relative/path", "http://example.com/path1/relative/path"},
+		{"http://example.com/path1/path2?q=p#hash", "/relative/path", "http://example.com/relative/path"},
+		{"http://example.com/path1/path2?q=p#hash", "http://example.org/absolute/path", "http://example.org/absolute/path"},
+	}
+
+	for _, tt := range urlTests {
+		baseURL, _ := url.Parse(tt.base)
+		absURL := toAbsoluteURL(baseURL, tt.relative)
+		if absURL != tt.expected {
+			t.Errorf("toAbsoluteURL(%s, %s): expected %s, actual %s", tt.base, tt.relative, tt.expected, absURL)
+		}
+	}
+}
+
+func TestProxifyURL(t *testing.T) {
+	var urlTests = []struct {
+		port          int
+		urlString     string
+		rewriteParams []string
+		expected      string
+	}{
+		{1234, "http://example.com/media/pl.m3u8?q=p&p=q#hash", []string{"rewriter1"}, "http://127.0.0.1:1234/tunneled-rewrite/http%3A%2F%2Fexample.com%2Fmedia%2Fpl.m3u8%3Fq%3Dp%26p%3Dq%23hash?rewriter1="},
+		{12345, "http://example.com/media/pl.aaa", []string{"rewriter1", "rewriter2"}, "http://127.0.0.1:12345/tunneled-rewrite/http%3A%2F%2Fexample.com%2Fmedia%2Fpl.aaa?rewriter1=&rewriter2="},
+		{12346, "http://example.com/media/bbb", nil, "http://127.0.0.1:12346/tunneled/http%3A%2F%2Fexample.com%2Fmedia%2Fbbb"},
+	}
+
+	for _, tt := range urlTests {
+		actual := proxifyURL(tt.port, tt.urlString, tt.rewriteParams)
+		if actual != tt.expected {
+			t.Errorf("proxifyURL(%d, %s, %v): expected %s, actual %s", tt.port, tt.urlString, tt.rewriteParams, tt.expected, actual)
+		}
+	}
+}
+
+func TestRewriteM3U8(t *testing.T) {
+	var tests = []struct {
+		url                 string
+		contentType         string
+		inFilename          string
+		expectedFilename    string
+		expectedContentType string
+	}{
+		// Relying on file extension to indicate type
+		{"http://example.com/test.m3u8", "", "testdata/master.m3u8.1", "testdata/master.m3u8.1.target", "application/x-mpegURL"},
+		// No file extension, Content-Type set
+		{"http://example.com/test", "application/x-mpegURL", "testdata/master.m3u8.1", "testdata/master.m3u8.1.target", "application/x-mpegURL"},
+		// No file extension, Content-Type set
+		{"http://example.com/test", "vnd.apple.mpegURL", "testdata/master.m3u8.1", "testdata/master.m3u8.1.target", "application/x-mpegURL"},
+		// No file extension, no Content-Type, so no change
+		{"http://example.com/test", "", "testdata/master.m3u8.1", "testdata/master.m3u8.1", ""},
+		// Media playlist
+		{"http://example.com/test.m3u8", "", "testdata/media.m3u8.1", "testdata/media.m3u8.1.target", "application/x-mpegURL"},
+		// Complex master playlist
+		{"http://example.com/test.m3u8", "", "testdata/master.m3u8.2", "testdata/master.m3u8.2.target", "application/x-mpegURL"},
+		// Complex media playlist
+		{"http://example.com/test.m3u8", "", "testdata/media.m3u8.2", "testdata/media.m3u8.2.target", "application/x-mpegURL"},
+		// Invalid file
+		{"http://example.com/test.m3u8", "application/x-mpegURL", "httpProxy.go", "httpProxy.go", ""},
+	}
+
+	for i, tt := range tests {
+		response := http.Response{
+			Request: new(http.Request),
+			Header:  http.Header{},
+		}
+
+		response.Request.URL, _ = url.Parse(tt.url)
+		if tt.contentType != "" {
+			response.Header.Set("Content-Type", tt.contentType)
+		}
+		inFile, _ := os.Open(tt.inFilename)
+		inFileInfo, _ := inFile.Stat()
+
+		response.Body = inFile
+		response.Header.Set("Content-Length", strconv.FormatInt(inFileInfo.Size(), 10))
+
+		err := rewriteM3U8(12345, &response)
+		if err != nil {
+			t.Errorf("rewriteM3U8 returned error: %s", err)
+		}
+
+		rewrittenBody, _ := ioutil.ReadAll(response.Body)
+		response.Body.Close()
+
+		expectedBody, _ := ioutil.ReadFile(tt.expectedFilename)
+
+		if bytes.Compare(rewrittenBody, expectedBody) != 0 {
+			t.Errorf("rewriteM3U8 body mismatch for test %d", i)
+		}
+
+		if tt.expectedContentType != "" && response.Header.Get("Content-Type") != tt.expectedContentType {
+			t.Errorf("rewriteM3U8 Content-Type mismatch for test %d: %s %s", i, tt.expectedContentType, response.Header.Get("Content-Type"))
+		}
+
+		contentLength, _ := strconv.ParseInt(response.Header.Get("Content-Length"), 10, 64)
+		if contentLength != int64(len(rewrittenBody)) {
+			t.Errorf("rewriteM3U8 Content-Length incorrect for test %d: %d != %d", i, contentLength, len(rewrittenBody))
+		}
+	}
+}

+ 13 - 0
psiphon/testdata/master.m3u8.1

@@ -0,0 +1,13 @@
+#EXTM3U
+#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=192000,RESOLUTION=256x144,CODECS="avc1.66.30, mp4a.40.2"
+http://example.com/i/ptv_1@78015/index_192_av-p.m3u8?sd=10&rebase=on
+#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=192000,RESOLUTION=256x144,CODECS="avc1.66.30, mp4a.40.2"
+http://example.com/i/ptv_1@78015/index_192_av-b.m3u8?sd=10&rebase=on
+#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=400000,RESOLUTION=512x288,CODECS="avc1.66.30, mp4a.40.2"
+http://example.com/i/ptv_1@78015/index_400_av-p.m3u8?sd=10&rebase=on
+#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=400000,RESOLUTION=512x288,CODECS="avc1.66.30, mp4a.40.2"
+http://example.com/i/ptv_1@78015/index_400_av-b.m3u8?sd=10&rebase=on
+#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=800000,RESOLUTION=640x360,CODECS="avc1.66.30, mp4a.40.2"
+http://example.com/i/ptv_1@78015/index_800_av-p.m3u8?sd=10&rebase=on
+#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=800000,RESOLUTION=640x360,CODECS="avc1.66.30, mp4a.40.2"
+http://example.com/i/ptv_1@78015/index_800_av-b.m3u8?sd=10&rebase=on

+ 14 - 0
psiphon/testdata/master.m3u8.1.target

@@ -0,0 +1,14 @@
+#EXTM3U
+#EXT-X-VERSION:3
+#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=192000,CODECS="avc1.66.30, mp4a.40.2",RESOLUTION=256x144
+http://127.0.0.1:12345/tunneled-rewrite/http%3A%2F%2Fexample.com%2Fi%2Fptv_1%4078015%2Findex_192_av-p.m3u8%3Fsd%3D10%26rebase%3Don?m3u8=
+#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=192000,CODECS="avc1.66.30, mp4a.40.2",RESOLUTION=256x144
+http://127.0.0.1:12345/tunneled-rewrite/http%3A%2F%2Fexample.com%2Fi%2Fptv_1%4078015%2Findex_192_av-b.m3u8%3Fsd%3D10%26rebase%3Don?m3u8=
+#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=400000,CODECS="avc1.66.30, mp4a.40.2",RESOLUTION=512x288
+http://127.0.0.1:12345/tunneled-rewrite/http%3A%2F%2Fexample.com%2Fi%2Fptv_1%4078015%2Findex_400_av-p.m3u8%3Fsd%3D10%26rebase%3Don?m3u8=
+#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=400000,CODECS="avc1.66.30, mp4a.40.2",RESOLUTION=512x288
+http://127.0.0.1:12345/tunneled-rewrite/http%3A%2F%2Fexample.com%2Fi%2Fptv_1%4078015%2Findex_400_av-b.m3u8%3Fsd%3D10%26rebase%3Don?m3u8=
+#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=800000,CODECS="avc1.66.30, mp4a.40.2",RESOLUTION=640x360
+http://127.0.0.1:12345/tunneled-rewrite/http%3A%2F%2Fexample.com%2Fi%2Fptv_1%4078015%2Findex_800_av-p.m3u8%3Fsd%3D10%26rebase%3Don?m3u8=
+#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=800000,CODECS="avc1.66.30, mp4a.40.2",RESOLUTION=640x360
+http://127.0.0.1:12345/tunneled-rewrite/http%3A%2F%2Fexample.com%2Fi%2Fptv_1%4078015%2Findex_800_av-b.m3u8%3Fsd%3D10%26rebase%3Don?m3u8=

+ 38 - 0
psiphon/testdata/master.m3u8.2

@@ -0,0 +1,38 @@
+#EXTM3U
+
+#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="bipbop_audio",LANGUAGE="eng",NAME="BipBop Audio 1",AUTOSELECT=YES,DEFAULT=YES
+#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="bipbop_audio",LANGUAGE="eng",NAME="BipBop Audio 2",AUTOSELECT=NO,DEFAULT=NO,URI="alternate_audio_aac_sinewave/prog_index.m3u8"
+
+
+#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="English",DEFAULT=YES,AUTOSELECT=YES,FORCED=NO,LANGUAGE="en",CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog, public.accessibility.describes-music-and-sound",URI="subtitles/eng/prog_index.m3u8"
+#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="English (Forced)",DEFAULT=NO,AUTOSELECT=NO,FORCED=YES,LANGUAGE="en",URI="subtitles/eng_forced/prog_index.m3u8"
+#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="Français",DEFAULT=NO,AUTOSELECT=YES,FORCED=NO,LANGUAGE="fr",CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog, public.accessibility.describes-music-and-sound",URI="subtitles/fra/prog_index.m3u8"
+#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="Français (Forced)",DEFAULT=NO,AUTOSELECT=NO,FORCED=YES,LANGUAGE="fr",URI="subtitles/fra_forced/prog_index.m3u8"
+#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="Español",DEFAULT=NO,AUTOSELECT=YES,FORCED=NO,LANGUAGE="es",CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog, public.accessibility.describes-music-and-sound",URI="subtitles/spa/prog_index.m3u8"
+#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="Español (Forced)",DEFAULT=NO,AUTOSELECT=NO,FORCED=YES,LANGUAGE="es",URI="subtitles/spa_forced/prog_index.m3u8"
+#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="日本語",DEFAULT=NO,AUTOSELECT=YES,FORCED=NO,LANGUAGE="ja",CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog, public.accessibility.describes-music-and-sound",URI="subtitles/jpn/prog_index.m3u8"
+#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="日本語 (Forced)",DEFAULT=NO,AUTOSELECT=NO,FORCED=YES,LANGUAGE="ja",URI="subtitles/jpn_forced/prog_index.m3u8"
+
+
+#EXT-X-STREAM-INF:BANDWIDTH=263851,CODECS="mp4a.40.2, avc1.4d400d",RESOLUTION=416x234,AUDIO="bipbop_audio",SUBTITLES="subs"
+gear1/prog_index.m3u8
+#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=28451,CODECS="avc1.4d400d",URI="gear1/iframe_index.m3u8"
+
+#EXT-X-STREAM-INF:BANDWIDTH=577610,CODECS="mp4a.40.2, avc1.4d401e",RESOLUTION=640x360,AUDIO="bipbop_audio",SUBTITLES="subs"
+gear2/prog_index.m3u8
+#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=181534,CODECS="avc1.4d401e",URI="gear2/iframe_index.m3u8"
+
+#EXT-X-STREAM-INF:BANDWIDTH=915905,CODECS="mp4a.40.2, avc1.4d401f",RESOLUTION=960x540,AUDIO="bipbop_audio",SUBTITLES="subs"
+gear3/prog_index.m3u8
+#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=297056,CODECS="avc1.4d401f",URI="gear3/iframe_index.m3u8"
+
+#EXT-X-STREAM-INF:BANDWIDTH=1030138,CODECS="mp4a.40.2, avc1.4d401f",RESOLUTION=1280x720,AUDIO="bipbop_audio",SUBTITLES="subs"
+gear4/prog_index.m3u8
+#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=339492,CODECS="avc1.4d401f",URI="gear4/iframe_index.m3u8"
+
+#EXT-X-STREAM-INF:BANDWIDTH=1924009,CODECS="mp4a.40.2, avc1.4d401f",RESOLUTION=1920x1080,AUDIO="bipbop_audio",SUBTITLES="subs"
+gear5/prog_index.m3u8
+#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=669554,CODECS="avc1.4d401f",URI="gear5/iframe_index.m3u8"
+
+#EXT-X-STREAM-INF:BANDWIDTH=41457,CODECS="mp4a.40.2",AUDIO="bipbop_audio",SUBTITLES="subs"
+gear0/prog_index.m3u8

+ 29 - 0
psiphon/testdata/master.m3u8.2.target

@@ -0,0 +1,29 @@
+#EXTM3U
+#EXT-X-VERSION:3
+#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="bipbop_audio",NAME="BipBop Audio 1",DEFAULT=YES,AUTOSELECT=YES,LANGUAGE="eng"
+#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="bipbop_audio",NAME="BipBop Audio 2",DEFAULT=NO,AUTOSELECT=NO,LANGUAGE="eng",URI="http://127.0.0.1:12345/tunneled-rewrite/http%3A%2F%2Fexample.com%2Falternate_audio_aac_sinewave%2Fprog_index.m3u8?m3u8="
+#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="English",DEFAULT=YES,AUTOSELECT=YES,LANGUAGE="en",FORCED="NO",CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog, public.accessibility.describes-music-and-sound",URI="http://127.0.0.1:12345/tunneled-rewrite/http%3A%2F%2Fexample.com%2Fsubtitles%2Feng%2Fprog_index.m3u8?m3u8="
+#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="English (Forced)",DEFAULT=NO,AUTOSELECT=NO,LANGUAGE="en",FORCED="YES",URI="http://127.0.0.1:12345/tunneled-rewrite/http%3A%2F%2Fexample.com%2Fsubtitles%2Feng_forced%2Fprog_index.m3u8?m3u8="
+#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="Français",DEFAULT=NO,AUTOSELECT=YES,LANGUAGE="fr",FORCED="NO",CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog, public.accessibility.describes-music-and-sound",URI="http://127.0.0.1:12345/tunneled-rewrite/http%3A%2F%2Fexample.com%2Fsubtitles%2Ffra%2Fprog_index.m3u8?m3u8="
+#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="Français (Forced)",DEFAULT=NO,AUTOSELECT=NO,LANGUAGE="fr",FORCED="YES",URI="http://127.0.0.1:12345/tunneled-rewrite/http%3A%2F%2Fexample.com%2Fsubtitles%2Ffra_forced%2Fprog_index.m3u8?m3u8="
+#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="Español",DEFAULT=NO,AUTOSELECT=YES,LANGUAGE="es",FORCED="NO",CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog, public.accessibility.describes-music-and-sound",URI="http://127.0.0.1:12345/tunneled-rewrite/http%3A%2F%2Fexample.com%2Fsubtitles%2Fspa%2Fprog_index.m3u8?m3u8="
+#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="Español (Forced)",DEFAULT=NO,AUTOSELECT=NO,LANGUAGE="es",FORCED="YES",URI="http://127.0.0.1:12345/tunneled-rewrite/http%3A%2F%2Fexample.com%2Fsubtitles%2Fspa_forced%2Fprog_index.m3u8?m3u8="
+#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="日本語",DEFAULT=NO,AUTOSELECT=YES,LANGUAGE="ja",FORCED="NO",CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog, public.accessibility.describes-music-and-sound",URI="http://127.0.0.1:12345/tunneled-rewrite/http%3A%2F%2Fexample.com%2Fsubtitles%2Fjpn%2Fprog_index.m3u8?m3u8="
+#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="日本語 (Forced)",DEFAULT=NO,AUTOSELECT=NO,LANGUAGE="ja",FORCED="YES",URI="http://127.0.0.1:12345/tunneled-rewrite/http%3A%2F%2Fexample.com%2Fsubtitles%2Fjpn_forced%2Fprog_index.m3u8?m3u8="
+#EXT-X-STREAM-INF:PROGRAM-ID=0,BANDWIDTH=263851,CODECS="mp4a.40.2, avc1.4d400d",RESOLUTION=416x234,AUDIO="bipbop_audio",SUBTITLES="subs"
+http://127.0.0.1:12345/tunneled-rewrite/http%3A%2F%2Fexample.com%2Fgear1%2Fprog_index.m3u8?m3u8=
+#EXT-X-I-FRAME-STREAM-INF:PROGRAM-ID=0,BANDWIDTH=28451,CODECS="avc1.4d400d",URI="http://127.0.0.1:12345/tunneled-rewrite/http%3A%2F%2Fexample.com%2Fgear1%2Fiframe_index.m3u8?m3u8="
+#EXT-X-STREAM-INF:PROGRAM-ID=0,BANDWIDTH=577610,CODECS="mp4a.40.2, avc1.4d401e",RESOLUTION=640x360,AUDIO="bipbop_audio",SUBTITLES="subs"
+http://127.0.0.1:12345/tunneled-rewrite/http%3A%2F%2Fexample.com%2Fgear2%2Fprog_index.m3u8?m3u8=
+#EXT-X-I-FRAME-STREAM-INF:PROGRAM-ID=0,BANDWIDTH=181534,CODECS="avc1.4d401e",URI="http://127.0.0.1:12345/tunneled-rewrite/http%3A%2F%2Fexample.com%2Fgear2%2Fiframe_index.m3u8?m3u8="
+#EXT-X-STREAM-INF:PROGRAM-ID=0,BANDWIDTH=915905,CODECS="mp4a.40.2, avc1.4d401f",RESOLUTION=960x540,AUDIO="bipbop_audio",SUBTITLES="subs"
+http://127.0.0.1:12345/tunneled-rewrite/http%3A%2F%2Fexample.com%2Fgear3%2Fprog_index.m3u8?m3u8=
+#EXT-X-I-FRAME-STREAM-INF:PROGRAM-ID=0,BANDWIDTH=297056,CODECS="avc1.4d401f",URI="http://127.0.0.1:12345/tunneled-rewrite/http%3A%2F%2Fexample.com%2Fgear3%2Fiframe_index.m3u8?m3u8="
+#EXT-X-STREAM-INF:PROGRAM-ID=0,BANDWIDTH=1030138,CODECS="mp4a.40.2, avc1.4d401f",RESOLUTION=1280x720,AUDIO="bipbop_audio",SUBTITLES="subs"
+http://127.0.0.1:12345/tunneled-rewrite/http%3A%2F%2Fexample.com%2Fgear4%2Fprog_index.m3u8?m3u8=
+#EXT-X-I-FRAME-STREAM-INF:PROGRAM-ID=0,BANDWIDTH=339492,CODECS="avc1.4d401f",URI="http://127.0.0.1:12345/tunneled-rewrite/http%3A%2F%2Fexample.com%2Fgear4%2Fiframe_index.m3u8?m3u8="
+#EXT-X-STREAM-INF:PROGRAM-ID=0,BANDWIDTH=1924009,CODECS="mp4a.40.2, avc1.4d401f",RESOLUTION=1920x1080,AUDIO="bipbop_audio",SUBTITLES="subs"
+http://127.0.0.1:12345/tunneled-rewrite/http%3A%2F%2Fexample.com%2Fgear5%2Fprog_index.m3u8?m3u8=
+#EXT-X-I-FRAME-STREAM-INF:PROGRAM-ID=0,BANDWIDTH=669554,CODECS="avc1.4d401f",URI="http://127.0.0.1:12345/tunneled-rewrite/http%3A%2F%2Fexample.com%2Fgear5%2Fiframe_index.m3u8?m3u8="
+#EXT-X-STREAM-INF:PROGRAM-ID=0,BANDWIDTH=41457,CODECS="mp4a.40.2",AUDIO="bipbop_audio",SUBTITLES="subs"
+http://127.0.0.1:12345/tunneled-rewrite/http%3A%2F%2Fexample.com%2Fgear0%2Fprog_index.m3u8?m3u8=

+ 11 - 0
psiphon/testdata/media.m3u8.1

@@ -0,0 +1,11 @@
+#EXTM3U
+#EXT-X-TARGETDURATION:10
+#EXT-X-ALLOW-CACHE:YES
+#EXT-X-VERSION:3
+#EXT-X-MEDIA-SEQUENCE:150237918
+#EXTINF:10.000,
+http://example.com/i/ptv_1@78015/segment150237918_192_av-p.ts?sd=10&rebase=on
+#EXTINF:10.000,
+http://example.com/i/ptv_1@78015/segment150237919_192_av-p.ts?sd=10&rebase=on
+#EXTINF:10.000,
+http://example.com/i/ptv_1@78015/segment150237920_192_av-p.ts?sd=10&rebase=on

+ 10 - 0
psiphon/testdata/media.m3u8.1.target

@@ -0,0 +1,10 @@
+#EXTM3U
+#EXT-X-VERSION:3
+#EXT-X-MEDIA-SEQUENCE:150237918
+#EXT-X-TARGETDURATION:10
+#EXTINF:10.000,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fi%2Fptv_1%4078015%2Fsegment150237918_192_av-p.ts%3Fsd%3D10%26rebase%3Don
+#EXTINF:10.000,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fi%2Fptv_1%4078015%2Fsegment150237919_192_av-p.ts%3Fsd%3D10%26rebase%3Don
+#EXTINF:10.000,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fi%2Fptv_1%4078015%2Fsegment150237920_192_av-p.ts%3Fsd%3D10%26rebase%3Don

+ 549 - 0
psiphon/testdata/media.m3u8.2

@@ -0,0 +1,549 @@
+#EXTM3U
+#EXT-X-TARGETDURATION:11
+#EXT-X-VERSION:4
+#EXT-X-MEDIA-SEQUENCE:0
+#EXT-X-PLAYLIST-TYPE:VOD
+#EXTINF:9.9766,
+#EXT-X-BYTERANGE:326744@0
+main.ts
+#EXTINF:9.9766,
+#EXT-X-BYTERANGE:326368@326744
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:327120@653112
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:326556@980232
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:326368@1306788
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:327684@1633156
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:327496@1960840
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:327120@2288336
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:327120@2615456
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:327496@2942576
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:326932@3270072
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:329000@3597004
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:330128@3926004
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:330128@4256132
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:328624@4586260
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:328248@4914884
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:327684@5243132
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:328248@5570816
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:328436@5899064
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:328248@6227500
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:328248@6555748
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:329376@6883996
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:329376@7213372
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:329564@7542748
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:329188@7872312
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:328812@8201500
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:328248@8530312
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:329000@8858560
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:328436@9187560
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:329188@9515996
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:325804@9845184
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:329564@10170988
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:329564@10500552
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:330128@10830116
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:329564@11160244
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:329564@11489808
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:329376@11819372
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:329000@12148748
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:329188@12477748
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:329752@12806936
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:329376@13136688
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:330128@13466064
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:330128@13796192
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:329564@14126320
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:329564@14455884
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:330316@14785448
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:329752@15115764
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:330316@15445516
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:330128@15775832
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:330128@16105960
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:329940@16436088
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:329940@16766028
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:330128@17095968
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:331068@17426096
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:331068@17757164
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:330692@18088232
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:330128@18418924
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:330316@18749052
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:330128@19079368
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:330692@19409496
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:323736@19740188
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:329564@20063924
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:330316@20393488
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:330880@20723804
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:330880@21054684
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:330880@21385564
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:330880@21716444
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:329564@22047324
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:329940@22376888
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:330316@22706828
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:329940@23037144
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:330316@23367084
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:330504@23697400
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:331068@24027904
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:331444@24358972
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:331444@24690416
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:330880@25021860
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:331068@25352740
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:331068@25683808
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:330880@26014876
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:330880@26345756
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:331632@26676636
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:331444@27008268
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:331820@27339712
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:330880@27671532
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:331256@28002412
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:331444@28333668
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:332948@28665112
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:331820@28998060
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:332572@29329880
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:326932@29662452
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:332384@29989384
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:332196@30321768
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:332008@30653964
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:331820@30985972
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:332196@31317792
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:331632@31649988
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:331632@31981620
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:326368@32313252
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:327496@32639620
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:325992@32967116
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:326368@33293108
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:325992@33619476
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:326180@33945468
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:326744@34271648
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:326556@34598392
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:326744@34924948
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:326932@35251692
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:327120@35578624
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:326932@35905744
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:326744@36232676
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:327308@36559420
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:326932@36886728
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:328248@37213660
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:327872@37541908
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:327496@37869780
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:327308@38197276
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:327496@38524584
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:327684@38852080
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:328248@39179764
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:322796@39508012
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:329000@39830808
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:329188@40159808
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:329752@40488996
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:327496@40818748
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:327872@41146244
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:327684@41474116
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:328624@41801800
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:326932@42130424
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:327496@42457356
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:327872@42784852
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:328248@43112724
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:328436@43440972
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:329376@43769408
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:328248@44098784
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:328812@44427032
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:328060@44755844
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:327872@45083904
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:328060@45411776
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:328060@45739836
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:327684@46067896
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:328624@46395580
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:328812@46724204
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:328812@47053016
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:328624@47381828
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:328624@47710452
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:328624@48039076
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:329000@48367700
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:328436@48696700
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:318284@49025136
+main.ts
+#EXTINF:9.9766,
+#EXT-X-BYTERANGE:329564@49343420
+main.ts
+#EXTINF:9.9766,
+#EXT-X-BYTERANGE:328624@49672984
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:328436@50001608
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:329564@50330044
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:329752@50659608
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:329752@50989360
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:329752@51319112
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:329376@51648864
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:329188@51978240
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:329752@52307428
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:329000@52637180
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:329188@52966180
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:329000@53295368
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:329000@53624368
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:329188@53953368
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:329940@54282556
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:329188@54612496
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:330128@54941684
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:329940@55271812
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:330128@55601752
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:329000@55931880
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:329376@56260880
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:328624@56590256
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:329940@56918880
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:330504@57248820
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:329940@57579324
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:329188@57909264
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:329188@58238452
+main.ts
+#EXTINF:10.01,
+#EXT-X-BYTERANGE:329376@58567640
+main.ts
+#EXTINF:9.9433,
+#EXT-X-BYTERANGE:329752@58897016
+main.ts
+#EXTINF:4.2485,
+#EXT-X-BYTERANGE:139872@59226768
+main.ts
+#EXT-X-ENDLIST

+ 549 - 0
psiphon/testdata/media.m3u8.2.target

@@ -0,0 +1,549 @@
+#EXTM3U
+#EXT-X-VERSION:4
+#EXT-X-PLAYLIST-TYPE:VOD
+#EXT-X-MEDIA-SEQUENCE:0
+#EXT-X-TARGETDURATION:11
+#EXT-X-BYTERANGE:326744@0
+#EXTINF:9.977,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:326368@326744
+#EXTINF:9.977,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:327120@653112
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:326556@980232
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:326368@1306788
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:327684@1633156
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:327496@1960840
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:327120@2288336
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:327120@2615456
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:327496@2942576
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:326932@3270072
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329000@3597004
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:330128@3926004
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:330128@4256132
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:328624@4586260
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:328248@4914884
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:327684@5243132
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:328248@5570816
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:328436@5899064
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:328248@6227500
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:328248@6555748
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329376@6883996
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329376@7213372
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329564@7542748
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329188@7872312
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:328812@8201500
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:328248@8530312
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329000@8858560
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:328436@9187560
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329188@9515996
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:325804@9845184
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329564@10170988
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329564@10500552
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:330128@10830116
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329564@11160244
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329564@11489808
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329376@11819372
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329000@12148748
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329188@12477748
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329752@12806936
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329376@13136688
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:330128@13466064
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:330128@13796192
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329564@14126320
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329564@14455884
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:330316@14785448
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329752@15115764
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:330316@15445516
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:330128@15775832
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:330128@16105960
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329940@16436088
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329940@16766028
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:330128@17095968
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:331068@17426096
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:331068@17757164
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:330692@18088232
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:330128@18418924
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:330316@18749052
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:330128@19079368
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:330692@19409496
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:323736@19740188
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329564@20063924
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:330316@20393488
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:330880@20723804
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:330880@21054684
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:330880@21385564
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:330880@21716444
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329564@22047324
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329940@22376888
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:330316@22706828
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329940@23037144
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:330316@23367084
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:330504@23697400
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:331068@24027904
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:331444@24358972
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:331444@24690416
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:330880@25021860
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:331068@25352740
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:331068@25683808
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:330880@26014876
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:330880@26345756
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:331632@26676636
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:331444@27008268
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:331820@27339712
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:330880@27671532
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:331256@28002412
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:331444@28333668
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:332948@28665112
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:331820@28998060
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:332572@29329880
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:326932@29662452
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:332384@29989384
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:332196@30321768
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:332008@30653964
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:331820@30985972
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:332196@31317792
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:331632@31649988
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:331632@31981620
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:326368@32313252
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:327496@32639620
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:325992@32967116
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:326368@33293108
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:325992@33619476
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:326180@33945468
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:326744@34271648
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:326556@34598392
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:326744@34924948
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:326932@35251692
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:327120@35578624
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:326932@35905744
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:326744@36232676
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:327308@36559420
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:326932@36886728
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:328248@37213660
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:327872@37541908
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:327496@37869780
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:327308@38197276
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:327496@38524584
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:327684@38852080
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:328248@39179764
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:322796@39508012
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329000@39830808
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329188@40159808
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329752@40488996
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:327496@40818748
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:327872@41146244
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:327684@41474116
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:328624@41801800
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:326932@42130424
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:327496@42457356
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:327872@42784852
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:328248@43112724
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:328436@43440972
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329376@43769408
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:328248@44098784
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:328812@44427032
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:328060@44755844
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:327872@45083904
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:328060@45411776
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:328060@45739836
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:327684@46067896
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:328624@46395580
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:328812@46724204
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:328812@47053016
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:328624@47381828
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:328624@47710452
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:328624@48039076
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329000@48367700
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:328436@48696700
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:318284@49025136
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329564@49343420
+#EXTINF:9.977,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:328624@49672984
+#EXTINF:9.977,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:328436@50001608
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329564@50330044
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329752@50659608
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329752@50989360
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329752@51319112
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329376@51648864
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329188@51978240
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329752@52307428
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329000@52637180
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329188@52966180
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329000@53295368
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329000@53624368
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329188@53953368
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329940@54282556
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329188@54612496
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:330128@54941684
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329940@55271812
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:330128@55601752
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329000@55931880
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329376@56260880
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:328624@56590256
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329940@56918880
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:330504@57248820
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329940@57579324
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329188@57909264
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329188@58238452
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329376@58567640
+#EXTINF:10.010,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:329752@58897016
+#EXTINF:9.943,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-BYTERANGE:139872@59226768
+#EXTINF:4.248,
+http://127.0.0.1:12345/tunneled/http%3A%2F%2Fexample.com%2Fmain.ts
+#EXT-X-ENDLIST