|
package net |
|
|
|
import ( |
|
"context" |
|
"fmt" |
|
"io" |
|
"mime" |
|
"mime/multipart" |
|
"net/http" |
|
"path/filepath" |
|
"strconv" |
|
"strings" |
|
"sync" |
|
"time" |
|
|
|
"github.com/alist-org/alist/v3/drivers/base" |
|
"github.com/alist-org/alist/v3/internal/conf" |
|
"github.com/alist-org/alist/v3/internal/model" |
|
"github.com/alist-org/alist/v3/pkg/http_range" |
|
"github.com/alist-org/alist/v3/pkg/utils" |
|
"github.com/pkg/errors" |
|
log "github.com/sirupsen/logrus" |
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func ServeHTTP(w http.ResponseWriter, r *http.Request, name string, modTime time.Time, size int64, RangeReaderFunc model.RangeReaderFunc) { |
|
setLastModified(w, modTime) |
|
done, rangeReq := checkPreconditions(w, r, modTime) |
|
if done { |
|
return |
|
} |
|
|
|
if size < 0 { |
|
|
|
|
|
http.Error(w, "negative content size not supported", http.StatusInternalServerError) |
|
return |
|
} |
|
|
|
code := http.StatusOK |
|
|
|
|
|
|
|
contentTypes, haveType := w.Header()["Content-Type"] |
|
var contentType string |
|
if !haveType { |
|
contentType = mime.TypeByExtension(filepath.Ext(name)) |
|
if contentType == "" { |
|
|
|
contentType = "application/octet-stream" |
|
} |
|
w.Header().Set("Content-Type", contentType) |
|
} else if len(contentTypes) > 0 { |
|
contentType = contentTypes[0] |
|
} |
|
|
|
|
|
sendSize := size |
|
var sendContent io.ReadCloser |
|
ranges, err := http_range.ParseRange(rangeReq, size) |
|
switch err { |
|
case nil: |
|
case http_range.ErrNoOverlap: |
|
if size == 0 { |
|
|
|
|
|
|
|
|
|
ranges = nil |
|
break |
|
} |
|
w.Header().Set("Content-Range", fmt.Sprintf("bytes */%d", size)) |
|
fallthrough |
|
default: |
|
http.Error(w, err.Error(), http.StatusRequestedRangeNotSatisfiable) |
|
return |
|
} |
|
|
|
if sumRangesSize(ranges) > size || size < 0 { |
|
|
|
|
|
ranges = nil |
|
} |
|
switch { |
|
case len(ranges) == 0: |
|
reader, err := RangeReaderFunc(context.Background(), http_range.Range{Length: -1}) |
|
if err != nil { |
|
http.Error(w, err.Error(), http.StatusInternalServerError) |
|
return |
|
} |
|
sendContent = reader |
|
case len(ranges) == 1: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ra := ranges[0] |
|
sendContent, err = RangeReaderFunc(context.Background(), ra) |
|
if err != nil { |
|
http.Error(w, err.Error(), http.StatusRequestedRangeNotSatisfiable) |
|
return |
|
} |
|
sendSize = ra.Length |
|
code = http.StatusPartialContent |
|
w.Header().Set("Content-Range", ra.ContentRange(size)) |
|
case len(ranges) > 1: |
|
sendSize, err = rangesMIMESize(ranges, contentType, size) |
|
if err != nil { |
|
http.Error(w, err.Error(), http.StatusRequestedRangeNotSatisfiable) |
|
} |
|
code = http.StatusPartialContent |
|
|
|
pr, pw := io.Pipe() |
|
mw := multipart.NewWriter(pw) |
|
w.Header().Set("Content-Type", "multipart/byteranges; boundary="+mw.Boundary()) |
|
sendContent = pr |
|
defer pr.Close() |
|
go func() { |
|
for _, ra := range ranges { |
|
part, err := mw.CreatePart(ra.MimeHeader(contentType, size)) |
|
if err != nil { |
|
pw.CloseWithError(err) |
|
return |
|
} |
|
reader, err := RangeReaderFunc(context.Background(), ra) |
|
if err != nil { |
|
pw.CloseWithError(err) |
|
return |
|
} |
|
if _, err := io.CopyN(part, reader, ra.Length); err != nil { |
|
pw.CloseWithError(err) |
|
return |
|
} |
|
|
|
} |
|
|
|
mw.Close() |
|
pw.Close() |
|
}() |
|
} |
|
|
|
w.Header().Set("Accept-Ranges", "bytes") |
|
if w.Header().Get("Content-Encoding") == "" { |
|
w.Header().Set("Content-Length", strconv.FormatInt(sendSize, 10)) |
|
} |
|
|
|
w.WriteHeader(code) |
|
|
|
if r.Method != "HEAD" { |
|
written, err := io.CopyN(w, sendContent, sendSize) |
|
if err != nil { |
|
log.Warnf("ServeHttp error. err: %s ", err) |
|
if written != sendSize { |
|
log.Warnf("Maybe size incorrect or reader not giving correct/full data, or connection closed before finish. written bytes: %d ,sendSize:%d, ", written, sendSize) |
|
} |
|
http.Error(w, err.Error(), http.StatusInternalServerError) |
|
} |
|
} |
|
|
|
} |
|
func ProcessHeader(origin, override http.Header) http.Header { |
|
result := http.Header{} |
|
|
|
for h, val := range origin { |
|
if utils.SliceContains(conf.SlicesMap[conf.ProxyIgnoreHeaders], strings.ToLower(h)) { |
|
continue |
|
} |
|
result[h] = val |
|
} |
|
|
|
for h, val := range override { |
|
result[h] = val |
|
} |
|
return result |
|
} |
|
|
|
|
|
func RequestHttp(ctx context.Context, httpMethod string, headerOverride http.Header, URL string) (*http.Response, error) { |
|
req, err := http.NewRequestWithContext(ctx, httpMethod, URL, nil) |
|
if err != nil { |
|
return nil, err |
|
} |
|
req.Header = headerOverride |
|
res, err := HttpClient().Do(req) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
res.Header.Del("set-cookie") |
|
if res.StatusCode >= 400 { |
|
all, _ := io.ReadAll(res.Body) |
|
_ = res.Body.Close() |
|
msg := string(all) |
|
log.Debugln(msg) |
|
return nil, fmt.Errorf("http request [%s] failure,status: %d response:%s", URL, res.StatusCode, msg) |
|
} |
|
return res, nil |
|
} |
|
|
|
var once sync.Once |
|
var httpClient *http.Client |
|
|
|
func HttpClient() *http.Client { |
|
once.Do(func() { |
|
httpClient = base.NewHttpClient() |
|
httpClient.CheckRedirect = func(req *http.Request, via []*http.Request) error { |
|
if len(via) >= 10 { |
|
return errors.New("stopped after 10 redirects") |
|
} |
|
req.Header.Del("Referer") |
|
return nil |
|
} |
|
}) |
|
return httpClient |
|
} |
|
|