Spaces:
Running
Running
File size: 3,970 Bytes
8549d8b 9ef2fb2 8549d8b 9ef2fb2 8549d8b 9ef2fb2 48e528b a7ca764 abadbbd a7ca764 abadbbd a7ca764 9ef2fb2 98e2d95 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
package main
import (
"io"
"log"
"net/http"
"net/url"
"strings"
"time"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
)
// SecureHeaders sets security-related HTTP headers
func SecureHeaders() gin.HandlerFunc {
return func(c *gin.Context) {
c.Writer.Header().Set("X-Content-Type-Options", "nosniff")
c.Next()
}
}
// ValidReqPath checks the X-Req-Path is safe for proxying
func ValidReqPath(path string) bool {
if !strings.HasPrefix(path, "https://") {
return false
}
if strings.Contains(path, "..") {
return false
}
if strings.TrimSpace(path) == "" {
return false
}
return true
}
// Extract the root domain (e.g., "example.com" from "sub.example.com")
func extractRootDomain(urlStr string) (string, error) {
parsedURL, err := url.Parse(urlStr)
if err != nil {
return "", err
}
hostname := parsedURL.Hostname()
parts := strings.Split(hostname, ".")
// Handle special cases with fewer than 2 parts
if len(parts) < 2 {
return hostname, nil
}
// Get the last two parts as the root domain
domain := parts[len(parts)-2] + "." + parts[len(parts)-1]
return domain, nil
}
// IsValidBackendURL validates that the constructed URL belongs to the expected backend
func IsValidBackendURL(targetURL, backend string) bool {
targetApexDomain, err := extractRootDomain(targetURL)
if err != nil {
return false
}
backendApexDomain, err := extractRootDomain(targetURL)
if err != nil {
return false
}
return targetApexDomain == backendApexDomain
}
func main() {
r := gin.Default()
r.Use(SecureHeaders())
// CORS config for /api/req
api := r.Group("/api")
api.Use(cors.New(cors.Config{
AllowOrigins: []string{"*"},
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowHeaders: []string{"Origin", "Content-Type", "Authorization", "X-Req-Path"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
MaxAge: 12 * time.Hour,
}))
// Secure proxy forward
api.Any("/req", func(c *gin.Context) {
const backend = "https://deeploy.ml"
reqPath := c.GetHeader("X-Req-Path")
if !ValidReqPath(reqPath) {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid X-Req-Path"})
return
}
targetURL := reqPath
if !IsValidBackendURL(targetURL, backend) {
c.JSON(http.StatusForbidden, gin.H{"error": "Target not allowed"})
return
}
// Prepare the proxied request
req, err := http.NewRequest(c.Request.Method, targetURL, c.Request.Body)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Request creation failed"})
return
}
req.Header = c.Request.Header.Clone()
req.Header.Del("X-Req-Path") // Don't forward custom header
client := &http.Client{
Timeout: 60 * time.Second,
}
resp, err := client.Do(req)
if err != nil {
log.Printf("Error proxying request: Method=%s URL=%s Headers=%v Error=%v",
req.Method, req.URL, req.Header, err)
c.JSON(http.StatusBadGateway, gin.H{"error": "Backend request failed."})
return
}
defer resp.Body.Close()
// Copy headers & status from the backend
for k, v := range resp.Header {
for _, vv := range v {
c.Writer.Header().Add(k, vv)
}
}
c.Status(resp.StatusCode)
io.Copy(c.Writer, resp.Body)
})
// Serve static files from ./frontend, no directory listing
r.StaticFS("/assets", gin.Dir("./assets", false))
r.GET("/", func(c *gin.Context) {
log.Println("root route")
// Load HTML templates from ./frontend folder
// Render the template by name
r.LoadHTMLFiles("index.html")
// Render the template by name
c.HTML(http.StatusOK, "index.html", gin.H{})
})
// Route to serve parsed HTML template partials
r.GET("/partials", func(c *gin.Context) {
// Load HTML templates from ./partials folder
r.LoadHTMLGlob("./frontend/partials/*")
// Render the template by name
c.HTML(http.StatusOK, "index_partial.html", gin.H{})
})
log.Println("Serving on http://localhost:7860")
r.Run(":7860")
} |