180 lines
4.0 KiB
Go
180 lines
4.0 KiB
Go
//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
|
||
|
|
}
|