Skip to content

Commit

Permalink
fix support for HTTP preflight requests (#1792)
Browse files Browse the repository at this point in the history
  • Loading branch information
aler9 committed Jul 6, 2024
1 parent 342c257 commit 3bda68c
Show file tree
Hide file tree
Showing 11 changed files with 273 additions and 10 deletions.
9 changes: 9 additions & 0 deletions internal/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,15 @@ func (a *API) writeError(ctx *gin.Context, status int, err error) {
func (a *API) middlewareOrigin(ctx *gin.Context) {
ctx.Writer.Header().Set("Access-Control-Allow-Origin", a.AllowOrigin)
ctx.Writer.Header().Set("Access-Control-Allow-Credentials", "true")

// preflight requests
if ctx.Request.Method == http.MethodOptions &&
ctx.Request.Header.Get("Access-Control-Request-Method") != "" {
ctx.Writer.Header().Set("Access-Control-Allow-Methods", "OPTIONS, GET, POST, PATCH, DELETE")
ctx.Writer.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type")
ctx.AbortWithStatus(http.StatusNoContent)
return
}
}

func (a *API) middlewareAuth(ctx *gin.Context) {
Expand Down
37 changes: 37 additions & 0 deletions internal/api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,43 @@ func checkError(t *testing.T, msg string, body io.Reader) {
require.Equal(t, map[string]interface{}{"error": msg}, resErr)
}

func TestPreflightRequest(t *testing.T) {
api := API{
Address: "localhost:9997",
AllowOrigin: "*",
ReadTimeout: conf.StringDuration(10 * time.Second),
AuthManager: test.NilAuthManager,
Parent: &testParent{},
}
err := api.Initialize()
require.NoError(t, err)
defer api.Close()

tr := &http.Transport{}
defer tr.CloseIdleConnections()
hc := &http.Client{Transport: tr}

req, err := http.NewRequest(http.MethodOptions, "http://localhost:9997", nil)
require.NoError(t, err)

req.Header.Add("Access-Control-Request-Method", "GET")

res, err := hc.Do(req)
require.NoError(t, err)
defer res.Body.Close()

require.Equal(t, http.StatusNoContent, res.StatusCode)

byts, err := io.ReadAll(res.Body)
require.NoError(t, err)

require.Equal(t, "*", res.Header.Get("Access-Control-Allow-Origin"))
require.Equal(t, "true", res.Header.Get("Access-Control-Allow-Credentials"))
require.Equal(t, "OPTIONS, GET, POST, PATCH, DELETE", res.Header.Get("Access-Control-Allow-Methods"))
require.Equal(t, "Authorization, Content-Type", res.Header.Get("Access-Control-Allow-Headers"))
require.Equal(t, byts, []byte{})
}

func TestConfigAuth(t *testing.T) {
cnf := tempConf(t, "api: yes\n")

Expand Down
9 changes: 9 additions & 0 deletions internal/metrics/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,15 @@ func (m *Metrics) onRequest(ctx *gin.Context) {
ctx.Writer.Header().Set("Access-Control-Allow-Origin", m.AllowOrigin)
ctx.Writer.Header().Set("Access-Control-Allow-Credentials", "true")

// preflight requests
if ctx.Request.Method == http.MethodOptions &&
ctx.Request.Header.Get("Access-Control-Request-Method") != "" {
ctx.Writer.Header().Set("Access-Control-Allow-Methods", "OPTIONS, GET")
ctx.Writer.Header().Set("Access-Control-Allow-Headers", "Authorization")
ctx.Writer.WriteHeader(http.StatusNoContent)
return
}

if ctx.Request.URL.Path != "/metrics" || ctx.Request.Method != http.MethodGet {
return
}
Expand Down
49 changes: 49 additions & 0 deletions internal/metrics/metrics_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package metrics

import (
"io"
"net/http"
"testing"
"time"

"github.com/bluenviron/mediamtx/internal/conf"
"github.com/bluenviron/mediamtx/internal/test"
"github.com/stretchr/testify/require"
)

func TestPreflightRequest(t *testing.T) {
api := Metrics{
Address: "localhost:9998",
AllowOrigin: "*",
ReadTimeout: conf.StringDuration(10 * time.Second),
AuthManager: test.NilAuthManager,
Parent: test.NilLogger,
}
err := api.Initialize()
require.NoError(t, err)
defer api.Close()

tr := &http.Transport{}
defer tr.CloseIdleConnections()
hc := &http.Client{Transport: tr}

req, err := http.NewRequest(http.MethodOptions, "http://localhost:9998", nil)
require.NoError(t, err)

req.Header.Add("Access-Control-Request-Method", "GET")

res, err := hc.Do(req)
require.NoError(t, err)
defer res.Body.Close()

require.Equal(t, http.StatusNoContent, res.StatusCode)

byts, err := io.ReadAll(res.Body)
require.NoError(t, err)

require.Equal(t, "*", res.Header.Get("Access-Control-Allow-Origin"))
require.Equal(t, "true", res.Header.Get("Access-Control-Allow-Credentials"))
require.Equal(t, "OPTIONS, GET", res.Header.Get("Access-Control-Allow-Methods"))
require.Equal(t, "Authorization", res.Header.Get("Access-Control-Allow-Headers"))
require.Equal(t, byts, []byte{})
}
9 changes: 9 additions & 0 deletions internal/playback/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,15 @@ func (s *Server) safeFindPathConf(name string) (*conf.Path, error) {
func (s *Server) middlewareOrigin(ctx *gin.Context) {
ctx.Writer.Header().Set("Access-Control-Allow-Origin", s.AllowOrigin)
ctx.Writer.Header().Set("Access-Control-Allow-Credentials", "true")

// preflight requests
if ctx.Request.Method == http.MethodOptions &&
ctx.Request.Header.Get("Access-Control-Request-Method") != "" {
ctx.Writer.Header().Set("Access-Control-Allow-Methods", "OPTIONS, GET")
ctx.Writer.Header().Set("Access-Control-Allow-Headers", "Authorization")
ctx.AbortWithStatus(http.StatusNoContent)
return
}
}

func (s *Server) doAuth(ctx *gin.Context, pathName string) bool {
Expand Down
48 changes: 48 additions & 0 deletions internal/playback/server_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package playback

import (
"io"
"net/http"
"testing"
"time"

"github.com/bluenviron/mediamtx/internal/conf"
"github.com/bluenviron/mediamtx/internal/test"
"github.com/stretchr/testify/require"
)

func TestPreflightRequest(t *testing.T) {
s := &Server{
Address: "127.0.0.1:9996",
AllowOrigin: "*",
ReadTimeout: conf.StringDuration(10 * time.Second),
Parent: test.NilLogger,
}
err := s.Initialize()
require.NoError(t, err)
defer s.Close()

tr := &http.Transport{}
defer tr.CloseIdleConnections()
hc := &http.Client{Transport: tr}

req, err := http.NewRequest(http.MethodOptions, "http://localhost:9996", nil)
require.NoError(t, err)

req.Header.Add("Access-Control-Request-Method", "GET")

res, err := hc.Do(req)
require.NoError(t, err)
defer res.Body.Close()

require.Equal(t, http.StatusNoContent, res.StatusCode)

byts, err := io.ReadAll(res.Body)
require.NoError(t, err)

require.Equal(t, "*", res.Header.Get("Access-Control-Allow-Origin"))
require.Equal(t, "true", res.Header.Get("Access-Control-Allow-Credentials"))
require.Equal(t, "OPTIONS, GET", res.Header.Get("Access-Control-Allow-Methods"))
require.Equal(t, "Authorization", res.Header.Get("Access-Control-Allow-Headers"))
require.Equal(t, byts, []byte{})
}
9 changes: 9 additions & 0 deletions internal/pprof/pprof.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,15 @@ func (pp *PPROF) onRequest(ctx *gin.Context) {
ctx.Writer.Header().Set("Access-Control-Allow-Origin", pp.AllowOrigin)
ctx.Writer.Header().Set("Access-Control-Allow-Credentials", "true")

// preflight requests
if ctx.Request.Method == http.MethodOptions &&
ctx.Request.Header.Get("Access-Control-Request-Method") != "" {
ctx.Writer.Header().Set("Access-Control-Allow-Methods", "OPTIONS, GET")
ctx.Writer.Header().Set("Access-Control-Allow-Headers", "Authorization")
ctx.Writer.WriteHeader(http.StatusNoContent)
return
}

user, pass, hasCredentials := ctx.Request.BasicAuth()

err := pp.AuthManager.Authenticate(&auth.Request{
Expand Down
48 changes: 48 additions & 0 deletions internal/pprof/pprof_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package pprof

import (
"io"
"net/http"
"testing"
"time"

"github.com/bluenviron/mediamtx/internal/conf"
"github.com/bluenviron/mediamtx/internal/test"
"github.com/stretchr/testify/require"
)

func TestPreflightRequest(t *testing.T) {
s := &PPROF{
Address: "127.0.0.1:9999",
AllowOrigin: "*",
ReadTimeout: conf.StringDuration(10 * time.Second),
Parent: test.NilLogger,
}
err := s.Initialize()
require.NoError(t, err)
defer s.Close()

tr := &http.Transport{}
defer tr.CloseIdleConnections()
hc := &http.Client{Transport: tr}

req, err := http.NewRequest(http.MethodOptions, "http://localhost:9999", nil)
require.NoError(t, err)

req.Header.Add("Access-Control-Request-Method", "GET")

res, err := hc.Do(req)
require.NoError(t, err)
defer res.Body.Close()

require.Equal(t, http.StatusNoContent, res.StatusCode)

byts, err := io.ReadAll(res.Body)
require.NoError(t, err)

require.Equal(t, "*", res.Header.Get("Access-Control-Allow-Origin"))
require.Equal(t, "true", res.Header.Get("Access-Control-Allow-Credentials"))
require.Equal(t, "OPTIONS, GET", res.Header.Get("Access-Control-Allow-Methods"))
require.Equal(t, "Authorization", res.Header.Get("Access-Control-Allow-Headers"))
require.Equal(t, byts, []byte{})
}
8 changes: 5 additions & 3 deletions internal/servers/hls/http_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,11 @@ func (s *httpServer) onRequest(ctx *gin.Context) {

switch ctx.Request.Method {
case http.MethodOptions:
ctx.Writer.Header().Set("Access-Control-Allow-Methods", "OPTIONS, GET")
ctx.Writer.Header().Set("Access-Control-Allow-Headers", "Authorization, Range")
ctx.Writer.WriteHeader(http.StatusNoContent)
if ctx.Request.Header.Get("Access-Control-Request-Method") != "" {
ctx.Writer.Header().Set("Access-Control-Allow-Methods", "OPTIONS, GET")
ctx.Writer.Header().Set("Access-Control-Allow-Headers", "Authorization, Range")
ctx.Writer.WriteHeader(http.StatusNoContent)
}
return

case http.MethodGet:
Expand Down
37 changes: 37 additions & 0 deletions internal/servers/hls/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package hls

import (
"fmt"
"io"
"net/http"
"os"
"path/filepath"
Expand Down Expand Up @@ -60,6 +61,42 @@ func (pm *dummyPathManager) AddReader(req defs.PathAddReaderReq) (defs.Path, *st
return pm.addReader(req)
}

func TestPreflightRequest(t *testing.T) {
s := &Server{
Address: "127.0.0.1:8888",
AllowOrigin: "*",
ReadTimeout: conf.StringDuration(10 * time.Second),
Parent: test.NilLogger,
}
err := s.Initialize()
require.NoError(t, err)
defer s.Close()

tr := &http.Transport{}
defer tr.CloseIdleConnections()
hc := &http.Client{Transport: tr}

req, err := http.NewRequest(http.MethodOptions, "http://localhost:8888", nil)
require.NoError(t, err)

req.Header.Add("Access-Control-Request-Method", "GET")

res, err := hc.Do(req)
require.NoError(t, err)
defer res.Body.Close()

require.Equal(t, http.StatusNoContent, res.StatusCode)

byts, err := io.ReadAll(res.Body)
require.NoError(t, err)

require.Equal(t, "*", res.Header.Get("Access-Control-Allow-Origin"))
require.Equal(t, "true", res.Header.Get("Access-Control-Allow-Credentials"))
require.Equal(t, "OPTIONS, GET", res.Header.Get("Access-Control-Allow-Methods"))
require.Equal(t, "Authorization, Range", res.Header.Get("Access-Control-Allow-Headers"))
require.Equal(t, byts, []byte{})
}

func TestServerNotFound(t *testing.T) {
for _, ca := range []string{
"always remux off",
Expand Down
20 changes: 13 additions & 7 deletions internal/servers/webrtc/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package webrtc
import (
"bytes"
"context"
"io"
"net/http"
"net/url"
"reflect"
Expand Down Expand Up @@ -102,7 +103,7 @@ func initializeTestServer(t *testing.T) *Server {
Encryption: false,
ServerKey: "",
ServerCert: "",
AllowOrigin: "",
AllowOrigin: "*",
TrustedProxies: conf.IPNetworks{},
ReadTimeout: conf.StringDuration(10 * time.Second),
WriteQueueSize: 512,
Expand Down Expand Up @@ -146,28 +147,33 @@ func TestServerStaticPages(t *testing.T) {
}
}

func TestServerOptionsPreflight(t *testing.T) {
func TestPreflightRequest(t *testing.T) {
s := initializeTestServer(t)
defer s.Close()

tr := &http.Transport{}
defer tr.CloseIdleConnections()
hc := &http.Client{Transport: tr}

// preflight requests must always work, without authentication
req, err := http.NewRequest(http.MethodOptions, "http://localhost:8886/teststream/whip", nil)
req, err := http.NewRequest(http.MethodOptions, "http://localhost:8886", nil)
require.NoError(t, err)

req.Header.Set("Access-Control-Request-Method", "OPTIONS")
req.Header.Add("Access-Control-Request-Method", "GET")

res, err := hc.Do(req)
require.NoError(t, err)
defer res.Body.Close()

require.Equal(t, http.StatusNoContent, res.StatusCode)

_, ok := res.Header["Link"]
require.Equal(t, false, ok)
byts, err := io.ReadAll(res.Body)
require.NoError(t, err)

require.Equal(t, "*", res.Header.Get("Access-Control-Allow-Origin"))
require.Equal(t, "true", res.Header.Get("Access-Control-Allow-Credentials"))
require.Equal(t, "OPTIONS, GET, POST, PATCH, DELETE", res.Header.Get("Access-Control-Allow-Methods"))
require.Equal(t, "Authorization, Content-Type, If-Match", res.Header.Get("Access-Control-Allow-Headers"))
require.Equal(t, byts, []byte{})
}

func TestServerOptionsICEServer(t *testing.T) {
Expand Down

0 comments on commit 3bda68c

Please sign in to comment.