21 Commits

Author SHA1 Message Date
Gianni Carafa
7ea7f253e8 fix Buildjob 2023-11-03 08:40:20 +01:00
Gianni Carafa
bf2529753d accept urls without scheme 2023-11-03 08:24:57 +01:00
Gianni Carafa
e3389d2df3 simplify Noform Option 2023-11-03 08:21:24 +01:00
Gianni Carafa
b786796595 improve logging and add disable form feature 2023-11-03 08:17:11 +01:00
Gianni Carafa
377a577c67 update docs 2023-11-02 23:30:13 +01:00
Gianni Carafa
6eb7b481d8 use fiber constant for statuscode 2023-11-02 23:20:02 +01:00
Gianni Carafa
2f1de95e06 improve docs 2023-11-02 23:05:42 +01:00
Gianni Carafa
7ae1a29932 add loggin switch 2023-11-02 22:59:07 +01:00
Gianni Carafa
63dcaeba3c handle queries 2023-11-02 22:50:03 +01:00
Gianni Carafa
62e03a384a Refactor duplicate code 2023-11-02 22:06:16 +01:00
Gianni Carafa
7f4d749c55 Add Version 2023-11-02 22:05:57 +01:00
Gianni Carafa
e748cb09a5 Add favicon 2023-11-02 22:04:29 +01:00
Gianni Carafa
c98e49f2b3 add basic auth 2023-11-02 20:57:44 +01:00
Gianni Carafa
3e3eebcdc2 make user-agent and x-forwaded-for configurable, rename debug path to raw 2023-11-02 19:35:10 +01:00
Gianni Carafa
45a3fe2adf rename debug path to raw 2023-11-02 19:19:13 +01:00
Gianni Carafa
ec7f2089fc Rename the binary from "kubero" to "ladder" in the .goreleaser.yaml file. 2023-11-02 19:17:03 +01:00
Gianni Carafa
622143211c add some tests 2023-11-02 19:00:21 +01:00
Gianni Carafa
796ffd1c87 rename public to assets 2023-11-02 18:46:16 +01:00
Gianni Carafa
210837a404 improve docs 2023-11-02 18:42:53 +01:00
Gianni Carafa
aa1caaca98 add api path 2023-11-02 18:25:31 +01:00
Gianni Carafa
ca26f6d88d add command 2023-11-02 17:47:08 +01:00
20 changed files with 423 additions and 88 deletions

View File

@@ -22,7 +22,7 @@ jobs:
- -
name: Set version name: Set version
run: | run: |
echo -n $(git describe --tags --abbrev=0) > cmd/VERSION echo -n $(git describe --tags --abbrev=0) > handlers/VERSION
- -
name: Set up Go name: Set up Go
uses: actions/setup-go@v3 uses: actions/setup-go@v3

View File

@@ -42,7 +42,7 @@ jobs:
- name: Set version - name: Set version
id: version id: version
run: | run: |
echo ${GITHUB_REF#refs/tags/v} > cmd/VERSION echo ${GITHUB_REF#refs/tags/v} > handlers/VERSION
# Install the cosign tool except on PR # Install the cosign tool except on PR
# https://github.com/sigstore/cosign-installer # https://github.com/sigstore/cosign-installer

View File

@@ -7,7 +7,7 @@ before:
builds: builds:
- -
main: cmd/main.go main: cmd/main.go
binary: kubero binary: ladder
env: env:
- CGO_ENABLED=0 - CGO_ENABLED=0
goos: goos:

View File

@@ -21,3 +21,5 @@ RUN apt update && apt install -y ca-certificates && rm -rf /var/lib/apt/lists/*
#EXPOSE 8080 #EXPOSE 8080
#ENTRYPOINT ["/usr/bin/dumb-init", "--"] #ENTRYPOINT ["/usr/bin/dumb-init", "--"]
CMD ["sh", "-c", "/app/ladder"]

View File

@@ -1,5 +1,5 @@
<p align="center"> <p align="center">
<img src="public/pigeon.svg" width="100px"> <img src="assets/pigeon.svg" width="100px">
</p> </p>
<h1 align="center">Ladder</h1> <h1 align="center">Ladder</h1>
@@ -10,22 +10,29 @@
Freedom of information is an essential pillar of democracy and informed decision-making. While media organizations have legitimate financial interests, it is crucial to strike a balance between profitability and the public's right to access information. The proliferation of paywalls raises concerns about the erosion of this fundamental freedom, and it is imperative for society to find innovative ways to preserve access to vital information without compromising the sustainability of journalism. In a world where knowledge should be shared and not commodified, paywalls should be critically examined to ensure that they do not undermine the principles of an open and informed society. Freedom of information is an essential pillar of democracy and informed decision-making. While media organizations have legitimate financial interests, it is crucial to strike a balance between profitability and the public's right to access information. The proliferation of paywalls raises concerns about the erosion of this fundamental freedom, and it is imperative for society to find innovative ways to preserve access to vital information without compromising the sustainability of journalism. In a world where knowledge should be shared and not commodified, paywalls should be critically examined to ensure that they do not undermine the principles of an open and informed society.
Some site might have missing images or other formating issues. This is due to the fact that the site using javascript or CSS to load the images/JS/CSS. This is a limitation of this proxy. If you prefer a full experience, please concider buying a subscription for the site. Certain sites may display missing images or encounter formatting issues. This can be attributed to the site's reliance on JavaScript or CSS for image and resource loading, which presents a limitation when accessed through this proxy. If you prefer a full experience, please concider buying a subscription for the site.
### Features ### Features
- [x] Bypass Paywalls - [x] Bypass Paywalls
- [x] Remove CORS - [x] Remove CORS headers from responses, assets, and images ...
- [x] Keep Site browsable - [x] Keep site browsable
- [x] Docker Container - [x] API
- [x] Linux Binary - [x] Show RAW HTML
- [x] Mac OS Binary - [x] Docker container
- [x] Windows Binary (Untested) - [x] Linux binary
- [x] Mac OS binary
- [x] Windows binary (untested)
- [x] Remove most of the ads (unexpected side effect)
- [x] Basic Auth
- [x] Disable logs
- [x] Custom User Agent
- [x] Custom X-Forwarded-For IP
## How to use ## Installation
### Binary ### Binary
1) Download Binary 1) Download binary [here](https://github.com/kubero-dev/ladder/releases/latest)
2) Run Binary 2) Unpack and run the binary `./ladder`
3) Open Browser (Default: http://localhost:8080) 3) Open Browser (Default: http://localhost:8080)
### Docker ### Docker
@@ -35,10 +42,28 @@ docker run -p 8080:8080 -d --name ladder ghcr.io/kubero-dev/ladder:latest
### Docker Compose ### Docker Compose
```bash ```bash
wget https://raw.githubusercontent.com/kubero-dev/ladder/main/docker-compose.yml curl https://raw.githubusercontent.com/kubero-dev/ladder/main/docker-compose.yaml --output docker-compose.yaml
docker-compose up -d docker-compose up -d
``` ```
## Usage
### Browser
1) Open Browser (Default: http://localhost:8080)
2) Enter URL
3) Press Enter
Or direct by appending the URL to the end of the proxy URL:
http://localhost:8080/https://www.google.com
### API
```bash
curl -X GET "http://localhost:8080/api/https://www.google.com"
```
### RAW
http://localhost:8080/raw/https://www.google.com
## Configuration ## Configuration
### Environment Variables ### Environment Variables
@@ -47,3 +72,8 @@ docker-compose up -d
| --- | --- | --- | | --- | --- | --- |
| `PORT` | Port to listen on | `8080` | | `PORT` | Port to listen on | `8080` |
| `PREFORK` | Spawn multiple server instances | `false` | | `PREFORK` | Spawn multiple server instances | `false` |
| `USER_AGENT` | User agent to emulate | `Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)` |
| `X_FORWARDED_FOR` | IP forwarder address | `66.249.66.1` |
| `USERPASS` | Enables Basic Auth, format `admin:123456` | `` |
| `LOG_URLS` | Log fetched URL's | `true` |
| `DISABLE_FORM` | Disables URL Form Frontpage | `false` |

View File

@@ -159,8 +159,11 @@
}); });
document.getElementById('inputForm').addEventListener('submit', function (e) { document.getElementById('inputForm').addEventListener('submit', function (e) {
e.preventDefault(); e.preventDefault();
const inputValue = document.getElementById('inputField').value; let url = document.getElementById('inputField').value;
window.location.href = '/' + inputValue; if (url.indexOf('http') === -1) {
url = 'https://' + url;
}
window.location.href = '/' + url;
return false; return false;
}); });
</script> </script>

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
cmd/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,14 +1,22 @@
package main package main
import ( import (
_ "embed"
"ladder/handlers" "ladder/handlers"
"log" "log"
"os" "os"
"strconv" "strconv"
"strings"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/basicauth"
"github.com/gofiber/fiber/v2/middleware/favicon"
) )
//go:embed favicon.ico
var faviconData string
func main() { func main() {
prefork, _ := strconv.ParseBool(os.Getenv("PREFORK")) prefork, _ := strconv.ParseBool(os.Getenv("PREFORK"))
@@ -18,8 +26,32 @@ func main() {
}, },
) )
userpass := os.Getenv("USERPASS")
if userpass != "" {
userpass := strings.Split(userpass, ":")
app.Use(basicauth.New(basicauth.Config{
Users: map[string]string{
userpass[0]: userpass[1],
},
}))
}
app.Use(favicon.New(favicon.Config{
Data: []byte(faviconData),
URL: "/favicon.ico",
}))
if os.Getenv("NOLOGS") != "true" {
app.Use(func(c *fiber.Ctx) error {
log.Println(c.Method(), c.Path())
return c.Next()
})
}
app.Get("/", handlers.Form) app.Get("/", handlers.Form)
app.Get("debug/*", handlers.Debug)
app.Get("raw/*", handlers.Raw)
app.Get("api/*", handlers.Api)
app.Get("/*", handlers.ProxySite) app.Get("/*", handlers.ProxySite)
port := os.Getenv("PORT") port := os.Getenv("PORT")

View File

@@ -5,12 +5,15 @@ services:
container_name: ladder container_name: ladder
build: . build: .
#restart: always #restart: always
#command: tail -f /dev/null #command: sh -c ./ladder
command: sh -c ./ladder
environment: environment:
- PORT=8080 - PORT=8080
- PREFORK=true #- PREFORK=true
#- GODEBUG=netdns=go+4 #- X_FORWARDED_FOR=66.249.66.1
#- USER_AGENT=Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)
#- USERPASS=foo:bar
#- LOG_URLS=true
#- GODEBUG=netdns=go
ports: ports:
- "8080:8080" - "8080:8080"
deploy: deploy:

8
go.mod
View File

@@ -2,17 +2,23 @@ module ladder
go 1.21.1 go 1.21.1
require github.com/gofiber/fiber/v2 v2.50.0
require ( require (
github.com/andybalholm/brotli v1.0.6 // indirect github.com/andybalholm/brotli v1.0.6 // indirect
github.com/gofiber/fiber/v2 v2.50.0 github.com/davecgh/go-spew v1.1.1 // indirect
github.com/google/uuid v1.4.0 // indirect github.com/google/uuid v1.4.0 // indirect
github.com/klauspost/compress v1.17.2 // indirect github.com/klauspost/compress v1.17.2 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.4.4 // indirect github.com/rivo/uniseg v0.4.4 // indirect
github.com/stretchr/objx v0.5.0 // indirect
github.com/stretchr/testify v1.8.4 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.50.0 // indirect github.com/valyala/fasthttp v1.50.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect
golang.org/x/sys v0.13.0 // indirect golang.org/x/sys v0.13.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
) )

17
go.sum
View File

@@ -1,5 +1,8 @@
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gofiber/fiber/v2 v2.50.0 h1:ia0JaB+uw3GpNSCR5nvC5dsaxXjRU5OEu36aytx+zGw= github.com/gofiber/fiber/v2 v2.50.0 h1:ia0JaB+uw3GpNSCR5nvC5dsaxXjRU5OEu36aytx+zGw=
github.com/gofiber/fiber/v2 v2.50.0/go.mod h1:21eytvay9Is7S6z+OgPi7c7n4++tnClWmhpimVHMimw= github.com/gofiber/fiber/v2 v2.50.0/go.mod h1:21eytvay9Is7S6z+OgPi7c7n4++tnClWmhpimVHMimw=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
@@ -13,9 +16,19 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.50.0 h1:H7fweIlBm0rXLs2q0XbalvJ6r0CUPFWK3/bB4N13e9M= github.com/valyala/fasthttp v1.50.0 h1:H7fweIlBm0rXLs2q0XbalvJ6r0CUPFWK3/bB4N13e9M=
@@ -26,3 +39,7 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

58
handlers/api.go Normal file
View File

@@ -0,0 +1,58 @@
package handlers
import (
_ "embed"
"log"
"github.com/gofiber/fiber/v2"
)
//go:embed VERSION
var version string
func Api(c *fiber.Ctx) error {
// Get the url from the URL
urlQuery := c.Params("*")
queries := c.Queries()
body, req, resp, err := fetchSite(urlQuery, queries)
if err != nil {
log.Println("ERROR:", err)
c.SendStatus(500)
return c.SendString(err.Error())
}
response := Response{
Version: version,
Body: body,
}
response.Request.Headers = make([]interface{}, 0)
for k, v := range req.Header {
response.Request.Headers = append(response.Request.Headers, map[string]string{
"key": k,
"value": v[0],
})
}
response.Response.Headers = make([]interface{}, 0)
for k, v := range resp.Header {
response.Response.Headers = append(response.Response.Headers, map[string]string{
"key": k,
"value": v[0],
})
}
return c.JSON(response)
}
type Response struct {
Version string `json:"version"`
Body string `json:"body"`
Request struct {
Headers []interface{} `json:"headers"`
} `json:"request"`
Response struct {
Headers []interface{} `json:"headers"`
} `json:"response"`
}

44
handlers/api.test.go Normal file
View File

@@ -0,0 +1,44 @@
// BEGIN: 7d5e1f7c7d5e
package handlers
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/gofiber/fiber/v2"
"github.com/stretchr/testify/assert"
)
func TestApi(t *testing.T) {
app := fiber.New()
app.Get("/api/*", Api)
tests := []struct {
name string
url string
expectedStatus int
}{
{
name: "valid url",
url: "https://www.google.com",
expectedStatus: http.StatusOK,
},
{
name: "invalid url",
url: "invalid-url",
expectedStatus: http.StatusBadRequest,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/api/"+tt.url, nil)
resp, err := app.Test(req)
assert.NoError(t, err)
assert.Equal(t, tt.expectedStatus, resp.StatusCode)
})
}
}
// END: 7d5e1f7c7d5e

View File

@@ -1,44 +0,0 @@
package handlers
import (
"io"
"log"
"net/http"
"net/url"
"github.com/gofiber/fiber/v2"
)
func Debug(c *fiber.Ctx) error {
// Get the url from the URL
urlQuery := c.Params("*")
u, err := url.Parse(urlQuery)
if err != nil {
return c.SendString(err.Error())
}
log.Println(u.String())
// Fetch the site
client := &http.Client{}
req, _ := http.NewRequest("GET", u.String(), nil)
req.Header.Set("User-Agent", "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)")
req.Header.Set("X-Forwarded-For", "66.249.66.1")
req.Header.Set("Referer", u.String())
req.Header.Set("Host", u.Host)
resp, err := client.Do(req)
if err != nil {
return c.SendString(err.Error())
}
defer resp.Body.Close()
bodyB, err := io.ReadAll(resp.Body)
if err != nil {
log.Println("ERROR", err)
return c.SendString(err.Error())
}
body := rewriteHtml(bodyB, u)
return c.SendString(body)
}

View File

@@ -1,11 +1,21 @@
package handlers package handlers
import "github.com/gofiber/fiber/v2" import (
"os"
"github.com/gofiber/fiber/v2"
)
func Form(c *fiber.Ctx) error { func Form(c *fiber.Ctx) error {
if os.Getenv("DISABLE_FORM") == "true" {
c.Set("Content-Type", "text/html")
c.SendStatus(fiber.StatusNotFound)
return c.SendString("Form Disabled")
} else {
c.Set("Content-Type", "text/html") c.Set("Content-Type", "text/html")
return c.SendString(html) return c.SendString(html)
} }
}
const html = ` const html = `
<!DOCTYPE html> <!DOCTYPE html>
@@ -169,8 +179,11 @@ const html = `
}); });
document.getElementById('inputForm').addEventListener('submit', function (e) { document.getElementById('inputForm').addEventListener('submit', function (e) {
e.preventDefault(); e.preventDefault();
const inputValue = document.getElementById('inputField').value; let url = document.getElementById('inputField').value;
window.location.href = '/' + inputValue; if (url.indexOf('http') === -1) {
url = 'https://' + url;
}
window.location.href = '/' + url;
return false; return false;
}); });
</script> </script>

View File

@@ -6,49 +6,73 @@ import (
"log" "log"
"net/http" "net/http"
"net/url" "net/url"
"os"
"regexp" "regexp"
"strings" "strings"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
) )
var UserAgent = getenv("USER_AGENT", "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)")
var ForwardedFor = getenv("X_FORWARDED_FOR", "66.249.66.1")
func ProxySite(c *fiber.Ctx) error { func ProxySite(c *fiber.Ctx) error {
// Get the url from the URL // Get the url from the URL
urlQuery := c.Params("*") url := c.Params("*")
u, err := url.Parse(urlQuery) queries := c.Queries()
body, _, resp, err := fetchSite(url, queries)
if err != nil { if err != nil {
log.Println("ERROR", err) log.Println("ERROR:", err)
c.SendStatus(500) c.SendStatus(fiber.StatusInternalServerError)
return c.SendString(err.Error()) return c.SendString(err.Error())
} }
log.Println(u.String()) c.Set("Content-Type", resp.Header.Get("Content-Type"))
return c.SendString(body)
}
func fetchSite(urlpath string, queries map[string]string) (string, *http.Request, *http.Response, error) {
urlQuery := "?"
if len(queries) > 0 {
for k, v := range queries {
urlQuery += k + "=" + v + "&"
}
}
urlQuery = strings.TrimSuffix(urlQuery, "&")
urlQuery = strings.TrimSuffix(urlQuery, "?")
u, err := url.Parse(urlpath)
if err != nil {
return "", nil, nil, err
}
if os.Getenv("DEBUG ") == "true" {
log.Println(u.String() + urlQuery)
}
// Fetch the site // Fetch the site
client := &http.Client{} client := &http.Client{}
req, _ := http.NewRequest("GET", u.String(), nil) req, _ := http.NewRequest("GET", u.String()+urlQuery, nil)
req.Header.Set("User-Agent", "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)") req.Header.Set("User-Agent", UserAgent)
req.Header.Set("X-Forwarded-For", "66.249.66.1") req.Header.Set("X-Forwarded-For", ForwardedFor)
req.Header.Set("Referer", u.String()) req.Header.Set("Referer", u.String())
req.Header.Set("Host", u.Host) req.Header.Set("Host", u.Host)
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
return c.SendString(err.Error()) return "", nil, nil, err
} }
defer resp.Body.Close() defer resp.Body.Close()
bodyB, err := io.ReadAll(resp.Body) bodyB, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
log.Println("ERROR", err) return "", nil, nil, err
c.SendStatus(500)
return c.SendString(err.Error())
} }
body := rewriteHtml(bodyB, u) body := rewriteHtml(bodyB, u)
c.Set("Content-Type", resp.Header.Get("Content-Type")) return body, req, resp, nil
return c.SendString(body)
} }
func rewriteHtml(bodyB []byte, u *url.URL) string { func rewriteHtml(bodyB []byte, u *url.URL) string {
@@ -63,7 +87,7 @@ func rewriteHtml(bodyB []byte, u *url.URL) string {
// scripts // scripts
scriptPattern := `<script\s+([^>]*\s+)?src="(/)([^"]*)"` scriptPattern := `<script\s+([^>]*\s+)?src="(/)([^"]*)"`
reScript := regexp.MustCompile(scriptPattern) reScript := regexp.MustCompile(scriptPattern)
body = reScript.ReplaceAllString(body, fmt.Sprintf(`<script $1 script="%s$3"`, "/https://"+u.Host)) body = reScript.ReplaceAllString(body, fmt.Sprintf(`<script $1 script="%s$3"`, "/https://"+u.Host+"/"))
//body = strings.ReplaceAll(body, "srcset=\"/", "srcset=\"/https://"+u.Host+"/") // TODO: Needs a regex to rewrite the URL's //body = strings.ReplaceAll(body, "srcset=\"/", "srcset=\"/https://"+u.Host+"/") // TODO: Needs a regex to rewrite the URL's
body = strings.ReplaceAll(body, "href=\"/", "href=\"/https://"+u.Host+"/") body = strings.ReplaceAll(body, "href=\"/", "href=\"/https://"+u.Host+"/")
@@ -73,3 +97,11 @@ func rewriteHtml(bodyB []byte, u *url.URL) string {
return body return body
} }
func getenv(key, fallback string) string {
value := os.Getenv(key)
if len(value) == 0 {
return fallback
}
return value
}

58
handlers/proxy.test.go Normal file
View File

@@ -0,0 +1,58 @@
// BEGIN: 6f8b3f5d5d5d
package handlers
import (
"net/http"
"net/http/httptest"
"net/url"
"testing"
"github.com/gofiber/fiber/v2"
"github.com/stretchr/testify/assert"
)
func TestProxySite(t *testing.T) {
app := fiber.New()
app.Get("/:url", ProxySite)
req := httptest.NewRequest("GET", "/https://example.com", nil)
resp, err := app.Test(req)
assert.NoError(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode)
}
func TestRewriteHtml(t *testing.T) {
bodyB := []byte(`
<html>
<head>
<title>Test Page</title>
</head>
<body>
<img src="/image.jpg">
<script src="/script.js"></script>
<a href="/about">About Us</a>
<div style="background-image: url('/background.jpg')"></div>
</body>
</html>
`)
u := &url.URL{Host: "example.com"}
expected := `
<html>
<head>
<title>Test Page</title>
</head>
<body>
<img src="/https://example.com/image.jpg">
<script script="/https://example.com/script.js"></script>
<a href="/https://example.com/about">About Us</a>
<div style="background-image: url('/https://example.com/background.jpg')"></div>
</body>
</html>
`
actual := rewriteHtml(bodyB, u)
assert.Equal(t, expected, actual)
}
// END: 6f8b3f5d5d5d

21
handlers/raw.go Normal file
View File

@@ -0,0 +1,21 @@
package handlers
import (
"log"
"github.com/gofiber/fiber/v2"
)
func Raw(c *fiber.Ctx) error {
// Get the url from the URL
urlQuery := c.Params("*")
queries := c.Queries()
body, _, _, err := fetchSite(urlQuery, queries)
if err != nil {
log.Println("ERROR:", err)
c.SendStatus(500)
return c.SendString(err.Error())
}
return c.SendString(body)
}

60
handlers/raw.test.go Normal file
View File

@@ -0,0 +1,60 @@
// BEGIN: 7f8d9e6d4b5c
package handlers
import (
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/gofiber/fiber/v2"
)
func TestRaw(t *testing.T) {
app := fiber.New()
app.Get("/raw/*", Raw)
testCases := []struct {
name string
url string
expected string
}{
{
name: "valid url",
url: "https://www.google.com",
expected: "<!doctype html>",
},
{
name: "invalid url",
url: "invalid-url",
expected: "parse invalid-url: invalid URI for request",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/raw/"+tc.url, nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Errorf("expected status OK; got %v", resp.Status)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !strings.Contains(string(body), tc.expected) {
t.Errorf("expected body to contain %q; got %q", tc.expected, string(body))
}
})
}
}
// END: 7f8d9e6d4b5c