Hi, I'm here to create a fully custom golang reverse proxy.

I already have some of the code written out, but some others I need help creating.

I believe it should be split up into main.go, firewall.go, ua_changes.go, flagged_ua.go, config.go utils.go.

I from what I could Identify have utils.go, some of config.go, some of ua_changed.go and flagged_ua.go. possibly some main.go too.

Please do not change anything or add anything, just advise on what I can do and put them in their associated file.

```
package main

import (
    "fmt"
    "log"
    "strings"
    "time"

    "github.com/valyala/fasthttp"
)

var (
    targetHost = "example.com:80"

    client = &fasthttp.HostClient{
        Addr:                          targetHost,
        MaxConns:                      1024,
        ReadTimeout:                   10 * time.Second,
        WriteTimeout:                  10 * time.Second,
        DisableHeaderNamesNormalizing: true,
    }
)

func ProxyHandler(ctx *fasthttp.RequestCtx) {
    req := new(fasthttp.Request)
    resp := new(fasthttp.Response)

    ctx.Request.CopyTo(req)

    req.SetHost(strings.Split(targetHost, ":")[0])

    err := client.Do(req, resp)
    if err != nil {
        ctx.Error(fmt.Sprintf("Error: %v", err), fasthttp.StatusBadGateway)
        log.Printf("Error forwarding request: %v", err)
        return
    }

    resp.CopyTo(&ctx.Response)
}

func main() {
    log.Println("Starting reverse proxy server on :8080...")

    err := fasthttp.ListenAndServe(":8080", ProxyHandler)
    if err != nil {
        log.Fatalf("Error in ListenAndServe: %v", err)
    }
}```

```
package firewall

import (
    "strings"
    "sync/atomic"
)

func containsUA(userAgent, flaggedUA string) bool {
    return strings.Contains(userAgent, flaggedUA)
}

func (f *Firewall) resetFlaggedUACount(tracker *requestTracker, flaggedUA string) {
    tracker.mu.Lock()
    defer tracker.mu.Unlock()
    
    atomic.StoreInt64(tracker.flaggedUACount[flaggedUA], 0)
    delete(tracker.uaResetTimers, flaggedUA)
}

func (f *Firewall) resetRequestCount(tracker *requestTracker, ip string) {
    tracker.mu.Lock()
    defer tracker.mu.Unlock()
    
    atomic.StoreInt64(tracker.requestCount[ip], 0)
    delete(tracker.resetTimers, ip)
}

func (f *Firewall) uaChangeStage(tracker *requestTracker, ip string, currentUA string, uaChangeLimit int64) int {
    previousUA, exists := tracker.previousUserAgent[ip]

    if !exists {
        tracker.previousUserAgent[ip] = currentUA
        return 1
    }

    if previousUA != currentUA {
        uaChangeCount := atomic.AddInt64(tracker.requestCount[ip], 1)
        log.Printf("UA change detected for %v: prev: %v, curr: %v, cc: %v", ip, previousUA, currentUA, uaChangeCount)

        if uaChangeCount > uaChangeLimit && uaChangeLimit != 0 {
            return 1003
        }

        tracker.previousUserAgent[ip] = currentUA
    }
    return 1
}```

```
for host, backend := range f.cfg.BackendMap {
    if host == string(ctx.Request.Host()) || backend.Parent == string(ctx.Request.Host()) {
        tracker := f.BackendTrackers[host]
        
        tracker.mu.Lock()
        tracker.Whitelist.Store(ip, time.Now().Add(tracker.WhitelistDuration))
        tracker.mu.Unlock()
    }
}
```

```
if isWebSocketUpgrade(ctx) && filter == 1 {
    backendConfig := cfg.BackendMap[string(ctx.Request.Host())]
    backendURL := backendConfig.Host + string(ctx.RequestURI())
    wsProxyHandler(ctx, backendURL, cfg)
    log.Print("%5")
    return
}
```
```
// Has passed all checks but it is not in the whitelist, do the embedded JS challenge.
if tracker.challengeFailures[ip] >= backend.challengeFailure {
    tracker.blacklist[ip] = time.Now().Add(tracker.blacklistDuration)
    log.Printf("IP %s has been blacklisted for exceeding challenge limit", ip)
    return 403
}
tracker.challengeFailures[ip]++
return 1001
```
```
  {
    "backend_map": {
      "bestreverseproxy.xyz": {
        "host": "https://example.com",
        "ratelimit": 100,
        "ratelimit_interval": 10,
        "tls": true,
        "cert_file": "./certs/bestreverseproxy.cert",
        "key_file": "./certs/bestreverseproxy.key",
        "host_header": true,
        "cloudflare": true,
        "ua_change_limit": 3
      }
    },
    "flagged_uas": [
      [
        "X11",
        150
      ]
    ]
  }
```
```
if filter == 1001 {
        contentType := resp.Header.Peek("Content-Type")
        if strings.Contains(string(contentType), "text/html") {
            challengeID := challenge.CreateUniqueChallengeID(ctx, cloudflare)
            challengeScript := `<script src="/rproxy/` + challengeID + `.js"></script>`
            body := resp.Body()
            modifiedBody := strings.ReplaceAll(string(body), "</body>", challengeScript+"</body>")

            ctx.SetStatusCode(resp.StatusCode())
            resp.Header.VisitAll(func(k, v []byte) {
                ctx.Response.Header.Set(string(k), string(v))
            })
            ctx.SetBodyString(modifiedBody)
            logger.AddEntry(logger.AccessLog{Action: "Invisible Challenge", SourceIP: ip, Timestamp: time.Now(), UserAgent: string(ctx.Request.Header.Peek("User-Agent")), Referer: string(ctx.Request.Header.Peek("Referer")), Host: string(ctx.Request.Host()), Path: string(ctx.Request.URI().String())})
        } else {
            ctx.SetBody(resp.Body())
        }
        return
    }```
this stops intrusive browser methods because this loads script directly:
```
if filter == 1001 {
        contentType := resp.Header.Peek("Content-Type")
        if strings.Contains(string(contentType), "text/html") {
            challengeID := challenge.CreateUniqueChallengeID(ctx, cloudflare)
            challengeScript := `<script src="/rproxy/` + challengeID + `.js"></script>`
            body := resp.Body()
            modifiedBody := strings.ReplaceAll(string(body), "</body>", challengeScript+"</body>")

            ctx.SetStatusCode(resp.StatusCode())
            resp.Header.VisitAll(func(k, v []byte) {
                ctx.Response.Header.Set(string(k), string(v))
            })
            ctx.SetBodyString(modifiedBody)
            logger.AddEntry(logger.AccessLog{Action: "Invisible Challenge", SourceIP: ip, Timestamp: time.Now(), UserAgent: string(ctx.Request.Header.Peek("User-Agent")), Referer: string(ctx.Request.Header.Peek("Referer")), Host: string(ctx.Request.Host()), Path: string(ctx.Request.URI().String())})
        } else {
            ctx.SetBody(resp.Body())
        }
        return
    }
```