2023-03-23 20:42:01 +08:00
|
|
|
package updater
|
|
|
|
|
|
|
|
import (
|
2024-05-17 11:49:09 +08:00
|
|
|
"context"
|
2023-03-23 20:42:01 +08:00
|
|
|
"fmt"
|
|
|
|
"io"
|
2024-05-17 11:49:09 +08:00
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
mihomoHttp "github.com/metacubex/mihomo/component/http"
|
2023-09-21 08:25:26 +08:00
|
|
|
|
|
|
|
"golang.org/x/exp/constraints"
|
2023-03-23 20:42:01 +08:00
|
|
|
)
|
|
|
|
|
2024-09-22 13:57:57 +08:00
|
|
|
const defaultHttpTimeout = time.Second * 90
|
|
|
|
|
2024-05-17 11:49:09 +08:00
|
|
|
func downloadForBytes(url string) ([]byte, error) {
|
2024-09-22 13:57:57 +08:00
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), defaultHttpTimeout)
|
2024-05-17 11:49:09 +08:00
|
|
|
defer cancel()
|
2024-09-09 16:08:48 +08:00
|
|
|
resp, err := mihomoHttp.HttpRequest(ctx, url, http.MethodGet, nil, nil)
|
2024-05-17 11:49:09 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
return io.ReadAll(resp.Body)
|
|
|
|
}
|
|
|
|
|
|
|
|
func saveFile(bytes []byte, path string) error {
|
|
|
|
return os.WriteFile(path, bytes, 0o644)
|
|
|
|
}
|
|
|
|
|
2023-03-23 20:42:01 +08:00
|
|
|
// LimitReachedError records the limit and the operation that caused it.
|
|
|
|
type LimitReachedError struct {
|
|
|
|
Limit int64
|
|
|
|
}
|
|
|
|
|
|
|
|
// Error implements the [error] interface for *LimitReachedError.
|
|
|
|
//
|
|
|
|
// TODO(a.garipov): Think about error string format.
|
|
|
|
func (lre *LimitReachedError) Error() string {
|
|
|
|
return fmt.Sprintf("attempted to read more than %d bytes", lre.Limit)
|
|
|
|
}
|
|
|
|
|
|
|
|
// limitedReader is a wrapper for [io.Reader] limiting the input and dealing
|
|
|
|
// with errors package.
|
|
|
|
type limitedReader struct {
|
|
|
|
r io.Reader
|
|
|
|
limit int64
|
|
|
|
n int64
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read implements the [io.Reader] interface.
|
|
|
|
func (lr *limitedReader) Read(p []byte) (n int, err error) {
|
|
|
|
if lr.n == 0 {
|
|
|
|
return 0, &LimitReachedError{
|
|
|
|
Limit: lr.limit,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-21 08:25:26 +08:00
|
|
|
p = p[:Min(lr.n, int64(len(p)))]
|
2023-03-23 20:42:01 +08:00
|
|
|
|
|
|
|
n, err = lr.r.Read(p)
|
|
|
|
lr.n -= int64(n)
|
|
|
|
|
|
|
|
return n, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// LimitReader wraps Reader to make it's Reader stop with ErrLimitReached after
|
|
|
|
// n bytes read.
|
|
|
|
func LimitReader(r io.Reader, n int64) (limited io.Reader, err error) {
|
|
|
|
if n < 0 {
|
|
|
|
return nil, &updateError{Message: "limit must be non-negative"}
|
|
|
|
}
|
|
|
|
|
|
|
|
return &limitedReader{
|
|
|
|
r: r,
|
|
|
|
limit: n,
|
|
|
|
n: n,
|
|
|
|
}, nil
|
|
|
|
}
|
2023-09-21 08:25:26 +08:00
|
|
|
|
|
|
|
// Min returns the smaller of x or y.
|
|
|
|
func Min[T constraints.Integer | ~string](x, y T) (res T) {
|
|
|
|
if x < y {
|
|
|
|
return x
|
|
|
|
}
|
|
|
|
|
|
|
|
return y
|
|
|
|
}
|