/* * Iptv-Proxy is a project to proxyfie an m3u file and to proxyfie an Xtream iptv service (client API). * Copyright (C) 2020 Pierre-Emmanuel Jacquier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package server import ( "bytes" "errors" "fmt" "io" "io/ioutil" "log" "net/http" "net/url" "strings" "time" "github.com/gin-gonic/gin" ) func (c *Config) getM3U(ctx *gin.Context) { ctx.Header("Content-Disposition", fmt.Sprintf(`attachment; filename=%q`, c.M3UFileName)) ctx.Header("Content-Type", "application/octet-stream") ctx.File(c.proxyfiedM3UPath) } func (c *Config) reverseProxy(ctx *gin.Context) { rpURL, err := url.Parse(c.track.URI) if err != nil { ctx.AbortWithError(http.StatusInternalServerError, err) // nolint: errcheck return } c.stream(ctx, rpURL) } func (c *Config) stream(ctx *gin.Context, oriURL *url.URL) { id := ctx.Param("id") if strings.HasSuffix(id, ".m3u8") { c.hlsStream(ctx, oriURL) return } client := &http.Client{} req, err := http.NewRequest("GET", oriURL.String(), nil) if err != nil { ctx.AbortWithError(http.StatusInternalServerError, err) // nolint: errcheck return } req.Header.Set("User-Agent", ctx.Request.UserAgent()) resp, err := client.Do(req) if err != nil { ctx.AbortWithError(http.StatusInternalServerError, err) // nolint: errcheck return } defer resp.Body.Close() copyHTTPHeader(ctx, resp.Header) ctx.Status(resp.StatusCode) ctx.Stream(func(w io.Writer) bool { io.Copy(w, resp.Body) // nolint: errcheck return false }) } func (c *Config) hlsStream(ctx *gin.Context, oriURL *url.URL) { client := &http.Client{ CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }, } req, err := http.NewRequest("GET", oriURL.String(), nil) if err != nil { ctx.AbortWithError(http.StatusInternalServerError, err) // nolint: errcheck return } req.Header.Set("User-Agent", ctx.Request.UserAgent()) resp, err := client.Do(req) if err != nil { ctx.AbortWithError(http.StatusInternalServerError, err) // nolint: errcheck return } defer resp.Body.Close() if resp.StatusCode == http.StatusFound { location, err := resp.Location() if err != nil { ctx.AbortWithError(http.StatusInternalServerError, err) // nolint: errcheck return } id := ctx.Param("id") if strings.Contains(location.String(), id) { hlsChannelsRedirectURLLock.Lock() hlsChannelsRedirectURL[id] = *location hlsChannelsRedirectURLLock.Unlock() hlsReq, err := http.NewRequest("GET", location.String(), nil) if err != nil { ctx.AbortWithError(http.StatusInternalServerError, err) // nolint: errcheck return } hlsReq.Header.Set("User-Agent", ctx.Request.UserAgent()) hlsResp, err := client.Do(hlsReq) if err != nil { ctx.AbortWithError(http.StatusInternalServerError, err) // nolint: errcheck return } defer hlsResp.Body.Close() b, err := ioutil.ReadAll(hlsResp.Body) if err != nil { ctx.AbortWithError(http.StatusInternalServerError, err) // nolint: errcheck return } body := string(b) body = strings.ReplaceAll(body, "/"+c.XtreamUser.String()+"/"+c.XtreamPassword.String()+"/", "/"+c.User.String()+"/"+c.Password.String()+"/") ctx.Data(http.StatusOK, hlsResp.Header.Get("Content-Type"), []byte(body)) return } ctx.AbortWithError(http.StatusInternalServerError, errors.New("Unable to HLS stream")) // nolint: errcheck return } ctx.Status(resp.StatusCode) } func copyHTTPHeader(ctx *gin.Context, header http.Header) { for k, v := range header { ctx.Header(k, strings.Join(v, ", ")) } } // authRequest handle auth credentials type authRequest struct { Username string `form:"username" binding:"required"` Password string `form:"password" binding:"required"` } func (c *Config) authenticate(ctx *gin.Context) { var authReq authRequest if err := ctx.Bind(&authReq); err != nil { ctx.AbortWithError(http.StatusBadRequest, err) // nolint: errcheck return } if c.ProxyConfig.User.String() != authReq.Username || c.ProxyConfig.Password.String() != authReq.Password { ctx.AbortWithStatus(http.StatusUnauthorized) } } func (c *Config) appAuthenticate(ctx *gin.Context) { contents, err := ioutil.ReadAll(ctx.Request.Body) if err != nil { ctx.AbortWithError(http.StatusInternalServerError, err) // nolint: errcheck return } q, err := url.ParseQuery(string(contents)) if err != nil { ctx.AbortWithError(http.StatusInternalServerError, err) // nolint: errcheck return } if len(q["username"]) == 0 || len(q["password"]) == 0 { ctx.AbortWithError(http.StatusBadRequest, fmt.Errorf("bad body url query parameters")) // nolint: errcheck return } log.Printf("[iptv-proxy] %v | %s |App Auth\n", time.Now().Format("2006/01/02 - 15:04:05"), ctx.ClientIP()) if c.ProxyConfig.User.String() != q["username"][0] || c.ProxyConfig.Password.String() != q["password"][0] { ctx.AbortWithStatus(http.StatusUnauthorized) } ctx.Request.Body = ioutil.NopCloser(bytes.NewReader(contents)) }