From 30a665f87c1858e81eb7f425f532e09dcab78d8c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 4 Nov 2025 18:03:03 +0000 Subject: [PATCH 1/9] Initial plan From 00da4c68ca6e4ef0f1bf46a1add70fa38f9b9d2b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 4 Nov 2025 18:11:18 +0000 Subject: [PATCH 2/9] Update Go version from 1.17 to 1.24 to address CVEs Co-authored-by: kadrim <8304331+kadrim@users.noreply.github.com> --- .gitignore | 1 + Dockerfile | 2 +- README.md | 2 +- go.mod | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index e949455..72927ab 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ *.so *.dylib *.zip +proxy4plex # Test binary, built with `go test -c` *.test diff --git a/Dockerfile b/Dockerfile index 41b6a13..fe9c4ee 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.17-alpine +FROM golang:1.24-alpine LABEL maintainer="Kadrim " diff --git a/README.md b/README.md index d83271d..b6c89bc 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ Beware: Sideloading (i.e. installing the app on the TV) does not work this way, ## Compiling -At the time of writing, this package needs at least [golang](https://golang.org/) v1.17 +At the time of writing, this package needs at least [golang](https://golang.org/) v1.24 To compile a binary for your currently running system, simply run this command: diff --git a/go.mod b/go.mod index e6c8c2d..0965f83 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module github.com/kadrim/proxy4plex -go 1.17 +go 1.24 From cc891c3058879bef7e2180ca081a22d653dd4252 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 4 Nov 2025 18:27:07 +0000 Subject: [PATCH 3/9] Add GitHub Actions CI/CD workflows for automated builds Co-authored-by: kadrim <8304331+kadrim@users.noreply.github.com> --- .github/workflows/build.yml | 51 +++++++++++++++++++++++++++++++++++ .github/workflows/release.yml | 37 +++++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..9cdbd89 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,51 @@ +name: Build and Test + +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] + +jobs: + test: + name: Test + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.24' + + - name: Run tests + run: go test -v ./... + + - name: Build + run: go build -v ./... + + build-all: + name: Build All Platforms + runs-on: ubuntu-latest + needs: test + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Needed for git describe + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.24' + + - name: Build all platforms + run: bash go-build-all.sh + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: binaries + path: build/ + retention-days: 7 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..ea5cfd9 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,37 @@ +name: Release + +on: + push: + tags: + - 'v*' + +permissions: + contents: write + +jobs: + release: + name: Create Release + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.24' + + - name: Build all platforms + run: bash go-build-all.sh + + - name: Create Release + uses: softprops/action-gh-release@v1 + with: + files: build/* + draft: false + prerelease: false + generate_release_notes: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 0b3415a1e08d82688c43b31ce42c81ba6140e49c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 4 Nov 2025 18:31:42 +0000 Subject: [PATCH 4/9] Set releases to draft mode for manual approval Co-authored-by: kadrim <8304331+kadrim@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ea5cfd9..a2330ac 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -30,7 +30,7 @@ jobs: uses: softprops/action-gh-release@v1 with: files: build/* - draft: false + draft: true prerelease: false generate_release_notes: true env: From a89fccac48718afa43ae14194d30551fe21b5674 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 4 Nov 2025 18:46:54 +0000 Subject: [PATCH 5/9] Fix security vulnerabilities identified by gosec - Add HTTP server timeouts to prevent DoS attacks - Fix file permissions from 0664 to 0600 - Add URL validation to prevent SSRF attacks - Add Content-Type headers to all responses - Improve error handling throughout - Add private IP blocking for proxy targets - Fix all unhandled errors in HTTP writes Co-authored-by: kadrim <8304331+kadrim@users.noreply.github.com> --- proxy.go | 112 ++++++++++++++++++++++++++++++++++++++++++++++++------- zip.go | 2 +- 2 files changed, 100 insertions(+), 14 deletions(-) diff --git a/proxy.go b/proxy.go index 5eff9bf..ec6f0a2 100644 --- a/proxy.go +++ b/proxy.go @@ -2,17 +2,49 @@ package main import ( "log" + "net" "net/http" "net/http/httputil" "net/url" "strconv" "strings" + "time" ) type transport struct { http.RoundTripper } +// isLocalOrPrivate checks if a hostname resolves to localhost or private IP ranges +func isLocalOrPrivate(hostname string) bool { + // Remove port if present + host := hostname + if colonIndex := strings.LastIndex(hostname, ":"); colonIndex != -1 { + host = hostname[:colonIndex] + } + + // Check for localhost + if host == "localhost" || host == "127.0.0.1" || host == "::1" { + return true + } + + // Try to resolve the hostname + ips, err := net.LookupIP(host) + if err != nil { + // If we can't resolve, block it to be safe + return true + } + + // Check if any resolved IP is private + for _, ip := range ips { + if ip.IsLoopback() || ip.IsPrivate() { + return true + } + } + + return false +} + func (t *transport) RoundTrip(req *http.Request) (resp *http.Response, err error) { resp, err = t.RoundTripper.RoundTrip(req) if err != nil { @@ -24,8 +56,8 @@ func (t *transport) RoundTrip(req *http.Request) (resp *http.Response, err error case 301: fallthrough case 302: - redirectURL, _ := url.Parse(resp.Header.Get("Location")) - if redirectURL.Scheme == "https" { + redirectURL, err := url.Parse(resp.Header.Get("Location")) + if err == nil && redirectURL.Scheme == "https" { resp.Header.Set("Location", "http://"+req.Header.Get("X-Forwarded-Host")+"?url="+resp.Header.Get("Location")) } } @@ -35,18 +67,49 @@ func (t *transport) RoundTrip(req *http.Request) (resp *http.Response, err error // handle a request send it to the server func handleRequest(res http.ResponseWriter, req *http.Request) { // get the request URI - server, _ := url.Parse("https://" + host) - reqURL, _ := url.Parse(req.RequestURI) + server, err := url.Parse("https://" + host) + if err != nil { + http.Error(res, "Invalid server URL", http.StatusInternalServerError) + return + } + + reqURL, err := url.Parse(req.RequestURI) + if err != nil { + http.Error(res, "Invalid request URI", http.StatusBadRequest) + return + } + if reqURL.Query().Get("url") != "" { // special proxy handling // extract the GET-Param url - server, _ = url.Parse(reqURL.Query().Get("url")) + targetURL := reqURL.Query().Get("url") + server, err = url.Parse(targetURL) + if err != nil { + http.Error(res, "Invalid target URL", http.StatusBadRequest) + return + } + + // Validate URL scheme to prevent SSRF attacks + if server.Scheme != "https" && server.Scheme != "http" { + http.Error(res, "Invalid URL scheme", http.StatusBadRequest) + return + } + + // Prevent access to localhost and private IP ranges + if isLocalOrPrivate(server.Host) { + http.Error(res, "Access to private addresses is not allowed", http.StatusForbidden) + return + } // replace request req.URL = server req.RequestURI = "" // mux host - server, _ = url.Parse(server.Scheme + "://" + server.Host) + server, err = url.Parse(server.Scheme + "://" + server.Host) + if err != nil { + http.Error(res, "Invalid server URL", http.StatusInternalServerError) + return + } } // prepare reverse proxy @@ -72,15 +135,17 @@ func handleRequest(res http.ResponseWriter, req *http.Request) { func runProxy(disableSideloading bool) { // handle simple information path http.HandleFunc("/info", func(res http.ResponseWriter, req *http.Request) { - res.Write([]byte("The Plex proxy service is running on " + req.Host)) + res.Header().Set("Content-Type", "text/plain; charset=utf-8") + _, _ = res.Write([]byte("The Plex proxy service is running on " + req.Host)) }) //handle widgetlist for sideloading http.HandleFunc("/widgetlist.xml", func(res http.ResponseWriter, req *http.Request) { buf, err := retreiveZipFile() if err != nil { + res.Header().Set("Content-Type", "text/plain; charset=utf-8") res.WriteHeader(http.StatusInternalServerError) - res.Write([]byte(err.Error())) + _, _ = res.Write([]byte(err.Error())) log.Println(err) return } @@ -96,34 +161,55 @@ func runProxy(disableSideloading bool) { ` - res.Write([]byte(xml)) + res.Header().Set("Content-Type", "application/xml; charset=utf-8") + _, _ = res.Write([]byte(xml)) }) // handle app-deployment http.HandleFunc("/"+modifiedAppFile, func(res http.ResponseWriter, req *http.Request) { buf, err := retreiveZipFile() if err != nil { + res.Header().Set("Content-Type", "text/plain; charset=utf-8") res.WriteHeader(http.StatusInternalServerError) - res.Write([]byte(err.Error())) + _, _ = res.Write([]byte(err.Error())) log.Println(err) return } // write the http-response - res.Write(buf) + res.Header().Set("Content-Type", "application/zip") + _, _ = res.Write(buf) }) // start real proxy http.HandleFunc("/", handleRequest) + // Configure HTTP server with timeouts to prevent DoS attacks + serverMain := &http.Server{ + Addr: ":" + port, + ReadTimeout: 15 * time.Second, + WriteTimeout: 15 * time.Second, + IdleTimeout: 60 * time.Second, + ReadHeaderTimeout: 5 * time.Second, + } + // try to handle everything on port 80 aswell for serving the app // Note: this will not work on non-rooted android because only high-ports can be used go func() { if !disableSideloading { log.Println("Trying to start app-deployer on port 80 ...") - http.ListenAndServe(":80", nil) + server80 := &http.Server{ + Addr: ":80", + ReadTimeout: 15 * time.Second, + WriteTimeout: 15 * time.Second, + IdleTimeout: 60 * time.Second, + ReadHeaderTimeout: 5 * time.Second, + } + if err := server80.ListenAndServe(); err != nil { + log.Printf("Port 80 server error: %v\n", err) + } } }() log.Println("Server starting on Port " + port + " ...") - log.Fatal(http.ListenAndServe(":"+port, nil)) + log.Fatal(serverMain.ListenAndServe()) } diff --git a/zip.go b/zip.go index 5dc9f9c..0915e28 100644 --- a/zip.go +++ b/zip.go @@ -79,7 +79,7 @@ func retreiveZipFile() ([]byte, error) { if download { // write zipData to local file for caching - err := os.WriteFile(officialAppFile, zipData, 0664) + err := os.WriteFile(officialAppFile, zipData, 0600) if err != nil { log.Println("could not save downloaded file, going on anyway") } From 09de4818e5f7118c5172e5e903bcaee6a5ed6fce Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 4 Nov 2025 18:49:54 +0000 Subject: [PATCH 6/9] Address code review feedback on security improvements - Use net.SplitHostPort for proper IPv6 address handling - Add comments about DNS rebinding attack mitigation - Improve error message for port 80 binding failures - Document expected behavior for non-privileged port binding Co-authored-by: kadrim <8304331+kadrim@users.noreply.github.com> --- proxy.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/proxy.go b/proxy.go index ec6f0a2..0caf806 100644 --- a/proxy.go +++ b/proxy.go @@ -17,10 +17,11 @@ type transport struct { // isLocalOrPrivate checks if a hostname resolves to localhost or private IP ranges func isLocalOrPrivate(hostname string) bool { - // Remove port if present - host := hostname - if colonIndex := strings.LastIndex(hostname, ":"); colonIndex != -1 { - host = hostname[:colonIndex] + // Use net.SplitHostPort to properly handle IPv6 addresses + host, _, err := net.SplitHostPort(hostname) + if err != nil { + // If there's no port, use the hostname as-is + host = hostname } // Check for localhost @@ -28,14 +29,15 @@ func isLocalOrPrivate(hostname string) bool { return true } - // Try to resolve the hostname + // Try to resolve the hostname to IP addresses ips, err := net.LookupIP(host) if err != nil { // If we can't resolve, block it to be safe return true } - // Check if any resolved IP is private + // Check if any resolved IP is private or loopback + // This helps prevent DNS rebinding attacks by checking the actual resolved IPs for _, ip := range ips { if ip.IsLoopback() || ip.IsPrivate() { return true @@ -194,6 +196,8 @@ func runProxy(disableSideloading bool) { // try to handle everything on port 80 aswell for serving the app // Note: this will not work on non-rooted android because only high-ports can be used + // The goroutine will exit after logging the error, which is expected behavior + // for cases where port 80 requires elevated privileges go func() { if !disableSideloading { log.Println("Trying to start app-deployer on port 80 ...") @@ -205,7 +209,7 @@ func runProxy(disableSideloading bool) { ReadHeaderTimeout: 5 * time.Second, } if err := server80.ListenAndServe(); err != nil { - log.Printf("Port 80 server error: %v\n", err) + log.Printf("Port 80 server error (this is expected on non-rooted systems): %v\n", err) } } }() From f328f26eb1fea8a9e57a3cfc55b45bc2466b4606 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 4 Nov 2025 18:59:49 +0000 Subject: [PATCH 7/9] Add automated security scanning to CI/CD pipeline - Add dedicated security.yml workflow with gosec and CodeQL - Security workflow runs on push, PR, and daily schedule - Add gosec quick check to build workflow for fast feedback - Upload results to GitHub Security tab (SARIF format) - Enable continuous security monitoring Co-authored-by: kadrim <8304331+kadrim@users.noreply.github.com> --- .github/workflows/build.yml | 5 +++ .github/workflows/security.yml | 59 ++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 .github/workflows/security.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9cdbd89..458a84e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,6 +25,11 @@ jobs: - name: Build run: go build -v ./... + - name: Run security scan (gosec) + run: | + go install github.com/securego/gosec/v2/cmd/gosec@latest + gosec ./... + build-all: name: Build All Platforms runs-on: ubuntu-latest diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml new file mode 100644 index 0000000..56069ba --- /dev/null +++ b/.github/workflows/security.yml @@ -0,0 +1,59 @@ +name: Security Scan + +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] + schedule: + # Run security scan daily at 2 AM UTC + - cron: '0 2 * * *' + +permissions: + contents: read + security-events: write + +jobs: + gosec: + name: Gosec Security Scanner + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.24' + + - name: Run Gosec Security Scanner + uses: securego/gosec@master + with: + args: '-fmt sarif -out gosec-results.sarif ./...' + + - name: Upload Gosec results to GitHub Security tab + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: gosec-results.sarif + + codeql: + name: CodeQL Analysis + runs-on: ubuntu-latest + permissions: + security-events: write + actions: read + contents: read + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: go + + - name: Autobuild + uses: github/codeql-action/autobuild@v3 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 From 98a818ef70724e4c7c677e2aca875eef12fcab1a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 4 Nov 2025 19:08:06 +0000 Subject: [PATCH 8/9] Remove comments to improve code self-documentation Per code review feedback, removed inline comments to make code more readable and self-documenting. The code structure and naming now clearly communicate intent without explanatory comments. Co-authored-by: kadrim <8304331+kadrim@users.noreply.github.com> --- proxy.go | 31 +------------------------------ 1 file changed, 1 insertion(+), 30 deletions(-) diff --git a/proxy.go b/proxy.go index 0caf806..8db718f 100644 --- a/proxy.go +++ b/proxy.go @@ -15,29 +15,21 @@ type transport struct { http.RoundTripper } -// isLocalOrPrivate checks if a hostname resolves to localhost or private IP ranges func isLocalOrPrivate(hostname string) bool { - // Use net.SplitHostPort to properly handle IPv6 addresses host, _, err := net.SplitHostPort(hostname) if err != nil { - // If there's no port, use the hostname as-is host = hostname } - // Check for localhost if host == "localhost" || host == "127.0.0.1" || host == "::1" { return true } - // Try to resolve the hostname to IP addresses ips, err := net.LookupIP(host) if err != nil { - // If we can't resolve, block it to be safe return true } - // Check if any resolved IP is private or loopback - // This helps prevent DNS rebinding attacks by checking the actual resolved IPs for _, ip := range ips { if ip.IsLoopback() || ip.IsPrivate() { return true @@ -53,7 +45,6 @@ func (t *transport) RoundTrip(req *http.Request) (resp *http.Response, err error return nil, err } - // check if result is a redirect and handle that accordingly switch resp.StatusCode { case 301: fallthrough @@ -66,9 +57,7 @@ func (t *transport) RoundTrip(req *http.Request) (resp *http.Response, err error return resp, nil } -// handle a request send it to the server func handleRequest(res http.ResponseWriter, req *http.Request) { - // get the request URI server, err := url.Parse("https://" + host) if err != nil { http.Error(res, "Invalid server URL", http.StatusInternalServerError) @@ -81,8 +70,7 @@ func handleRequest(res http.ResponseWriter, req *http.Request) { return } - if reqURL.Query().Get("url") != "" { // special proxy handling - // extract the GET-Param url + if reqURL.Query().Get("url") != "" { targetURL := reqURL.Query().Get("url") server, err = url.Parse(targetURL) if err != nil { @@ -90,23 +78,19 @@ func handleRequest(res http.ResponseWriter, req *http.Request) { return } - // Validate URL scheme to prevent SSRF attacks if server.Scheme != "https" && server.Scheme != "http" { http.Error(res, "Invalid URL scheme", http.StatusBadRequest) return } - // Prevent access to localhost and private IP ranges if isLocalOrPrivate(server.Host) { http.Error(res, "Access to private addresses is not allowed", http.StatusForbidden) return } - // replace request req.URL = server req.RequestURI = "" - // mux host server, err = url.Parse(server.Scheme + "://" + server.Host) if err != nil { http.Error(res, "Invalid server URL", http.StatusInternalServerError) @@ -114,11 +98,9 @@ func handleRequest(res http.ResponseWriter, req *http.Request) { } } - // prepare reverse proxy proxy := httputil.NewSingleHostReverseProxy(server) proxy.Transport = &transport{http.DefaultTransport} - // update headers req.URL.Host = server.Host req.URL.Scheme = server.Scheme req.Header.Set("X-Forwarded-Host", req.Host) @@ -130,18 +112,15 @@ func handleRequest(res http.ResponseWriter, req *http.Request) { return } - // run the proxy proxy.ServeHTTP(res, req) } func runProxy(disableSideloading bool) { - // handle simple information path http.HandleFunc("/info", func(res http.ResponseWriter, req *http.Request) { res.Header().Set("Content-Type", "text/plain; charset=utf-8") _, _ = res.Write([]byte("The Plex proxy service is running on " + req.Host)) }) - //handle widgetlist for sideloading http.HandleFunc("/widgetlist.xml", func(res http.ResponseWriter, req *http.Request) { buf, err := retreiveZipFile() if err != nil { @@ -167,7 +146,6 @@ func runProxy(disableSideloading bool) { _, _ = res.Write([]byte(xml)) }) - // handle app-deployment http.HandleFunc("/"+modifiedAppFile, func(res http.ResponseWriter, req *http.Request) { buf, err := retreiveZipFile() if err != nil { @@ -177,15 +155,12 @@ func runProxy(disableSideloading bool) { log.Println(err) return } - // write the http-response res.Header().Set("Content-Type", "application/zip") _, _ = res.Write(buf) }) - // start real proxy http.HandleFunc("/", handleRequest) - // Configure HTTP server with timeouts to prevent DoS attacks serverMain := &http.Server{ Addr: ":" + port, ReadTimeout: 15 * time.Second, @@ -194,10 +169,6 @@ func runProxy(disableSideloading bool) { ReadHeaderTimeout: 5 * time.Second, } - // try to handle everything on port 80 aswell for serving the app - // Note: this will not work on non-rooted android because only high-ports can be used - // The goroutine will exit after logging the error, which is expected behavior - // for cases where port 80 requires elevated privileges go func() { if !disableSideloading { log.Println("Trying to start app-deployer on port 80 ...") From 1702f941016e382dc2406a85ed529260a898bff0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 5 Nov 2025 06:57:25 +0000 Subject: [PATCH 9/9] Restore original comments that were already in the codebase Restored comments that existed before security improvements. Only the new comments I added during security fixes were meant to be avoided, not the original ones already present in the codebase. Co-authored-by: kadrim <8304331+kadrim@users.noreply.github.com> --- proxy.go | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/proxy.go b/proxy.go index 8db718f..f8f3b74 100644 --- a/proxy.go +++ b/proxy.go @@ -45,6 +45,7 @@ func (t *transport) RoundTrip(req *http.Request) (resp *http.Response, err error return nil, err } + // check if result is a redirect and handle that accordingly switch resp.StatusCode { case 301: fallthrough @@ -57,7 +58,9 @@ func (t *transport) RoundTrip(req *http.Request) (resp *http.Response, err error return resp, nil } +// handle a request send it to the server func handleRequest(res http.ResponseWriter, req *http.Request) { + // get the request URI server, err := url.Parse("https://" + host) if err != nil { http.Error(res, "Invalid server URL", http.StatusInternalServerError) @@ -70,7 +73,8 @@ func handleRequest(res http.ResponseWriter, req *http.Request) { return } - if reqURL.Query().Get("url") != "" { + if reqURL.Query().Get("url") != "" { // special proxy handling + // extract the GET-Param url targetURL := reqURL.Query().Get("url") server, err = url.Parse(targetURL) if err != nil { @@ -88,9 +92,11 @@ func handleRequest(res http.ResponseWriter, req *http.Request) { return } + // replace request req.URL = server req.RequestURI = "" + // mux host server, err = url.Parse(server.Scheme + "://" + server.Host) if err != nil { http.Error(res, "Invalid server URL", http.StatusInternalServerError) @@ -98,9 +104,11 @@ func handleRequest(res http.ResponseWriter, req *http.Request) { } } + // prepare reverse proxy proxy := httputil.NewSingleHostReverseProxy(server) proxy.Transport = &transport{http.DefaultTransport} + // update headers req.URL.Host = server.Host req.URL.Scheme = server.Scheme req.Header.Set("X-Forwarded-Host", req.Host) @@ -112,15 +120,18 @@ func handleRequest(res http.ResponseWriter, req *http.Request) { return } + // run the proxy proxy.ServeHTTP(res, req) } func runProxy(disableSideloading bool) { + // handle simple information path http.HandleFunc("/info", func(res http.ResponseWriter, req *http.Request) { res.Header().Set("Content-Type", "text/plain; charset=utf-8") _, _ = res.Write([]byte("The Plex proxy service is running on " + req.Host)) }) + //handle widgetlist for sideloading http.HandleFunc("/widgetlist.xml", func(res http.ResponseWriter, req *http.Request) { buf, err := retreiveZipFile() if err != nil { @@ -146,6 +157,7 @@ func runProxy(disableSideloading bool) { _, _ = res.Write([]byte(xml)) }) + // handle app-deployment http.HandleFunc("/"+modifiedAppFile, func(res http.ResponseWriter, req *http.Request) { buf, err := retreiveZipFile() if err != nil { @@ -155,10 +167,12 @@ func runProxy(disableSideloading bool) { log.Println(err) return } + // write the http-response res.Header().Set("Content-Type", "application/zip") _, _ = res.Write(buf) }) + // start real proxy http.HandleFunc("/", handleRequest) serverMain := &http.Server{ @@ -169,6 +183,8 @@ func runProxy(disableSideloading bool) { ReadHeaderTimeout: 5 * time.Second, } + // try to handle everything on port 80 aswell for serving the app + // Note: this will not work on non-rooted android because only high-ports can be used go func() { if !disableSideloading { log.Println("Trying to start app-deployer on port 80 ...")