//go:build linux || freebsd // +build linux freebsd package http import ( "fmt" "mime" "net/http" "net/url" "path/filepath" "strings" ) // URLInfo contains extracted information from an HTTP URL type URLInfo struct { Scheme string Host string Port string Path string Query string Fragment string User *url.Userinfo } // Handler contains HTTP-specific logic type Handler struct { client *Client } // NewHandler creates new handler func NewHandler(client *Client) *Handler { return &Handler{client: client} } // ParseHTTPURL parses an HTTP URL and extracts information func ParseHTTPURL(u *url.URL) (*URLInfo, error) { if u.Scheme != "http" && u.Scheme != "https" { return nil, fmt.Errorf("invalid http url scheme: %s", u.Scheme) } host := u.Hostname() port := u.Port() if port == "" { if u.Scheme == "https" { port = "443" } else { port = "80" } } return &URLInfo{ Scheme: u.Scheme, Host: host, Port: port, Path: u.Path, Query: u.RawQuery, Fragment: u.Fragment, User: u.User, }, nil } // GetContentType extracts Content-Type from the response func GetContentType(resp *http.Response) string { return resp.Header.Get("Content-Type") } // GetContentLength extracts Content-Length from the response func GetContentLength(resp *http.Response) int64 { return resp.ContentLength } // GetContentEncoding extracts Content-Encoding from the response func GetContentEncoding(resp *http.Response) string { return resp.Header.Get("Content-Encoding") } // GetETag extracts ETag from the response func GetETag(resp *http.Response) string { return resp.Header.Get("ETag") } // GetLastModified extracts Last-Modified from the response func GetLastModified(resp *http.Response) string { return resp.Header.Get("Last-Modified") } // SupportsRangeCheck checks whether the server supports range requests func SupportsRangeCheck(resp *http.Response) bool { acceptRanges := resp.Header.Get("Accept-Ranges") return strings.ToLower(acceptRanges) == "bytes" } // IsRedirect checks whether the response is a redirect func IsRedirect(statusCode int) bool { return statusCode >= 300 && statusCode < 400 } // GetRedirectLocation gets the Location header from a redirect response func GetRedirectLocation(resp *http.Response) string { return resp.Header.Get("Location") } // BuildURLWithQuery creates a URL with query parameters func BuildURLWithQuery(base *url.URL, params map[string]string) (*url.URL, error) { if base == nil { return nil, fmt.Errorf("base url cannot be nil") } result := *base query := result.Query() for key, value := range params { query.Set(key, value) } result.RawQuery = query.Encode() return &result, nil } // GetUserinfo extracts authentication credentials from the URL func GetUserinfo(u *url.URL) (username, password string, ok bool) { if u.User == nil { return "", "", false } username = u.User.Username() password, ok = u.User.Password() return username, password, true } // IsSecure checks whether the URL uses HTTPS func IsSecure(u *url.URL) bool { return u.Scheme == "https" } // GetDefaultPortForScheme returns the default port for the scheme func GetDefaultPortForScheme(scheme string) string { switch scheme { case "https": return "443" case "http": return "80" default: return "" } } // ParseContentDisposition extracts filename from Content-Disposition header func ParseContentDisposition(headerValue string) string { if headerValue == "" { return "" } _, params, err := mime.ParseMediaType(headerValue) if err != nil { return "" } // Try filename* first (RFC 5987, extended filename) if filename, ok := params["filename*"]; ok { return filename } // Fall back to filename if filename, ok := params["filename"]; ok { return filename } return "" } // SanitizeFilename removes path components from a filename for security func SanitizeFilename(name string) string { name = filepath.Base(name) if name == "" || name == "." || name == ".." { return "" } return name }