mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-07-28 23:08:59 +02:00
fix(network): harden audit findings in transport, uTLS pool, and cover cache
- sharedTransport ResponseHeaderTimeout 45s -> 120s: downloads ride this transport and some providers prepare files server-side before the first byte; the stall watchdog still bounds dead transfers - uTLS pool: a request that fails on a pooled (possibly silently dead) connection now re-dials once when the body is repeatable, matching the old dial-per-request reliability - cover singleflight completes via defer with a default error so a panicking fetch can never strand waiters in (nil, nil) or leave a permanent in-flight entry
This commit is contained in:
+10
-6
@@ -108,21 +108,25 @@ func fetchCoverCached(downloadURL string) ([]byte, error) {
|
||||
return call.data, call.err
|
||||
}
|
||||
call := &coverInflightCall{}
|
||||
// Default error so a panicking fetch never strands waiters with a
|
||||
// (nil, nil) "success"; overwritten on normal completion.
|
||||
call.err = fmt.Errorf("cover fetch aborted")
|
||||
call.wg.Add(1)
|
||||
coverInflight[downloadURL] = call
|
||||
coverMu.Unlock()
|
||||
|
||||
defer func() {
|
||||
call.wg.Done()
|
||||
coverMu.Lock()
|
||||
delete(coverInflight, downloadURL)
|
||||
coverMu.Unlock()
|
||||
}()
|
||||
|
||||
data, err := coverFetch(downloadURL)
|
||||
call.data, call.err = data, err
|
||||
if err == nil {
|
||||
coverCachePut(downloadURL, data)
|
||||
}
|
||||
call.wg.Done()
|
||||
|
||||
coverMu.Lock()
|
||||
delete(coverInflight, downloadURL)
|
||||
coverMu.Unlock()
|
||||
|
||||
return data, err
|
||||
}
|
||||
|
||||
|
||||
+10
-6
@@ -68,12 +68,16 @@ var sharedTransport = &http.Transport{
|
||||
Timeout: 10 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).DialContext,
|
||||
MaxIdleConns: 100,
|
||||
MaxIdleConnsPerHost: 10,
|
||||
MaxConnsPerHost: 20,
|
||||
IdleConnTimeout: 60 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ResponseHeaderTimeout: 45 * time.Second,
|
||||
MaxIdleConns: 100,
|
||||
MaxIdleConnsPerHost: 10,
|
||||
MaxConnsPerHost: 20,
|
||||
IdleConnTimeout: 60 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
// Downloads ride this transport; some extension providers prepare the
|
||||
// file server-side before the first byte, so give TTFB more headroom
|
||||
// than the API/metadata transports. The 60s stall watchdog still bounds
|
||||
// dead transfers.
|
||||
ResponseHeaderTimeout: 120 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
DisableKeepAlives: false,
|
||||
ForceAttemptHTTP2: true,
|
||||
|
||||
@@ -49,10 +49,19 @@ func (t *utlsTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
|
||||
if cc := t.cachedConn(addr); cc != nil {
|
||||
resp, err := cc.RoundTrip(req)
|
||||
if err != nil {
|
||||
t.invalidate(addr, cc)
|
||||
if err == nil {
|
||||
return resp, nil
|
||||
}
|
||||
return resp, err
|
||||
// A pooled conn can be silently dead after a network switch. Drop it
|
||||
// and, when the request is safely repeatable, fall through to a fresh
|
||||
// dial instead of failing where the old dial-per-request code would
|
||||
// have succeeded.
|
||||
t.invalidate(addr, cc)
|
||||
retryReq, ok := rewindRequestBody(req)
|
||||
if !ok {
|
||||
return nil, err
|
||||
}
|
||||
req = retryReq
|
||||
}
|
||||
|
||||
tlsConn, proto, err := t.dial(req.Context(), host, addr)
|
||||
@@ -80,6 +89,25 @@ func (t *utlsTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
return cc.RoundTrip(req)
|
||||
}
|
||||
|
||||
// rewindRequestBody returns a request whose body can be sent again after a
|
||||
// failed attempt on a pooled connection: bodyless requests as-is, requests
|
||||
// with GetBody with a rebuilt body, anything else not ok.
|
||||
func rewindRequestBody(req *http.Request) (*http.Request, bool) {
|
||||
if req.Body == nil {
|
||||
return req, true
|
||||
}
|
||||
if req.GetBody == nil {
|
||||
return nil, false
|
||||
}
|
||||
body, err := req.GetBody()
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
retryReq := req.Clone(req.Context())
|
||||
retryReq.Body = body
|
||||
return retryReq, true
|
||||
}
|
||||
|
||||
// dial opens a TCP connection and completes the Chrome-fingerprint TLS handshake,
|
||||
// returning the connection and negotiated ALPN protocol.
|
||||
func (t *utlsTransport) dial(ctx context.Context, host, addr string) (*utls.UConn, string, error) {
|
||||
|
||||
Reference in New Issue
Block a user