package handlers import ( "errors" "fmt" "net" "net/http" "net/url" "strings" "example.com/m/internal/services" "github.com/gin-gonic/gin" ) type LinksHandler struct { service *services.LinksService host string port string } type CreateLinkResponse struct { Status string `json:"status"` ShortLink string `json:"short_link"` OriginalLink string `json:"original_link"` ExpiresIn string `json:"expires_in"` } func NewLinksHandler(service *services.LinksService, host string, port string) *LinksHandler { return &LinksHandler{service: service, host: host, port: port} } func (h *LinksHandler) CreateLink(c *gin.Context) { link := c.Param("link") link = strings.TrimPrefix(link, "/") if c.Request.URL.RawQuery != "" { link += "?" + c.Request.URL.RawQuery } link, err := NormalizeURL(link) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "failed", "message": "invalid link"}) return } id, err := h.service.CreateLink(link) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed", "message": "failed to create link"}) return } address := fmt.Sprintf("https://%s/r/%s", h.host, id) response := CreateLinkResponse{ Status: "success", ShortLink: address, OriginalLink: link, ExpiresIn: "30d", } c.JSON(http.StatusOK, response) } func (h *LinksHandler) Redirect(c *gin.Context) { id := c.Param("id") original, err := h.service.GetLink(id) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "failed", "message": "link not found"}) return } c.Redirect(http.StatusFound, original) } func NormalizeURL(raw string) (string, error) { raw = strings.TrimSpace(raw) if raw == "" { return "", errors.New("empty URL") } if !strings.HasPrefix(raw, "http://") && !strings.HasPrefix(raw, "https://") { raw = "https://" + raw } u, err := url.Parse(raw) if err != nil { return "", errors.New("invalid URL") } if u.Host == "" { return "", errors.New("invalid host in URL") } host := u.Hostname() if isPrivateHost(host) { return "", errors.New("URLs pointing to private/internal addresses are not allowed") } return u.String(), nil } func isPrivateHost(host string) bool { ip := net.ParseIP(host) if ip == nil { addrs, err := net.LookupHost(host) if err != nil || len(addrs) == 0 { return false } ip = net.ParseIP(addrs[0]) if ip == nil { return false } } privateRanges := []string{ "127.0.0.0/8", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "169.254.0.0/16", "0.0.0.0/8", "::1/128", "fc00::/7", "fe80::/10", } for _, cidr := range privateRanges { _, network, _ := net.ParseCIDR(cidr) if network.Contains(ip) { return true } } return false }