Skip to content

Commit

Permalink
Allow user to restrict handler methods (#108)
Browse files Browse the repository at this point in the history
* Allow user to restrict handler methods
---------

Signed-off-by: Matheus Horstmann <[email protected]>
Co-authored-by: Matheus Horstmann <[email protected]>
  • Loading branch information
horstmannmat and Matheus Horstmann authored Nov 8, 2023
1 parent e2b1424 commit 307216b
Show file tree
Hide file tree
Showing 3 changed files with 179 additions and 31 deletions.
88 changes: 65 additions & 23 deletions v3/sockjs/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,29 +34,9 @@ func NewHandler(prefix string, opts Options, handlerFunc func(Session)) *Handler
handlerFunc: handlerFunc,
sessions: make(map[string]*session),
}
xhrCors := xhrCorsFactory(opts)
h.mappings = []*mapping{
newMapping("GET", "^[/]?$", welcomeHandler),
newMapping("OPTIONS", "^/info$", opts.cookie, xhrCors, cacheFor, opts.info),
newMapping("GET", "^/info$", opts.cookie, xhrCors, noCache, opts.info),
// XHR
newMapping("POST", sessionPrefix+"/xhr_send$", opts.cookie, xhrCors, noCache, h.xhrSend),
newMapping("OPTIONS", sessionPrefix+"/xhr_send$", opts.cookie, xhrCors, cacheFor, xhrOptions),
newMapping("POST", sessionPrefix+"/xhr$", opts.cookie, xhrCors, noCache, h.xhrPoll),
newMapping("OPTIONS", sessionPrefix+"/xhr$", opts.cookie, xhrCors, cacheFor, xhrOptions),
newMapping("POST", sessionPrefix+"/xhr_streaming$", opts.cookie, xhrCors, noCache, h.xhrStreaming),
newMapping("OPTIONS", sessionPrefix+"/xhr_streaming$", opts.cookie, xhrCors, cacheFor, xhrOptions),
// EventStream
newMapping("GET", sessionPrefix+"/eventsource$", opts.cookie, xhrCors, noCache, h.eventSource),
// Htmlfile
newMapping("GET", sessionPrefix+"/htmlfile$", opts.cookie, xhrCors, noCache, h.htmlFile),
// JsonP
newMapping("GET", sessionPrefix+"/jsonp$", opts.cookie, xhrCors, noCache, h.jsonp),
newMapping("OPTIONS", sessionPrefix+"/jsonp$", opts.cookie, xhrCors, cacheFor, xhrOptions),
newMapping("POST", sessionPrefix+"/jsonp_send$", opts.cookie, xhrCors, noCache, h.jsonpSend),
// IFrame
newMapping("GET", "^/iframe[0-9-.a-z_]*.html$", cacheFor, h.iframe),
}

h.fillMappingsWithAllowedMethods()

if opts.Websocket {
h.mappings = append(h.mappings, newMapping("GET", sessionPrefix+"/websocket$", h.sockjsWebsocket))
}
Expand Down Expand Up @@ -121,3 +101,65 @@ func (h *Handler) sessionByRequest(req *http.Request) (*session, error) {
sess.setCurrentRequest(req)
return sess, nil
}

// fillMappingsWithAllowedMethods adds only allowed methods to handler.mappings, by if method is not disabled
func (h *Handler) fillMappingsWithAllowedMethods() {

xhrCors := xhrCorsFactory(h.options)

// Default Methods
h.mappings = []*mapping{
newMapping("GET", "^[/]?$", welcomeHandler),
newMapping("OPTIONS", "^/info$", h.options.cookie, xhrCors, cacheFor, h.options.info),
newMapping("GET", "^/info$", h.options.cookie, xhrCors, noCache, h.options.info),
// IFrame
newMapping("GET", "^/iframe[0-9-.a-z_]*.html$", cacheFor, h.iframe),
}

// Adding XHR to mapping
if !h.options.DisableXHR {
h.mappings = append(h.mappings,
newMapping("POST", sessionPrefix+"/xhr$", h.options.cookie, xhrCors, noCache, h.xhrPoll),
newMapping("OPTIONS", sessionPrefix+"/xhr$", h.options.cookie, xhrCors, cacheFor, xhrOptions),
)
}

// Adding XHRStreaming to mapping
if !h.options.DisableXHRStreaming {
h.mappings = append(h.mappings,
newMapping("POST", sessionPrefix+"/xhr_streaming$", h.options.cookie, xhrCors, noCache, h.xhrStreaming),
newMapping("OPTIONS", sessionPrefix+"/xhr_streaming$", h.options.cookie, xhrCors, cacheFor, xhrOptions),
)
}

// Adding EventSource to mapping
if !h.options.DisableEventSource {
h.mappings = append(h.mappings,
newMapping("GET", sessionPrefix+"/eventsource$", h.options.cookie, xhrCors, noCache, h.eventSource),
)
}

// Adding HtmlFile to mapping
if !h.options.DisableHtmlFile {
h.mappings = append(h.mappings,
newMapping("GET", sessionPrefix+"/htmlfile$", h.options.cookie, xhrCors, noCache, h.htmlFile),
)
}

// Adding JSONP to mapping
if !h.options.DisableJSONP {
h.mappings = append(h.mappings,
newMapping("GET", sessionPrefix+"/jsonp$", h.options.cookie, xhrCors, noCache, h.jsonp),
newMapping("OPTIONS", sessionPrefix+"/jsonp$", h.options.cookie, xhrCors, cacheFor, xhrOptions),
newMapping("POST", sessionPrefix+"/jsonp_send$", h.options.cookie, xhrCors, noCache, h.jsonpSend),
)
}

// when adding XHRPoll or/and XHRStreaming xhr_send must be added too (only once)
if !h.options.DisableXHR || !h.options.DisableXHRStreaming {
h.mappings = append(h.mappings,
newMapping("POST", sessionPrefix+"/xhr_send$", h.options.cookie, xhrCors, noCache, h.xhrSend),
newMapping("OPTIONS", sessionPrefix+"/xhr_send$", h.options.cookie, xhrCors, cacheFor, xhrOptions),
)
}
}
85 changes: 85 additions & 0 deletions v3/sockjs/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,88 @@ func TestHandler_StrictPathMatching(t *testing.T) {
})
}
}

func TestHandler_DisabledMethods(t *testing.T) {
disabledMethodsOptions := testOptions
disabledMethodsOptions.DisableXHR = true
disabledMethodsOptions.DisableJSONP = true
disabledMethodsOptions.DisableHtmlFile = true
disabledMethodsOptions.DisableEventSource = true
disabledMethodsOptions.DisableXHRStreaming = true

handler := NewHandler("", disabledMethodsOptions, nil)

// Two servers are created to compare the different options
server := httptest.NewServer(handler)

defer server.Close()

cases := []struct {
URL string
expectedStatus int
HTTPMethod string
}{

{
URL: "/",
expectedStatus: http.StatusOK,
HTTPMethod: "GET",
},
// XHR
{
URL: "/server/session/xhr",
expectedStatus: http.StatusNotFound,
HTTPMethod: "POST",
},
{
URL: "/server/session/xhr_streaming",
expectedStatus: http.StatusNotFound,
HTTPMethod: "POST",
},
{
URL: "/server/session/xhr_send",
expectedStatus: http.StatusNotFound,
HTTPMethod: "POST",
},

// EventStream
{
URL: "/server/session/eventsource",
expectedStatus: http.StatusNotFound,
HTTPMethod: "GET",
},

//Htmlfile
{
URL: "/server/session/htmlfile",
expectedStatus: http.StatusNotFound,
HTTPMethod: "GET",
},

// JSONP
{
URL: "/server/session/jsonp",
expectedStatus: http.StatusNotFound,
HTTPMethod: "GET",
},
{
URL: "/server/session/jsonp_send",
expectedStatus: http.StatusNotFound,
HTTPMethod: "POST",
},
}

for _, urlCase := range cases {
t.Run(urlCase.URL, func(t *testing.T) {

req, err := http.NewRequest(urlCase.HTTPMethod, server.URL+urlCase.URL, nil)
require.NoError(t, err)

resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)

assert.Equal(t, urlCase.expectedStatus, resp.StatusCode)
assert.NoError(t, resp.Body.Close())
})
}
}
37 changes: 29 additions & 8 deletions v3/sockjs/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,18 +68,39 @@ type Options struct {
// won't be set at all. If this function is nil then Origin option above will
// be taken into account.
CheckOrigin func(*http.Request) bool

// DisableXHR This option can be used to restrict handler to use XHR method. By default, DisableXHR is false, meaning that handler is allowed to use XHR
DisableXHR bool

// DisableXHRStreaming This option can be used to restrict handler to use XHRStreaming method. By default, DisableXHRStreaming is false, meaning that handler is allowed to use XHRStreaming
DisableXHRStreaming bool

// DisableEventSource This option can be used to restrict handler to use EventSource method. By default, DisableEventSource is false, meaning that handler is allowed to use EventSource
DisableEventSource bool

// DisableHtmlFile This option can be used to restrict handler to use HtmlFile method. By default, DisableHtmlFile is false, meaning that handler is allowed to use HtmlFile

DisableHtmlFile bool

// DisableJSONP is option can be used to restrict handler to use JSONP method. By default, DisableJSONP is false, meaning that handler is allowed to use JSONP
DisableJSONP bool
}

// DefaultOptions is a convenient set of options to be used for sockjs
var DefaultOptions = Options{
Websocket: true,
RawWebsocket: false,
JSessionID: nil,
SockJSURL: "//cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js",
HeartbeatDelay: 25 * time.Second,
DisconnectDelay: 5 * time.Second,
ResponseLimit: 128 * 1024,
WebsocketUpgrader: &websocket.Upgrader{},
Websocket: true,
RawWebsocket: false,
JSessionID: nil,
SockJSURL: "//cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js",
HeartbeatDelay: 25 * time.Second,
DisconnectDelay: 5 * time.Second,
ResponseLimit: 128 * 1024,
WebsocketUpgrader: &websocket.Upgrader{},
DisableXHR: false,
DisableXHRStreaming: false,
DisableEventSource: false,
DisableHtmlFile: false,
DisableJSONP: false,
}

type info struct {
Expand Down

0 comments on commit 307216b

Please sign in to comment.