feat: initial goget release — modern IPv6-first download utility
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
//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
|
||||
}
|
||||
Reference in New Issue
Block a user