diff --git a/transcoder/main.go b/transcoder/main.go index e6fc54fb..152b0972 100644 --- a/transcoder/main.go +++ b/transcoder/main.go @@ -16,9 +16,9 @@ import ( // Retrieve the raw video stream, in the same container as the one on the server. No transcoding or // transmuxing is done. // -// Path: /direct +// Path: /:path/direct func DirectStream(c echo.Context) error { - path, err := GetPath(c) + path, _, err := GetPath(c) if err != nil { return err } @@ -31,18 +31,18 @@ func DirectStream(c echo.Context) error { // Note that the direct stream is missing (since the direct is not an hls stream) and // subtitles/fonts are not included to support more codecs than just webvtt. // -// Path: /master.m3u8 +// Path: /:path/master.m3u8 func (h *Handler) GetMaster(c echo.Context) error { client, err := GetClientId(c) if err != nil { return err } - path, err := GetPath(c) + path, sha, err := GetPath(c) if err != nil { return err } - ret, err := h.transcoder.GetMaster(path, client, GetRoute(c)) + ret, err := h.transcoder.GetMaster(path, client, sha) if err != nil { return err } @@ -55,7 +55,7 @@ func (h *Handler) GetMaster(c echo.Context) error { // This route can take a few seconds to respond since it will way for at least one segment to be // available. // -// Path: /:quality/index.m3u8 +// Path: /:path/:quality/index.m3u8 func (h *Handler) GetVideoIndex(c echo.Context) error { quality, err := src.QualityFromString(c.Param("quality")) if err != nil { @@ -65,12 +65,12 @@ func (h *Handler) GetVideoIndex(c echo.Context) error { if err != nil { return err } - path, err := GetPath(c) + path, sha, err := GetPath(c) if err != nil { return err } - ret, err := h.transcoder.GetVideoIndex(path, quality, client, GetRoute(c)) + ret, err := h.transcoder.GetVideoIndex(path, quality, client, sha) if err != nil { return err } @@ -83,7 +83,7 @@ func (h *Handler) GetVideoIndex(c echo.Context) error { // This route can take a few seconds to respond since it will way for at least one segment to be // available. // -// Path: /audio/:audio/index.m3u8 +// Path: /:path/audio/:audio/index.m3u8 func (h *Handler) GetAudioIndex(c echo.Context) error { audio, err := strconv.ParseInt(c.Param("audio"), 10, 32) if err != nil { @@ -93,12 +93,12 @@ func (h *Handler) GetAudioIndex(c echo.Context) error { if err != nil { return err } - path, err := GetPath(c) + path, sha, err := GetPath(c) if err != nil { return err } - ret, err := h.transcoder.GetAudioIndex(path, int32(audio), client, GetRoute(c)) + ret, err := h.transcoder.GetAudioIndex(path, int32(audio), client, sha) if err != nil { return err } @@ -109,7 +109,7 @@ func (h *Handler) GetAudioIndex(c echo.Context) error { // // Retrieve a chunk of a transmuxed video. // -// Path: /:quality/segments-:chunk.ts +// Path: /:path/:quality/segments-:chunk.ts func (h *Handler) GetVideoSegment(c echo.Context) error { quality, err := src.QualityFromString(c.Param("quality")) if err != nil { @@ -123,12 +123,12 @@ func (h *Handler) GetVideoSegment(c echo.Context) error { if err != nil { return err } - path, err := GetPath(c) + path, sha, err := GetPath(c) if err != nil { return err } - ret, err := h.transcoder.GetVideoSegment(path, quality, segment, client, GetRoute(c)) + ret, err := h.transcoder.GetVideoSegment(path, quality, segment, client, sha) if err != nil { return err } @@ -139,7 +139,7 @@ func (h *Handler) GetVideoSegment(c echo.Context) error { // // Retrieve a chunk of a transcoded audio. // -// Path: /audio/:audio/segments-:chunk.ts +// Path: /:path/audio/:audio/segments-:chunk.ts func (h *Handler) GetAudioSegment(c echo.Context) error { audio, err := strconv.ParseInt(c.Param("audio"), 10, 32) if err != nil { @@ -153,12 +153,12 @@ func (h *Handler) GetAudioSegment(c echo.Context) error { if err != nil { return err } - path, err := GetPath(c) + path, sha, err := GetPath(c) if err != nil { return err } - ret, err := h.transcoder.GetAudioSegment(path, int32(audio), segment, client, GetRoute(c)) + ret, err := h.transcoder.GetAudioSegment(path, int32(audio), segment, client, sha) if err != nil { return err } @@ -169,29 +169,20 @@ func (h *Handler) GetAudioSegment(c echo.Context) error { // // Identify metadata about a file. // -// Path: /info +// Path: /:path/info func (h *Handler) GetInfo(c echo.Context) error { - path, err := GetPath(c) + path, sha, err := GetPath(c) if err != nil { return err } - route := GetRoute(c) - sha, err := src.GetHash(path) - if err != nil { - return err - } - ret, err := src.GetInfo(path, sha, route) + ret, err := src.GetInfo(path, sha) if err != nil { return err } // Run extractors to have them in cache - src.Extract(ret.Path, sha, route) - go src.ExtractThumbnail( - ret.Path, - route, - sha, - ) + src.Extract(ret.Path, sha) + go src.ExtractThumbnail(ret.Path, sha) return c.JSON(http.StatusOK, ret) } @@ -199,9 +190,9 @@ func (h *Handler) GetInfo(c echo.Context) error { // // Get a specific attachment. // -// Path: /attachment/:name +// Path: /:path/attachment/:name func (h *Handler) GetAttachment(c echo.Context) error { - path, err := GetPath(c) + path, sha, err := GetPath(c) if err != nil { return err } @@ -210,12 +201,7 @@ func (h *Handler) GetAttachment(c echo.Context) error { return err } - route := GetRoute(c) - sha, err := src.GetHash(path) - if err != nil { - return err - } - wait, err := src.Extract(path, sha, route) + wait, err := src.Extract(path, sha) if err != nil { return err } @@ -229,9 +215,9 @@ func (h *Handler) GetAttachment(c echo.Context) error { // // Get a specific subtitle. // -// Path: /subtitle/:name +// Path: /:path/subtitle/:name func (h *Handler) GetSubtitle(c echo.Context) error { - path, err := GetPath(c) + path, sha, err := GetPath(c) if err != nil { return err } @@ -240,12 +226,7 @@ func (h *Handler) GetSubtitle(c echo.Context) error { return err } - route := GetRoute(c) - sha, err := src.GetHash(path) - if err != nil { - return err - } - wait, err := src.Extract(path, sha, route) + wait, err := src.Extract(path, sha) if err != nil { return err } @@ -259,22 +240,14 @@ func (h *Handler) GetSubtitle(c echo.Context) error { // // Get a sprite file containing all the thumbnails of the show. // -// Path: /thumbnails.png +// Path: /:path/thumbnails.png func (h *Handler) GetThumbnails(c echo.Context) error { - path, err := GetPath(c) - if err != nil { - return err - } - sha, err := src.GetHash(path) + path, sha, err := GetPath(c) if err != nil { return err } - out, err := src.ExtractThumbnail( - path, - GetRoute(c), - sha, - ) + out, err := src.ExtractThumbnail(path, sha) if err != nil { return err } @@ -287,22 +260,14 @@ func (h *Handler) GetThumbnails(c echo.Context) error { // Get a vtt file containing timing/position of thumbnails inside the sprite file. // https://developer.bitmovin.com/playback/docs/webvtt-based-thumbnails for more info. // -// Path: /:resource/:slug/thumbnails.vtt +// Path: /:path/:resource/:slug/thumbnails.vtt func (h *Handler) GetThumbnailsVtt(c echo.Context) error { - path, err := GetPath(c) - if err != nil { - return err - } - sha, err := src.GetHash(path) + path, sha, err := GetPath(c) if err != nil { return err } - out, err := src.ExtractThumbnail( - path, - GetRoute(c), - sha, - ) + out, err := src.ExtractThumbnail(path, sha) if err != nil { return err } @@ -328,17 +293,17 @@ func main() { transcoder: transcoder, } - e.GET("/direct", DirectStream) - e.GET("/master.m3u8", h.GetMaster) - e.GET("/:quality/index.m3u8", h.GetVideoIndex) - e.GET("/audio/:audio/index.m3u8", h.GetAudioIndex) - e.GET("/:quality/:chunk", h.GetVideoSegment) - e.GET("/audio/:audio/:chunk", h.GetAudioSegment) - e.GET("/info", h.GetInfo) - e.GET("/thumbnails.png", h.GetThumbnails) - e.GET("/thumbnails.vtt", h.GetThumbnailsVtt) - e.GET("/attachment/:name", h.GetAttachment) - e.GET("/subtitle/:name", h.GetSubtitle) + e.GET("/:path/direct", DirectStream) + e.GET("/:path/master.m3u8", h.GetMaster) + e.GET("/:path/:quality/index.m3u8", h.GetVideoIndex) + e.GET("/:path/audio/:audio/index.m3u8", h.GetAudioIndex) + e.GET("/:path/:quality/:chunk", h.GetVideoSegment) + e.GET("/:path/audio/:audio/:chunk", h.GetAudioSegment) + e.GET("/:path/info", h.GetInfo) + e.GET("/:path/thumbnails.png", h.GetThumbnails) + e.GET("/:path/thumbnails.vtt", h.GetThumbnailsVtt) + e.GET("/:path/attachment/:name", h.GetAttachment) + e.GET("/:path/subtitle/:name", h.GetSubtitle) e.Logger.Fatal(e.Start(":7666")) } diff --git a/transcoder/src/transcoder.go b/transcoder/src/transcoder.go index 53cec7b1..1dffecb6 100644 --- a/transcoder/src/transcoder.go +++ b/transcoder/src/transcoder.go @@ -33,14 +33,10 @@ func NewTranscoder() (*Transcoder, error) { return ret, nil } -func (t *Transcoder) getFileStream(path string, route string) (*FileStream, error) { +func (t *Transcoder) getFileStream(path string, sha string) (*FileStream, error) { var err error ret, _ := t.streams.GetOrCreate(path, func() *FileStream { - sha, err := GetHash(path) - if err != nil { - return nil - } - return NewFileStream(path, sha, route) + return NewFileStream(path, sha) }) ret.ready.Wait() if err != nil || ret.err != nil { @@ -50,8 +46,8 @@ func (t *Transcoder) getFileStream(path string, route string) (*FileStream, erro return ret, nil } -func (t *Transcoder) GetMaster(path string, client string, route string) (string, error) { - stream, err := t.getFileStream(path, route) +func (t *Transcoder) GetMaster(path string, client string, sha string) (string, error) { + stream, err := t.getFileStream(path, sha) if err != nil { return "", err } @@ -69,9 +65,9 @@ func (t *Transcoder) GetVideoIndex( path string, quality Quality, client string, - route string, + sha string, ) (string, error) { - stream, err := t.getFileStream(path, route) + stream, err := t.getFileStream(path, sha) if err != nil { return "", err } @@ -89,9 +85,9 @@ func (t *Transcoder) GetAudioIndex( path string, audio int32, client string, - route string, + sha string, ) (string, error) { - stream, err := t.getFileStream(path, route) + stream, err := t.getFileStream(path, sha) if err != nil { return "", err } @@ -109,9 +105,9 @@ func (t *Transcoder) GetVideoSegment( quality Quality, segment int32, client string, - route string, + sha string, ) (string, error) { - stream, err := t.getFileStream(path, route) + stream, err := t.getFileStream(path, sha) if err != nil { return "", err } @@ -130,9 +126,9 @@ func (t *Transcoder) GetAudioSegment( audio int32, segment int32, client string, - route string, + sha string, ) (string, error) { - stream, err := t.getFileStream(path, route) + stream, err := t.getFileStream(path, sha) if err != nil { return "", err } diff --git a/transcoder/src/utils.go b/transcoder/src/utils.go index 9fd84e8b..5277def3 100644 --- a/transcoder/src/utils.go +++ b/transcoder/src/utils.go @@ -1,11 +1,8 @@ package src import ( - "crypto/sha1" - "encoding/hex" "fmt" "log" - "os" "time" ) @@ -18,15 +15,3 @@ func printExecTime(message string, args ...any) func() { log.Printf("%s finished in %s", msg, time.Since(start)) } } - -func GetHash(path string) (string, error) { - info, err := os.Stat(path) - if err != nil { - return "", err - } - h := sha1.New() - h.Write([]byte(path)) - h.Write([]byte(info.ModTime().String())) - sha := hex.EncodeToString(h.Sum(nil)) - return sha, nil -} diff --git a/transcoder/utils.go b/transcoder/utils.go index 4eaf7d4e..fbf6c554 100644 --- a/transcoder/utils.go +++ b/transcoder/utils.go @@ -1,30 +1,55 @@ package main import ( + "crypto/sha1" + "encoding/base64" + "encoding/hex" "fmt" "net/http" + "os" + "path/filepath" "strings" - "time" "github.com/labstack/echo/v4" + "github.com/zoriya/kyoo/transcoder/src" ) -var client = &http.Client{Timeout: 10 * time.Second} +var safe_path = src.GetEnvOr("GOCODER_SAFE_PATH", "/video") -type Item struct { - Path string `json:"path"` -} - -func GetPath(c echo.Context) (string, error) { - key := c.Request().Header.Get("X-Path") +func GetPath(c echo.Context) (string, string, error) { + key := c.Param("path") if key == "" { - return "", echo.NewHTTPError(http.StatusBadRequest, "Missing resouce path. You need to set the X-Path header.") + return "", "", echo.NewHTTPError(http.StatusBadRequest, "Missing resouce path.") } - return key, nil + pathb, err := base64.StdEncoding.DecodeString(key) + if err != nil { + return "", "", echo.NewHTTPError(http.StatusBadRequest, "Invalid path. Should be base64 encoded.") + } + path := string(pathb) + if !filepath.IsAbs(path) { + return "", "", echo.NewHTTPError(http.StatusBadRequest, "Absolute path required.") + } + if !strings.HasPrefix(path, safe_path) { + return "", "", echo.NewHTTPError(http.StatusBadRequest, "Selected path is not marked as safe.") + } + hash, err := getHash(path) + if err != nil { + return "", "", echo.NewHTTPError(http.StatusNotFound, "File does not exist") + } + + return path, hash, nil } -func GetRoute(c echo.Context) string { - return c.Request().Header.Get("X-Route") +func getHash(path string) (string, error) { + info, err := os.Stat(path) + if err != nil { + return "", err + } + h := sha1.New() + h.Write([]byte(path)) + h.Write([]byte(info.ModTime().String())) + sha := hex.EncodeToString(h.Sum(nil)) + return sha, nil } func SanitizePath(path string) error {