diff --git a/server/handlers/movie.go b/server/handlers/movie.go index c8aa7be1..135c3abe 100644 --- a/server/handlers/movie.go +++ b/server/handlers/movie.go @@ -600,7 +600,7 @@ func ProxyMovie(ctx *gin.Context) { // TODO: cache mpd file fallthrough default: - err = proxy.AuthProxyURL(ctx, m.Movie.MovieBase.Url, m.Movie.MovieBase.Type, m.Movie.MovieBase.Headers, ctx.GetString("token"), room.ID, m.ID) + err = proxy.AutoProxyURL(ctx, m.Movie.MovieBase.Url, m.Movie.MovieBase.Type, m.Movie.MovieBase.Headers, ctx.GetString("token"), room.ID, m.ID) if err != nil { log.Errorf("proxy movie error: %v", err) return @@ -785,7 +785,10 @@ func JoinHlsLive(ctx *gin.Context) { } if utils.IsM3u8Url(m.Movie.MovieBase.Url) { - _ = proxy.ProxyM3u8(ctx, m.Movie.MovieBase.Url, m.Movie.MovieBase.Headers, true, ctx.GetString("token"), room.ID, m.ID) + err = proxy.ProxyM3u8(ctx, m.Movie.MovieBase.Url, m.Movie.MovieBase.Headers, true, ctx.GetString("token"), room.ID, m.ID) + if err != nil { + log.Errorf("proxy m3u8 hls live error: %v", err) + } return } channel, err := m.Channel() diff --git a/server/handlers/proxy/m3u8.go b/server/handlers/proxy/m3u8.go index c47b18fa..3c9351e3 100644 --- a/server/handlers/proxy/m3u8.go +++ b/server/handlers/proxy/m3u8.go @@ -10,6 +10,7 @@ import ( "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" "github.com/synctv-org/synctv/internal/conf" + "github.com/synctv-org/synctv/server/model" "github.com/synctv-org/synctv/utils" "github.com/synctv-org/synctv/utils/m3u8" "github.com/zijiren233/go-uhc" @@ -52,6 +53,8 @@ func NewM3u8TargetToken(targetUrl, roomId, movieId string, isM3u8File bool) (str return jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(stream.StringToBytes(conf.Conf.Jwt.Secret)) } +const maxM3u8FileSize = 3 * 1024 * 1024 // + func ProxyM3u8(ctx *gin.Context, u string, headers map[string]string, isM3u8File bool, token, roomId, movieId string) error { if !isM3u8File { return ProxyURL(ctx, u, headers) @@ -59,6 +62,11 @@ func ProxyM3u8(ctx *gin.Context, u string, headers map[string]string, isM3u8File req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil) if err != nil { + ctx.AbortWithStatusJSON(http.StatusBadRequest, + model.NewApiErrorStringResp( + fmt.Sprintf("new request error: %v", err), + ), + ) return fmt.Errorf("new request error: %w", err) } for k, v := range headers { @@ -69,11 +77,32 @@ func ProxyM3u8(ctx *gin.Context, u string, headers map[string]string, isM3u8File } resp, err := uhc.Do(req) if err != nil { + ctx.AbortWithStatusJSON(http.StatusBadRequest, + model.NewApiErrorStringResp( + fmt.Sprintf("do request error: %v", err), + ), + ) return fmt.Errorf("do request error: %w", err) } defer resp.Body.Close() - b, err := io.ReadAll(resp.Body) + // if contentType := resp.Header.Get("Content-Type"); !strings.HasPrefix(contentType, "application/vnd.apple.mpegurl") { + // return fmt.Errorf("m3u8 file is not a valid m3u8 file, content type: %s", contentType) + // } + if resp.ContentLength > maxM3u8FileSize { + ctx.AbortWithStatusJSON(http.StatusBadRequest, + model.NewApiErrorStringResp( + fmt.Sprintf("m3u8 file is too large: %d, max: %d (3MB)", resp.ContentLength, maxM3u8FileSize), + ), + ) + return fmt.Errorf("m3u8 file is too large: %d, max: %d (3MB)", resp.ContentLength, maxM3u8FileSize) + } + b, err := io.ReadAll(io.LimitReader(resp.Body, maxM3u8FileSize)) if err != nil { + ctx.AbortWithStatusJSON(http.StatusBadRequest, + model.NewApiErrorStringResp( + fmt.Sprintf("read response body error: %v", err), + ), + ) return fmt.Errorf("read response body error: %w", err) } hasM3u8File := false @@ -85,6 +114,11 @@ func ProxyM3u8(ctx *gin.Context, u string, headers map[string]string, isM3u8File return true, nil }) if err != nil { + ctx.AbortWithStatusJSON(http.StatusBadRequest, + model.NewApiErrorStringResp( + fmt.Sprintf("range m3u8 segments with base url error: %v", err), + ), + ) return fmt.Errorf("range m3u8 segments with base url error: %w", err) } m3u8Str, err := m3u8.ReplaceM3u8SegmentsWithBaseUrl(stream.BytesToString(b), u, func(segmentUrl string) (string, error) { @@ -95,6 +129,11 @@ func ProxyM3u8(ctx *gin.Context, u string, headers map[string]string, isM3u8File return fmt.Sprintf("/api/room/movie/proxy/%s/m3u8/%s?token=%s&roomId=%s", movieId, targetToken, token, roomId), nil }) if err != nil { + ctx.AbortWithStatusJSON(http.StatusBadRequest, + model.NewApiErrorStringResp( + fmt.Sprintf("replace m3u8 segments with base url error: %v", err), + ), + ) return fmt.Errorf("replace m3u8 segments with base url error: %w", err) } ctx.Data(http.StatusOK, hls.M3U8ContentType, stream.StringToBytes(m3u8Str)) diff --git a/server/handlers/proxy/proxy.go b/server/handlers/proxy/proxy.go index 952a88a0..f57bb077 100644 --- a/server/handlers/proxy/proxy.go +++ b/server/handlers/proxy/proxy.go @@ -10,6 +10,7 @@ import ( "github.com/gin-gonic/gin" "github.com/synctv-org/synctv/internal/settings" + "github.com/synctv-org/synctv/server/model" "github.com/synctv-org/synctv/utils" "github.com/zijiren233/go-uhc" ) @@ -17,8 +18,18 @@ import ( func ProxyURL(ctx *gin.Context, u string, headers map[string]string) error { if !settings.AllowProxyToLocal.Get() { if l, err := utils.ParseURLIsLocalIP(u); err != nil { + ctx.AbortWithStatusJSON(http.StatusBadRequest, + model.NewApiErrorStringResp( + fmt.Sprintf("check url is local ip error: %v", err), + ), + ) return fmt.Errorf("check url is local ip error: %w", err) } else if l { + ctx.AbortWithStatusJSON(http.StatusBadRequest, + model.NewApiErrorStringResp( + "not allow proxy to local", + ), + ) return errors.New("not allow proxy to local") } } @@ -26,6 +37,11 @@ func ProxyURL(ctx *gin.Context, u string, headers map[string]string) error { defer cf() req, err := http.NewRequestWithContext(ctx2, http.MethodGet, u, nil) if err != nil { + ctx.AbortWithStatusJSON(http.StatusBadRequest, + model.NewApiErrorStringResp( + fmt.Sprintf("new request error: %v", err), + ), + ) return fmt.Errorf("new request error: %w", err) } for k, v := range headers { @@ -61,6 +77,11 @@ func ProxyURL(ctx *gin.Context, u string, headers map[string]string) error { } resp, err := cli.Do(req) if err != nil { + ctx.AbortWithStatusJSON(http.StatusBadRequest, + model.NewApiErrorStringResp( + fmt.Sprintf("request url error: %v", err), + ), + ) return fmt.Errorf("request url error: %w", err) } defer resp.Body.Close() @@ -72,12 +93,17 @@ func ProxyURL(ctx *gin.Context, u string, headers map[string]string) error { ctx.Header("Content-Type", resp.Header.Get("Content-Type")) _, err = copyBuffer(ctx.Writer, resp.Body) if err != nil && err != io.EOF { + ctx.AbortWithStatusJSON(http.StatusBadRequest, + model.NewApiErrorStringResp( + fmt.Sprintf("copy response body error: %v", err), + ), + ) return fmt.Errorf("copy response body error: %w", err) } return nil } -func AuthProxyURL(ctx *gin.Context, u, t string, headers map[string]string, token, roomId, movieId string) error { +func AutoProxyURL(ctx *gin.Context, u, t string, headers map[string]string, token, roomId, movieId string) error { if strings.HasPrefix(t, "m3u") || utils.IsM3u8Url(u) { return ProxyM3u8(ctx, u, headers, true, token, roomId, movieId) } diff --git a/server/handlers/vendors/vendorAlist/alist.go b/server/handlers/vendors/vendorAlist/alist.go index 2aaddcba..9e2bcf5b 100644 --- a/server/handlers/vendors/vendorAlist/alist.go +++ b/server/handlers/vendors/vendorAlist/alist.go @@ -134,7 +134,7 @@ func (s *alistVendorService) ProxyMovie(ctx *gin.Context) { ctx.Data(http.StatusOK, "audio/mpegurl", data.Ali.M3U8ListFile) return case "raw": - err := proxy.AuthProxyURL(ctx, data.URL, s.movie.MovieBase.Type, nil, ctx.GetString("token"), s.movie.RoomID, s.movie.ID) + err := proxy.AutoProxyURL(ctx, data.URL, s.movie.MovieBase.Type, nil, ctx.GetString("token"), s.movie.RoomID, s.movie.ID) if err != nil { log.Errorf("proxy vendor movie error: %v", err) } @@ -173,7 +173,7 @@ func (s *alistVendorService) ProxyMovie(ctx *gin.Context) { ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("proxy is not enabled")) return } - err = proxy.AuthProxyURL(ctx, data.URL, s.movie.MovieBase.Type, nil, ctx.GetString("token"), s.movie.RoomID, s.movie.ID) + err = proxy.AutoProxyURL(ctx, data.URL, s.movie.MovieBase.Type, nil, ctx.GetString("token"), s.movie.RoomID, s.movie.ID) if err != nil { log.Errorf("proxy vendor movie error: %v", err) } diff --git a/server/handlers/vendors/vendorEmby/emby.go b/server/handlers/vendors/vendorEmby/emby.go index b3c35064..9db698d9 100644 --- a/server/handlers/vendors/vendorEmby/emby.go +++ b/server/handlers/vendors/vendorEmby/emby.go @@ -151,7 +151,7 @@ func (s *embyVendorService) ProxyMovie(ctx *gin.Context) { ctx.Redirect(http.StatusFound, embyC.Sources[source].URL) return } - err = proxy.AuthProxyURL(ctx, embyC.Sources[source].URL, "", nil, ctx.GetString("token"), s.movie.RoomID, s.movie.ID) + err = proxy.AutoProxyURL(ctx, embyC.Sources[source].URL, "", nil, ctx.GetString("token"), s.movie.RoomID, s.movie.ID) if err != nil { log.Errorf("proxy vendor movie error: %v", err) }