Secure-by-default HTTP servers in Go.
(Windows port) The system statistics collection daemon. Mirror of the canonical repository (updated hourly), but feel free to send Pull Requests!
Proxy server for helping with focus written in Rust ...
dotfiles
A simple package to parse reports coming from browsers implementing the Reporting API
Collection of handy scrips
Little language that compiles into C++
C++ tricks
Pull request review commentgoogle/go-safeweb
type ResponseWriter interface { // // If the ResponseWriter has already been written to, then this method panics. Redirect(r *IncomingRequest, url string, code StatusCode) Result+} +// ResponseHeadersWriter is used to alter the HTTP response headers.+//+// A ResponseHeadersWriter may not be used after the Handler.ServeHTTP method has returned.+type ResponseHeadersWriter interface {
I would document that if somethings has already been written this entire type methods set becomes a no-op.
comment created time in 10 hours
startedvdumoulin/conv_arithmetic
started time in 20 hours
Git-powered content management for Bawang
fork in a day
fork mattiasgrenfeldt/knock-knock
Vem där? Kolla vem som är på SM (i framtiden även in och utlista)
fork in 2 days
Historia-system
fork in 2 days
Yoggi, som i yoghurt, som i fil, som i statiska filer. Bra jobbat Mauritz
fork in 2 days
fork mattiasgrenfeldt/verksamt
System for keeping track of the computer science chapters operational plan (verksamhetsplan)
fork in 2 days
Hacky application for displaying news and stuff running on the monitors where we eat lunch.
fork in 2 days
Hantering av Datasektionens nuvarande och tidigare funktionärer
fork in 2 days
fork mattiasgrenfeldt/cashflow
Django and React project to manage receipts and reimbursements at Datasektionen
https://cashflow.datasektionen.se
fork in 2 days
Pull request review commentgoogle/go-safeweb
Consolidated request handling and the ResponseWriter implementation to one object
+// Copyright 2020 Google LLC+//+// Licensed under the Apache License, Version 2.0 (the "License");+// you may not use this file except in compliance with the License.+// You may obtain a copy of the License at+//+// https://www.apache.org/licenses/LICENSE-2.0+//+// Unless required by applicable law or agreed to in writing, software+// distributed under the License is distributed on an "AS IS" BASIS,+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+// See the License for the specific language governing permissions and+// limitations under the License.++package safehttp++import (+ "fmt"+ "net/http"+)++// TODO(kele): come up with a better name than this.
Maybe we can call this a "flight"
comment created time in 3 days
Pull request review commentgoogle/go-safeweb
Consolidated request handling and the ResponseWriter implementation to one object
+// Copyright 2020 Google LLC+//+// Licensed under the Apache License, Version 2.0 (the "License");+// you may not use this file except in compliance with the License.+// You may obtain a copy of the License at+//+// https://www.apache.org/licenses/LICENSE-2.0+//+// Unless required by applicable law or agreed to in writing, software+// distributed under the License is distributed on an "AS IS" BASIS,+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+// See the License for the specific language governing permissions and+// limitations under the License.++package safehttp++import (+ "fmt"+ "net/http"+)++// TODO(kele): come up with a better name than this.+type task struct {+ rw http.ResponseWriter+ req *IncomingRequest++ cfg HandlerConfig++ code StatusCode+ header Header++ // TODO(kele): we need to distinguish between calling Write and actually+ // writing to the net/http.ResponseWriter.+ written bool+ writtenError bool+}++// DeprecatedNewResponseWriter creates a ResponseWriter implementation that has+// been historically used for testing. DO NOT USE this function, it's going+// to be removed soon.+//+// The main problem with this is the implementation of the returned+// ResponseWriter is incomplete, i.e. lacks the Handler and Interceptors fields+// of the HandlerConfig needed by the task. For the existing tests that use it,+// it's fine, but they should be migrated.+//+// TODO(kele): remove this once we have a better option to provide a+// ResponseWriter implementation for interceptor testing.+func DeprecatedNewResponseWriter(rw http.ResponseWriter, dispatcher Dispatcher) ResponseWriter {+ return &task{+ cfg: HandlerConfig{Dispatcher: dispatcher},+ rw: rw,+ header: newHeader(rw.Header()),+ }+}++// HandlerConfig is the safe HTTP handler configuration, including the+// dispatcher and interceptors.+type HandlerConfig struct {+ Handler Handler+ Dispatcher Dispatcher+ Interceptors []ConfiguredInterceptor+}++func processRequest(cfg HandlerConfig, rw http.ResponseWriter, req *http.Request) {
personal taste: I'd make processRequest a method of HandlerConfig.
comment created time in 3 days
Pull request review commentgoogle/go-safeweb
Consolidated request handling and the ResponseWriter implementation to one object
+// Copyright 2020 Google LLC+//+// Licensed under the Apache License, Version 2.0 (the "License");+// you may not use this file except in compliance with the License.+// You may obtain a copy of the License at+//+// https://www.apache.org/licenses/LICENSE-2.0+//+// Unless required by applicable law or agreed to in writing, software+// distributed under the License is distributed on an "AS IS" BASIS,+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+// See the License for the specific language governing permissions and+// limitations under the License.++package safehttp++import (+ "net/http"+)++type Task struct {+ rw http.ResponseWriter+ req *IncomingRequest++ cfg HandlerConfig++ code StatusCode+ header Header++ written bool+ writtenError bool+}++// NewTask creates a ResponseWriter from a safehttp.Dispatcher, an+// http.ResponseWriter and a reference to the current IncomingRequest being served.+// The IncomingRequest will only be used by the commit phase, which only runs when+// the ServeMux is used, and can be passed as nil in tests.+// TODO: remove the IncomingRequest parameter.++// ProperNewTask TODO+func BrokenNewTask(rw http.ResponseWriter, dispatcher Dispatcher) *Task {+ return &Task{+ cfg: HandlerConfig{Dispatcher: dispatcher},+ rw: rw,+ header: newHeader(rw.Header()),+ }+}++type HandlerConfig struct {+ Handler Handler+ Dispatcher Dispatcher+ Interceptors []ConfiguredInterceptor+}++func ProcessRequest(cfg HandlerConfig, rw http.ResponseWriter, req *http.Request) {+ t := &Task{+ cfg: cfg,+ rw: rw,+ header: newHeader(rw.Header()),+ req: NewIncomingRequest(req),+ }++ // The `net/http` package recovers handler panics, but we cannot rely on that behavior here.+ // The reason is, we might need to run After/Commit stages of the interceptors before we+ // respond with a 500 Internal Server Error.+ defer func() {+ if r := recover(); r != nil {+ t.WriteError(StatusInternalServerError)+ }+ }()++ for _, it := range t.cfg.Interceptors {+ it.Before(t, t.req)+ if t.written {+ return+ }+ }++ t.cfg.Handler.ServeHTTP(t, t.req)+ if !t.written {+ t.NoContent()+ }+}++// Write dispatches the response to the Dispatcher. This will be written to the+// underlying http.ResponseWriter if the Dispatcher decides it's safe to do so.+//+// TODO: replace panics with proper error handling when writing the response fails.+func (t *Task) Write(resp Response) Result {+ if t.written {+ panic("ResponseWriter was already written to")+ }+ t.written = true+ t.commitPhase(resp)++ ct, err := t.cfg.Dispatcher.ContentType(resp)+ if err != nil {+ panic(err)+ }+ t.rw.Header().Set("Content-Type", ct)++ if t.code == 0 {+ t.code = StatusOK+ }+ t.rw.WriteHeader(int(t.code))++ if err := t.cfg.Dispatcher.Write(t.rw, resp); err != nil {+ panic(err)+ }+ return Result{}+}++// NoContent responds with a 204 No Content response.+//+// If the ResponseWriter has already been written to, then this method will panic.+func (t *Task) NoContent() Result {+ if t.written {+ panic("ResponseWriter was already written to")+ }+ t.written = true+ t.commitPhase(NoContentResponse{})+ t.rw.WriteHeader(int(StatusNoContent))+ return Result{}+}++// WriteError writes an error response (400-599) according to the provided+// status code.+//+// If the ResponseWriter has already been written to, then this method will panic.+func (t *Task) WriteError(code StatusCode) Result {+ // TODO: accept custom error responses that need to go through the dispatcher.+ if t.writtenError {+ panic("ResponseWriter.WriteError called twice")+ }+ t.written = true+ t.writtenError = true+ resp := &ErrorResponse{Code: code}+ t.errorPhase(resp)+ http.Error(t.rw, http.StatusText(int(resp.Code)), int(resp.Code))+ return Result{}+}++// Redirect responds with a redirect to the given url, using code as the status code.+//+// If the ResponseWriter has already been written to, then this method will panic.+func (t *Task) Redirect(r *IncomingRequest, url string, code StatusCode) Result {+ if code < 300 || code >= 400 {+ panic("wrong method called")+ }+ if t.written {+ panic("ResponseWriter was already written to")+ }+ t.written = true+ http.Redirect(t.rw, r.req, url, int(code))+ return Result{}+}++// Header returns the collection of headers that will be set+// on the response. Headers must be set before writing a+// response (e.g. Write, WriteTemplate).+func (t *Task) Header() Header {+ return t.header+}++// SetCookie adds a Set-Cookie header to the provided ResponseWriter's headers.+// The provided cookie must have a valid Name, otherwise an error will be+// returned.+func (t *Task) SetCookie(c *Cookie) error {+ return t.header.addCookie(c)+}++// SetCode allows setting a response status. If the response was already+// written, trying to set the status code will have no effect. This method will+// panic if an invalid status code is passed (i.e. not in the range 1XX-5XX).+//+// If SetCode was called before NoContent, Redirect or WriteError, the status+// code set by the latter will be the actual response status.+//+// TODO(empijei@, kele@, maramihali@): decide what should be done if the+// code passed is either 3XX (redirect) or 4XX-5XX (client/server error).+func (t *Task) SetCode(code StatusCode) {+ if t.written {+ return+ }+ if code < 100 || code >= 600 {+ panic("invalid status code")+ }+ t.code = code+}++// commitPhase calls the Commit phases of all the interceptors. This stage will+// run before a response is written to the ResponseWriter. If a response is+// written to the ResponseWriter in a Commit phase then the Commit phases of the+// remaining interceptors won't execute.+//+// TODO: BIG WARNING, if ResponseWriter.Write and ResponseWriter.WriteTemplate+// are called in Commit then this will recurse. CommitResponseWriter was an+// attempt to prevent this by not giving access to Write and WriteTemplate in+// the Commit phase.+func (t *Task) commitPhase(resp Response) {+ for i := len(t.cfg.Interceptors) - 1; i >= 0; i-- {+ t.cfg.Interceptors[i].Commit(t, t.req, resp)+ }+}++// errorPhase calls the OnError phases of all the interceptors associated with+// a handler. This stage runs before an error response is written to the+// ResponseWriter.+//+// TODO: BIG WARNING, if an interceptor attempts to write to the ResponseWriter+// in the OnError phase will result in an irrecoverable error.+func (t *Task) errorPhase(resp Response) {+ for i := len(t.cfg.Interceptors) - 1; i >= 0; i-- {+ t.cfg.Interceptors[i].OnError(t, t.req, resp)
sometimes we call it after the response has been partially written, there isn't much it can modify at that point, is there?
comment created time in 3 days
Pull request review commentgoogle/go-safeweb
Consolidated request handling and the ResponseWriter implementation to one object
+// Copyright 2020 Google LLC+//+// Licensed under the Apache License, Version 2.0 (the "License");+// you may not use this file except in compliance with the License.+// You may obtain a copy of the License at+//+// https://www.apache.org/licenses/LICENSE-2.0+//+// Unless required by applicable law or agreed to in writing, software+// distributed under the License is distributed on an "AS IS" BASIS,+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+// See the License for the specific language governing permissions and+// limitations under the License.++package safehttp++import (+ "net/http"+)++type Task struct {+ rw http.ResponseWriter+ req *IncomingRequest++ cfg HandlerConfig++ code StatusCode+ header Header++ written bool+ writtenError bool+}++// NewTask creates a ResponseWriter from a safehttp.Dispatcher, an+// http.ResponseWriter and a reference to the current IncomingRequest being served.+// The IncomingRequest will only be used by the commit phase, which only runs when+// the ServeMux is used, and can be passed as nil in tests.+// TODO: remove the IncomingRequest parameter.++// ProperNewTask TODO+func BrokenNewTask(rw http.ResponseWriter, dispatcher Dispatcher) *Task {+ return &Task{+ cfg: HandlerConfig{Dispatcher: dispatcher},+ rw: rw,+ header: newHeader(rw.Header()),+ }+}++type HandlerConfig struct {+ Handler Handler+ Dispatcher Dispatcher+ Interceptors []ConfiguredInterceptor+}++func ProcessRequest(cfg HandlerConfig, rw http.ResponseWriter, req *http.Request) {+ t := &Task{+ cfg: cfg,+ rw: rw,+ header: newHeader(rw.Header()),+ req: NewIncomingRequest(req),+ }++ // The `net/http` package recovers handler panics, but we cannot rely on that behavior here.+ // The reason is, we might need to run After/Commit stages of the interceptors before we+ // respond with a 500 Internal Server Error.+ defer func() {+ if r := recover(); r != nil {+ t.WriteError(StatusInternalServerError)+ }+ }()++ for _, it := range t.cfg.Interceptors {+ it.Before(t, t.req)+ if t.written {+ return+ }+ }++ t.cfg.Handler.ServeHTTP(t, t.req)+ if !t.written {+ t.NoContent()+ }+}++// Write dispatches the response to the Dispatcher. This will be written to the+// underlying http.ResponseWriter if the Dispatcher decides it's safe to do so.+//+// TODO: replace panics with proper error handling when writing the response fails.+func (t *Task) Write(resp Response) Result {+ if t.written {+ panic("ResponseWriter was already written to")+ }+ t.written = true+ t.commitPhase(resp)++ ct, err := t.cfg.Dispatcher.ContentType(resp)+ if err != nil {+ panic(err)+ }+ t.rw.Header().Set("Content-Type", ct)++ if t.code == 0 {+ t.code = StatusOK+ }+ t.rw.WriteHeader(int(t.code))++ if err := t.cfg.Dispatcher.Write(t.rw, resp); err != nil {+ panic(err)+ }+ return Result{}+}++// NoContent responds with a 204 No Content response.+//+// If the ResponseWriter has already been written to, then this method will panic.+func (t *Task) NoContent() Result {+ if t.written {+ panic("ResponseWriter was already written to")+ }+ t.written = true+ t.commitPhase(NoContentResponse{})+ t.rw.WriteHeader(int(StatusNoContent))+ return Result{}+}
+1 on Skip and on asking the Dispatcher to handle a NoContent type.
comment created time in 3 days
Pull request review commentgoogle/go-safeweb
+// Copyright 2020 Google LLC+//+// Licensed under the Apache License, Version 2.0 (the "License");+// you may not use this file except in compliance with the License.+// You may obtain a copy of the License at+//+// https://www.apache.org/licenses/LICENSE-2.0+//+// Unless required by applicable law or agreed to in writing, software+// distributed under the License is distributed on an "AS IS" BASIS,+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+// See the License for the specific language governing permissions and+// limitations under the License.++package safehttp++import (+ "net/http"+)++type Task struct {+ rw http.ResponseWriter+ req *IncomingRequest++ cfg HandlerConfig++ code StatusCode+ header Header++ written bool+ writtenError bool+}++// NewTask creates a ResponseWriter from a safehttp.Dispatcher, an+// http.ResponseWriter and a reference to the current IncomingRequest being served.+// The IncomingRequest will only be used by the commit phase, which only runs when+// the ServeMux is used, and can be passed as nil in tests.+// TODO: remove the IncomingRequest parameter.++// ProperNewTask TODO+func BrokenNewTask(rw http.ResponseWriter, dispatcher Dispatcher) *Task {+ return &Task{+ cfg: HandlerConfig{Dispatcher: dispatcher},+ rw: rw,+ header: newHeader(rw.Header()),+ }+}++type HandlerConfig struct {+ Handler Handler+ Dispatcher Dispatcher+ Interceptors []ConfiguredInterceptor+}++func ProcessRequest(cfg HandlerConfig, rw http.ResponseWriter, req *http.Request) {+ t := &Task{+ cfg: cfg,+ rw: rw,+ header: newHeader(rw.Header()),+ req: NewIncomingRequest(req),+ }++ // The `net/http` package recovers handler panics, but we cannot rely on that behavior here.+ // The reason is, we might need to run After/Commit stages of the interceptors before we+ // respond with a 500 Internal Server Error.+ defer func() {+ if r := recover(); r != nil {+ t.WriteError(StatusInternalServerError)+ }+ }()++ for _, it := range t.cfg.Interceptors {+ it.Before(t, t.req)+ if t.written {+ return+ }+ }++ t.cfg.Handler.ServeHTTP(t, t.req)+ if !t.written {+ t.NoContent()+ }+}++// Write dispatches the response to the Dispatcher. This will be written to the+// underlying http.ResponseWriter if the Dispatcher decides it's safe to do so.+//+// TODO: replace panics with proper error handling when writing the response fails.+func (t *Task) Write(resp Response) Result {+ if t.written {+ panic("ResponseWriter was already written to")+ }+ t.written = true+ t.commitPhase(resp)++ ct, err := t.cfg.Dispatcher.ContentType(resp)+ if err != nil {+ panic(err)+ }+ t.rw.Header().Set("Content-Type", ct)++ if t.code == 0 {+ t.code = StatusOK+ }+ t.rw.WriteHeader(int(t.code))++ if err := t.cfg.Dispatcher.Write(t.rw, resp); err != nil {+ panic(err)+ }+ return Result{}+}++// NoContent responds with a 204 No Content response.+//+// If the ResponseWriter has already been written to, then this method will panic.+func (t *Task) NoContent() Result {+ if t.written {+ panic("ResponseWriter was already written to")+ }+ t.written = true+ t.commitPhase(NoContentResponse{})+ t.rw.WriteHeader(int(StatusNoContent))+ return Result{}+}++// WriteError writes an error response (400-599) according to the provided+// status code.+//+// If the ResponseWriter has already been written to, then this method will panic.+func (t *Task) WriteError(code StatusCode) Result {+ // TODO: accept custom error responses that need to go through the dispatcher.+ if t.writtenError {+ panic("ResponseWriter.WriteError called twice")+ }+ t.written = true+ t.writtenError = true+ resp := &ErrorResponse{Code: code}+ t.errorPhase(resp)+ http.Error(t.rw, http.StatusText(int(resp.Code)), int(resp.Code))+ return Result{}+}++// Redirect responds with a redirect to the given url, using code as the status code.+//+// If the ResponseWriter has already been written to, then this method will panic.+func (t *Task) Redirect(r *IncomingRequest, url string, code StatusCode) Result {+ if code < 300 || code >= 400 {+ panic("wrong method called")+ }+ if t.written {+ panic("ResponseWriter was already written to")+ }+ t.written = true+ http.Redirect(t.rw, r.req, url, int(code))+ return Result{}+}++// Header returns the collection of headers that will be set+// on the response. Headers must be set before writing a+// response (e.g. Write, WriteTemplate).+func (t *Task) Header() Header {+ return t.header+}++// SetCookie adds a Set-Cookie header to the provided ResponseWriter's headers.+// The provided cookie must have a valid Name, otherwise an error will be+// returned.+func (t *Task) SetCookie(c *Cookie) error {+ return t.header.addCookie(c)+}++// SetCode allows setting a response status. If the response was already+// written, trying to set the status code will have no effect. This method will+// panic if an invalid status code is passed (i.e. not in the range 1XX-5XX).+//+// If SetCode was called before NoContent, Redirect or WriteError, the status+// code set by the latter will be the actual response status.+//+// TODO(empijei@, kele@, maramihali@): decide what should be done if the+// code passed is either 3XX (redirect) or 4XX-5XX (client/server error).+func (t *Task) SetCode(code StatusCode) {+ if t.written {+ return+ }+ if code < 100 || code >= 600 {+ panic("invalid status code")+ }+ t.code = code+}++// commitPhase calls the Commit phases of all the interceptors. This stage will+// run before a response is written to the ResponseWriter. If a response is+// written to the ResponseWriter in a Commit phase then the Commit phases of the+// remaining interceptors won't execute.+//+// TODO: BIG WARNING, if ResponseWriter.Write and ResponseWriter.WriteTemplate+// are called in Commit then this will recurse. CommitResponseWriter was an+// attempt to prevent this by not giving access to Write and WriteTemplate in+// the Commit phase.+func (t *Task) commitPhase(resp Response) {+ for i := len(t.cfg.Interceptors) - 1; i >= 0; i-- {+ t.cfg.Interceptors[i].Commit(t, t.req, resp)+ }+}++// errorPhase calls the OnError phases of all the interceptors associated with+// a handler. This stage runs before an error response is written to the+// ResponseWriter.+//+// TODO: BIG WARNING, if an interceptor attempts to write to the ResponseWriter+// in the OnError phase will result in an irrecoverable error.+func (t *Task) errorPhase(resp Response) {+ for i := len(t.cfg.Interceptors) - 1; i >= 0; i-- {+ t.cfg.Interceptors[i].OnError(t, t.req, resp)
Should we maybe pass a smaller interface to OnError that doesn't have Write* methods so we make sure Write is not called in this step?
comment created time in 17 days
Pull request review commentgoogle/go-safeweb
+// Copyright 2020 Google LLC+//+// Licensed under the Apache License, Version 2.0 (the "License");+// you may not use this file except in compliance with the License.+// You may obtain a copy of the License at+//+// https://www.apache.org/licenses/LICENSE-2.0+//+// Unless required by applicable law or agreed to in writing, software+// distributed under the License is distributed on an "AS IS" BASIS,+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+// See the License for the specific language governing permissions and+// limitations under the License.++package safehttp++import (+ "net/http"+)++type Task struct {+ rw http.ResponseWriter+ req *IncomingRequest++ cfg HandlerConfig++ code StatusCode+ header Header++ written bool+ writtenError bool+}++// NewTask creates a ResponseWriter from a safehttp.Dispatcher, an+// http.ResponseWriter and a reference to the current IncomingRequest being served.+// The IncomingRequest will only be used by the commit phase, which only runs when+// the ServeMux is used, and can be passed as nil in tests.+// TODO: remove the IncomingRequest parameter.++// ProperNewTask TODO+func BrokenNewTask(rw http.ResponseWriter, dispatcher Dispatcher) *Task {+ return &Task{+ cfg: HandlerConfig{Dispatcher: dispatcher},+ rw: rw,+ header: newHeader(rw.Header()),+ }+}++type HandlerConfig struct {+ Handler Handler+ Dispatcher Dispatcher+ Interceptors []ConfiguredInterceptor+}++func ProcessRequest(cfg HandlerConfig, rw http.ResponseWriter, req *http.Request) {+ t := &Task{+ cfg: cfg,+ rw: rw,+ header: newHeader(rw.Header()),+ req: NewIncomingRequest(req),+ }++ // The `net/http` package recovers handler panics, but we cannot rely on that behavior here.+ // The reason is, we might need to run After/Commit stages of the interceptors before we+ // respond with a 500 Internal Server Error.+ defer func() {+ if r := recover(); r != nil {+ t.WriteError(StatusInternalServerError)+ }+ }()++ for _, it := range t.cfg.Interceptors {+ it.Before(t, t.req)+ if t.written {+ return+ }+ }++ t.cfg.Handler.ServeHTTP(t, t.req)+ if !t.written {+ t.NoContent()+ }+}++// Write dispatches the response to the Dispatcher. This will be written to the+// underlying http.ResponseWriter if the Dispatcher decides it's safe to do so.+//+// TODO: replace panics with proper error handling when writing the response fails.+func (t *Task) Write(resp Response) Result {+ if t.written {+ panic("ResponseWriter was already written to")+ }+ t.written = true+ t.commitPhase(resp)++ ct, err := t.cfg.Dispatcher.ContentType(resp)+ if err != nil {+ panic(err)+ }+ t.rw.Header().Set("Content-Type", ct)++ if t.code == 0 {+ t.code = StatusOK+ }+ t.rw.WriteHeader(int(t.code))++ if err := t.cfg.Dispatcher.Write(t.rw, resp); err != nil {+ panic(err)+ }+ return Result{}+}++// NoContent responds with a 204 No Content response.+//+// If the ResponseWriter has already been written to, then this method will panic.+func (t *Task) NoContent() Result {+ if t.written {+ panic("ResponseWriter was already written to")+ }+ t.written = true+ t.commitPhase(NoContentResponse{})+ t.rw.WriteHeader(int(StatusNoContent))+ return Result{}+}++// WriteError writes an error response (400-599) according to the provided+// status code.+//+// If the ResponseWriter has already been written to, then this method will panic.+func (t *Task) WriteError(code StatusCode) Result {+ // TODO: accept custom error responses that need to go through the dispatcher.+ if t.writtenError {+ panic("ResponseWriter.WriteError called twice")+ }+ t.written = true+ t.writtenError = true+ resp := &ErrorResponse{Code: code}+ t.errorPhase(resp)+ http.Error(t.rw, http.StatusText(int(resp.Code)), int(resp.Code))+ return Result{}+}++// Redirect responds with a redirect to the given url, using code as the status code.+//+// If the ResponseWriter has already been written to, then this method will panic.+func (t *Task) Redirect(r *IncomingRequest, url string, code StatusCode) Result {+ if code < 300 || code >= 400 {+ panic("wrong method called")+ }+ if t.written {+ panic("ResponseWriter was already written to")+ }+ t.written = true+ http.Redirect(t.rw, r.req, url, int(code))+ return Result{}+}++// Header returns the collection of headers that will be set+// on the response. Headers must be set before writing a+// response (e.g. Write, WriteTemplate).+func (t *Task) Header() Header {+ return t.header+}++// SetCookie adds a Set-Cookie header to the provided ResponseWriter's headers.+// The provided cookie must have a valid Name, otherwise an error will be+// returned.+func (t *Task) SetCookie(c *Cookie) error {+ return t.header.addCookie(c)+}++// SetCode allows setting a response status. If the response was already+// written, trying to set the status code will have no effect. This method will+// panic if an invalid status code is passed (i.e. not in the range 1XX-5XX).+//+// If SetCode was called before NoContent, Redirect or WriteError, the status+// code set by the latter will be the actual response status.+//+// TODO(empijei@, kele@, maramihali@): decide what should be done if the+// code passed is either 3XX (redirect) or 4XX-5XX (client/server error).+func (t *Task) SetCode(code StatusCode) {+ if t.written {+ return+ }+ if code < 100 || code >= 600 {+ panic("invalid status code")+ }+ t.code = code+}++// commitPhase calls the Commit phases of all the interceptors. This stage will+// run before a response is written to the ResponseWriter. If a response is+// written to the ResponseWriter in a Commit phase then the Commit phases of the+// remaining interceptors won't execute.+//+// TODO: BIG WARNING, if ResponseWriter.Write and ResponseWriter.WriteTemplate+// are called in Commit then this will recurse. CommitResponseWriter was an+// attempt to prevent this by not giving access to Write and WriteTemplate in+// the Commit phase.
comment created time in 17 days
Pull request review commentgoogle/go-safeweb
+// Copyright 2020 Google LLC+//+// Licensed under the Apache License, Version 2.0 (the "License");+// you may not use this file except in compliance with the License.+// You may obtain a copy of the License at+//+// https://www.apache.org/licenses/LICENSE-2.0+//+// Unless required by applicable law or agreed to in writing, software+// distributed under the License is distributed on an "AS IS" BASIS,+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+// See the License for the specific language governing permissions and+// limitations under the License.++package safehttp++import (+ "net/http"+)++type Task struct {+ rw http.ResponseWriter+ req *IncomingRequest++ cfg HandlerConfig++ code StatusCode+ header Header++ written bool+ writtenError bool+}++// NewTask creates a ResponseWriter from a safehttp.Dispatcher, an+// http.ResponseWriter and a reference to the current IncomingRequest being served.+// The IncomingRequest will only be used by the commit phase, which only runs when+// the ServeMux is used, and can be passed as nil in tests.+// TODO: remove the IncomingRequest parameter.++// ProperNewTask TODO+func BrokenNewTask(rw http.ResponseWriter, dispatcher Dispatcher) *Task {+ return &Task{+ cfg: HandlerConfig{Dispatcher: dispatcher},+ rw: rw,+ header: newHeader(rw.Header()),+ }+}++type HandlerConfig struct {+ Handler Handler+ Dispatcher Dispatcher+ Interceptors []ConfiguredInterceptor+}++func ProcessRequest(cfg HandlerConfig, rw http.ResponseWriter, req *http.Request) {+ t := &Task{+ cfg: cfg,+ rw: rw,+ header: newHeader(rw.Header()),+ req: NewIncomingRequest(req),+ }++ // The `net/http` package recovers handler panics, but we cannot rely on that behavior here.+ // The reason is, we might need to run After/Commit stages of the interceptors before we+ // respond with a 500 Internal Server Error.+ defer func() {+ if r := recover(); r != nil {+ t.WriteError(StatusInternalServerError)+ }+ }()++ for _, it := range t.cfg.Interceptors {+ it.Before(t, t.req)+ if t.written {+ return+ }+ }++ t.cfg.Handler.ServeHTTP(t, t.req)+ if !t.written {+ t.NoContent()+ }+}++// Write dispatches the response to the Dispatcher. This will be written to the+// underlying http.ResponseWriter if the Dispatcher decides it's safe to do so.+//+// TODO: replace panics with proper error handling when writing the response fails.+func (t *Task) Write(resp Response) Result {+ if t.written {+ panic("ResponseWriter was already written to")+ }+ t.written = true+ t.commitPhase(resp)++ ct, err := t.cfg.Dispatcher.ContentType(resp)+ if err != nil {+ panic(err)+ }+ t.rw.Header().Set("Content-Type", ct)++ if t.code == 0 {+ t.code = StatusOK+ }+ t.rw.WriteHeader(int(t.code))++ if err := t.cfg.Dispatcher.Write(t.rw, resp); err != nil {+ panic(err)+ }+ return Result{}+}++// NoContent responds with a 204 No Content response.+//+// If the ResponseWriter has already been written to, then this method will panic.+func (t *Task) NoContent() Result {+ if t.written {+ panic("ResponseWriter was already written to")+ }+ t.written = true+ t.commitPhase(NoContentResponse{})+ t.rw.WriteHeader(int(StatusNoContent))+ return Result{}+}++// WriteError writes an error response (400-599) according to the provided+// status code.+//+// If the ResponseWriter has already been written to, then this method will panic.+func (t *Task) WriteError(code StatusCode) Result {+ // TODO: accept custom error responses that need to go through the dispatcher.+ if t.writtenError {+ panic("ResponseWriter.WriteError called twice")+ }+ t.written = true+ t.writtenError = true+ resp := &ErrorResponse{Code: code}+ t.errorPhase(resp)+ http.Error(t.rw, http.StatusText(int(resp.Code)), int(resp.Code))+ return Result{}+}++// Redirect responds with a redirect to the given url, using code as the status code.+//+// If the ResponseWriter has already been written to, then this method will panic.+func (t *Task) Redirect(r *IncomingRequest, url string, code StatusCode) Result {+ if code < 300 || code >= 400 {+ panic("wrong method called")+ }+ if t.written {+ panic("ResponseWriter was already written to")+ }+ t.written = true+ http.Redirect(t.rw, r.req, url, int(code))+ return Result{}+}++// Header returns the collection of headers that will be set+// on the response. Headers must be set before writing a+// response (e.g. Write, WriteTemplate).
// response.
comment created time in 17 days
Pull request review commentgoogle/go-safeweb
+// Copyright 2020 Google LLC+//+// Licensed under the Apache License, Version 2.0 (the "License");+// you may not use this file except in compliance with the License.+// You may obtain a copy of the License at+//+// https://www.apache.org/licenses/LICENSE-2.0+//+// Unless required by applicable law or agreed to in writing, software+// distributed under the License is distributed on an "AS IS" BASIS,+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+// See the License for the specific language governing permissions and+// limitations under the License.++package safehttp++import (+ "net/http"+)++type Task struct {+ rw http.ResponseWriter+ req *IncomingRequest++ cfg HandlerConfig++ code StatusCode+ header Header++ written bool+ writtenError bool+}++// NewTask creates a ResponseWriter from a safehttp.Dispatcher, an+// http.ResponseWriter and a reference to the current IncomingRequest being served.+// The IncomingRequest will only be used by the commit phase, which only runs when+// the ServeMux is used, and can be passed as nil in tests.+// TODO: remove the IncomingRequest parameter.++// ProperNewTask TODO+func BrokenNewTask(rw http.ResponseWriter, dispatcher Dispatcher) *Task {+ return &Task{+ cfg: HandlerConfig{Dispatcher: dispatcher},+ rw: rw,+ header: newHeader(rw.Header()),+ }+}++type HandlerConfig struct {+ Handler Handler+ Dispatcher Dispatcher+ Interceptors []ConfiguredInterceptor+}++func ProcessRequest(cfg HandlerConfig, rw http.ResponseWriter, req *http.Request) {+ t := &Task{+ cfg: cfg,+ rw: rw,+ header: newHeader(rw.Header()),+ req: NewIncomingRequest(req),+ }++ // The `net/http` package recovers handler panics, but we cannot rely on that behavior here.+ // The reason is, we might need to run After/Commit stages of the interceptors before we+ // respond with a 500 Internal Server Error.+ defer func() {+ if r := recover(); r != nil {+ t.WriteError(StatusInternalServerError)+ }+ }()++ for _, it := range t.cfg.Interceptors {+ it.Before(t, t.req)+ if t.written {+ return+ }+ }++ t.cfg.Handler.ServeHTTP(t, t.req)+ if !t.written {+ t.NoContent()+ }+}++// Write dispatches the response to the Dispatcher. This will be written to the+// underlying http.ResponseWriter if the Dispatcher decides it's safe to do so.+//+// TODO: replace panics with proper error handling when writing the response fails.+func (t *Task) Write(resp Response) Result {+ if t.written {+ panic("ResponseWriter was already written to")+ }+ t.written = true+ t.commitPhase(resp)++ ct, err := t.cfg.Dispatcher.ContentType(resp)+ if err != nil {+ panic(err)+ }+ t.rw.Header().Set("Content-Type", ct)++ if t.code == 0 {+ t.code = StatusOK+ }+ t.rw.WriteHeader(int(t.code))++ if err := t.cfg.Dispatcher.Write(t.rw, resp); err != nil {+ panic(err)+ }+ return Result{}+}++// NoContent responds with a 204 No Content response.+//+// If the ResponseWriter has already been written to, then this method will panic.+func (t *Task) NoContent() Result {+ if t.written {+ panic("ResponseWriter was already written to")+ }+ t.written = true+ t.commitPhase(NoContentResponse{})+ t.rw.WriteHeader(int(StatusNoContent))+ return Result{}+}++// WriteError writes an error response (400-599) according to the provided+// status code.+//+// If the ResponseWriter has already been written to, then this method will panic.+func (t *Task) WriteError(code StatusCode) Result {+ // TODO: accept custom error responses that need to go through the dispatcher.+ if t.writtenError {+ panic("ResponseWriter.WriteError called twice")+ }+ t.written = true+ t.writtenError = true+ resp := &ErrorResponse{Code: code}+ t.errorPhase(resp)+ http.Error(t.rw, http.StatusText(int(resp.Code)), int(resp.Code))+ return Result{}+}++// Redirect responds with a redirect to the given url, using code as the status code.+//+// If the ResponseWriter has already been written to, then this method will panic.+func (t *Task) Redirect(r *IncomingRequest, url string, code StatusCode) Result {+ if code < 300 || code >= 400 {+ panic("wrong method called")
panic(fmt.Sprintf("wrong method called: redirect with status %d", code))
comment created time in 17 days
Pull request review commentgoogle/go-safeweb
+// Copyright 2020 Google LLC+//+// Licensed under the Apache License, Version 2.0 (the "License");+// you may not use this file except in compliance with the License.+// You may obtain a copy of the License at+//+// https://www.apache.org/licenses/LICENSE-2.0+//+// Unless required by applicable law or agreed to in writing, software+// distributed under the License is distributed on an "AS IS" BASIS,+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+// See the License for the specific language governing permissions and+// limitations under the License.++package safehttp++import (+ "net/http"+)++type Task struct {+ rw http.ResponseWriter+ req *IncomingRequest++ cfg HandlerConfig++ code StatusCode+ header Header++ written bool+ writtenError bool+}++// NewTask creates a ResponseWriter from a safehttp.Dispatcher, an+// http.ResponseWriter and a reference to the current IncomingRequest being served.+// The IncomingRequest will only be used by the commit phase, which only runs when+// the ServeMux is used, and can be passed as nil in tests.+// TODO: remove the IncomingRequest parameter.++// ProperNewTask TODO+func BrokenNewTask(rw http.ResponseWriter, dispatcher Dispatcher) *Task {+ return &Task{+ cfg: HandlerConfig{Dispatcher: dispatcher},+ rw: rw,+ header: newHeader(rw.Header()),+ }+}++type HandlerConfig struct {+ Handler Handler+ Dispatcher Dispatcher+ Interceptors []ConfiguredInterceptor+}++func ProcessRequest(cfg HandlerConfig, rw http.ResponseWriter, req *http.Request) {+ t := &Task{+ cfg: cfg,+ rw: rw,+ header: newHeader(rw.Header()),+ req: NewIncomingRequest(req),+ }++ // The `net/http` package recovers handler panics, but we cannot rely on that behavior here.+ // The reason is, we might need to run After/Commit stages of the interceptors before we+ // respond with a 500 Internal Server Error.+ defer func() {+ if r := recover(); r != nil {+ t.WriteError(StatusInternalServerError)+ }+ }()++ for _, it := range t.cfg.Interceptors {+ it.Before(t, t.req)+ if t.written {+ return+ }+ }++ t.cfg.Handler.ServeHTTP(t, t.req)+ if !t.written {+ t.NoContent()+ }+}++// Write dispatches the response to the Dispatcher. This will be written to the+// underlying http.ResponseWriter if the Dispatcher decides it's safe to do so.+//+// TODO: replace panics with proper error handling when writing the response fails.+func (t *Task) Write(resp Response) Result {+ if t.written {+ panic("ResponseWriter was already written to")+ }+ t.written = true+ t.commitPhase(resp)++ ct, err := t.cfg.Dispatcher.ContentType(resp)+ if err != nil {+ panic(err)+ }+ t.rw.Header().Set("Content-Type", ct)++ if t.code == 0 {+ t.code = StatusOK+ }+ t.rw.WriteHeader(int(t.code))++ if err := t.cfg.Dispatcher.Write(t.rw, resp); err != nil {+ panic(err)+ }+ return Result{}+}++// NoContent responds with a 204 No Content response.+//+// If the ResponseWriter has already been written to, then this method will panic.+func (t *Task) NoContent() Result {+ if t.written {+ panic("ResponseWriter was already written to")+ }+ t.written = true+ t.commitPhase(NoContentResponse{})+ t.rw.WriteHeader(int(StatusNoContent))+ return Result{}+}++// WriteError writes an error response (400-599) according to the provided+// status code.+//+// If the ResponseWriter has already been written to, then this method will panic.+func (t *Task) WriteError(code StatusCode) Result {+ // TODO: accept custom error responses that need to go through the dispatcher.+ if t.writtenError {+ panic("ResponseWriter.WriteError called twice")+ }+ t.written = true+ t.writtenError = true+ resp := &ErrorResponse{Code: code}+ t.errorPhase(resp)+ http.Error(t.rw, http.StatusText(int(resp.Code)), int(resp.Code))+ return Result{}+}++// Redirect responds with a redirect to the given url, using code as the status code.+//+// If the ResponseWriter has already been written to, then this method will panic.+func (t *Task) Redirect(r *IncomingRequest, url string, code StatusCode) Result {+ if code < 300 || code >= 400 {+ panic("wrong method called")+ }+ if t.written {+ panic("ResponseWriter was already written to")+ }+ t.written = true+ http.Redirect(t.rw, r.req, url, int(code))+ return Result{}+}++// Header returns the collection of headers that will be set+// on the response. Headers must be set before writing a+// response (e.g. Write, WriteTemplate).+func (t *Task) Header() Header {+ return t.header+}++// SetCookie adds a Set-Cookie header to the provided ResponseWriter's headers.+// The provided cookie must have a valid Name, otherwise an error will be+// returned.+func (t *Task) SetCookie(c *Cookie) error {
Maybe we should change this to AddCookie?
comment created time in 17 days
Pull request review commentgoogle/go-safeweb
+// Copyright 2020 Google LLC+//+// Licensed under the Apache License, Version 2.0 (the "License");+// you may not use this file except in compliance with the License.+// You may obtain a copy of the License at+//+// https://www.apache.org/licenses/LICENSE-2.0+//+// Unless required by applicable law or agreed to in writing, software+// distributed under the License is distributed on an "AS IS" BASIS,+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+// See the License for the specific language governing permissions and+// limitations under the License.++package safehttp++import (+ "net/http"+)++type Task struct {+ rw http.ResponseWriter+ req *IncomingRequest++ cfg HandlerConfig++ code StatusCode+ header Header++ written bool+ writtenError bool+}++// NewTask creates a ResponseWriter from a safehttp.Dispatcher, an+// http.ResponseWriter and a reference to the current IncomingRequest being served.+// The IncomingRequest will only be used by the commit phase, which only runs when+// the ServeMux is used, and can be passed as nil in tests.+// TODO: remove the IncomingRequest parameter.++// ProperNewTask TODO+func BrokenNewTask(rw http.ResponseWriter, dispatcher Dispatcher) *Task {+ return &Task{+ cfg: HandlerConfig{Dispatcher: dispatcher},+ rw: rw,+ header: newHeader(rw.Header()),+ }+}++type HandlerConfig struct {+ Handler Handler+ Dispatcher Dispatcher+ Interceptors []ConfiguredInterceptor+}++func ProcessRequest(cfg HandlerConfig, rw http.ResponseWriter, req *http.Request) {
Does this need to be exported?
comment created time in 17 days
Pull request review commentgoogle/go-safeweb
+// Copyright 2020 Google LLC+//+// Licensed under the Apache License, Version 2.0 (the "License");+// you may not use this file except in compliance with the License.+// You may obtain a copy of the License at+//+// https://www.apache.org/licenses/LICENSE-2.0+//+// Unless required by applicable law or agreed to in writing, software+// distributed under the License is distributed on an "AS IS" BASIS,+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+// See the License for the specific language governing permissions and+// limitations under the License.++package safehttp++import (+ "net/http"+)++type Task struct {+ rw http.ResponseWriter+ req *IncomingRequest++ cfg HandlerConfig++ code StatusCode+ header Header++ written bool
We are not distinguishing between actual writes and write calls. This means that when we start writing an error we don't know if the response serialization was already started (and thus writing the error is useless).
comment created time in 17 days
Pull request review commentgoogle/go-safeweb
+// Copyright 2020 Google LLC+//+// Licensed under the Apache License, Version 2.0 (the "License");+// you may not use this file except in compliance with the License.+// You may obtain a copy of the License at+//+// https://www.apache.org/licenses/LICENSE-2.0+//+// Unless required by applicable law or agreed to in writing, software+// distributed under the License is distributed on an "AS IS" BASIS,+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+// See the License for the specific language governing permissions and+// limitations under the License.++package safehttp++import (+ "net/http"+)++type Task struct {+ rw http.ResponseWriter+ req *IncomingRequest++ cfg HandlerConfig++ code StatusCode+ header Header++ written bool+ writtenError bool+}++// NewTask creates a ResponseWriter from a safehttp.Dispatcher, an+// http.ResponseWriter and a reference to the current IncomingRequest being served.+// The IncomingRequest will only be used by the commit phase, which only runs when+// the ServeMux is used, and can be passed as nil in tests.+// TODO: remove the IncomingRequest parameter.++// ProperNewTask TODO+func BrokenNewTask(rw http.ResponseWriter, dispatcher Dispatcher) *Task {+ return &Task{+ cfg: HandlerConfig{Dispatcher: dispatcher},+ rw: rw,+ header: newHeader(rw.Header()),+ }+}++type HandlerConfig struct {+ Handler Handler+ Dispatcher Dispatcher+ Interceptors []ConfiguredInterceptor+}++func ProcessRequest(cfg HandlerConfig, rw http.ResponseWriter, req *http.Request) {+ t := &Task{+ cfg: cfg,+ rw: rw,+ header: newHeader(rw.Header()),+ req: NewIncomingRequest(req),+ }++ // The `net/http` package recovers handler panics, but we cannot rely on that behavior here.+ // The reason is, we might need to run After/Commit stages of the interceptors before we+ // respond with a 500 Internal Server Error.+ defer func() {+ if r := recover(); r != nil {+ t.WriteError(StatusInternalServerError)+ }+ }()++ for _, it := range t.cfg.Interceptors {+ it.Before(t, t.req)+ if t.written {+ return+ }+ }++ t.cfg.Handler.ServeHTTP(t, t.req)+ if !t.written {+ t.NoContent()+ }+}++// Write dispatches the response to the Dispatcher. This will be written to the+// underlying http.ResponseWriter if the Dispatcher decides it's safe to do so.+//+// TODO: replace panics with proper error handling when writing the response fails.+func (t *Task) Write(resp Response) Result {+ if t.written {+ panic("ResponseWriter was already written to")+ }+ t.written = true+ t.commitPhase(resp)++ ct, err := t.cfg.Dispatcher.ContentType(resp)+ if err != nil {+ panic(err)+ }+ t.rw.Header().Set("Content-Type", ct)++ if t.code == 0 {+ t.code = StatusOK+ }+ t.rw.WriteHeader(int(t.code))++ if err := t.cfg.Dispatcher.Write(t.rw, resp); err != nil {+ panic(err)+ }+ return Result{}+}++// NoContent responds with a 204 No Content response.+//+// If the ResponseWriter has already been written to, then this method will panic.+func (t *Task) NoContent() Result {+ if t.written {+ panic("ResponseWriter was already written to")+ }+ t.written = true+ t.commitPhase(NoContentResponse{})+ t.rw.WriteHeader(int(StatusNoContent))+ return Result{}+}++// WriteError writes an error response (400-599) according to the provided+// status code.+//+// If the ResponseWriter has already been written to, then this method will panic.+func (t *Task) WriteError(code StatusCode) Result {+ // TODO: accept custom error responses that need to go through the dispatcher.+ if t.writtenError {+ panic("ResponseWriter.WriteError called twice")+ }+ t.written = true
See comment I left on the Task type: at this point we should know if we already started writing or not.
Depending on the answer we might have to skip the http.Error call on line 139.
comment created time in 17 days
Pull request review commentgoogle/go-safeweb
+// Copyright 2020 Google LLC+//+// Licensed under the Apache License, Version 2.0 (the "License");+// you may not use this file except in compliance with the License.+// You may obtain a copy of the License at+//+// https://www.apache.org/licenses/LICENSE-2.0+//+// Unless required by applicable law or agreed to in writing, software+// distributed under the License is distributed on an "AS IS" BASIS,+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+// See the License for the specific language governing permissions and+// limitations under the License.++package safehttp++import (+ "net/http"+)++type Task struct {+ rw http.ResponseWriter+ req *IncomingRequest++ cfg HandlerConfig++ code StatusCode+ header Header++ written bool+ writtenError bool+}++// NewTask creates a ResponseWriter from a safehttp.Dispatcher, an+// http.ResponseWriter and a reference to the current IncomingRequest being served.+// The IncomingRequest will only be used by the commit phase, which only runs when+// the ServeMux is used, and can be passed as nil in tests.+// TODO: remove the IncomingRequest parameter.++// ProperNewTask TODO+func BrokenNewTask(rw http.ResponseWriter, dispatcher Dispatcher) *Task {+ return &Task{+ cfg: HandlerConfig{Dispatcher: dispatcher},+ rw: rw,+ header: newHeader(rw.Header()),+ }+}++type HandlerConfig struct {+ Handler Handler+ Dispatcher Dispatcher+ Interceptors []ConfiguredInterceptor+}++func ProcessRequest(cfg HandlerConfig, rw http.ResponseWriter, req *http.Request) {+ t := &Task{+ cfg: cfg,+ rw: rw,+ header: newHeader(rw.Header()),+ req: NewIncomingRequest(req),+ }++ // The `net/http` package recovers handler panics, but we cannot rely on that behavior here.+ // The reason is, we might need to run After/Commit stages of the interceptors before we+ // respond with a 500 Internal Server Error.+ defer func() {+ if r := recover(); r != nil {+ t.WriteError(StatusInternalServerError)+ }+ }()++ for _, it := range t.cfg.Interceptors {+ it.Before(t, t.req)+ if t.written {+ return+ }+ }++ t.cfg.Handler.ServeHTTP(t, t.req)+ if !t.written {+ t.NoContent()+ }+}++// Write dispatches the response to the Dispatcher. This will be written to the+// underlying http.ResponseWriter if the Dispatcher decides it's safe to do so.+//+// TODO: replace panics with proper error handling when writing the response fails.+func (t *Task) Write(resp Response) Result {+ if t.written {+ panic("ResponseWriter was already written to")+ }+ t.written = true+ t.commitPhase(resp)++ ct, err := t.cfg.Dispatcher.ContentType(resp)+ if err != nil {+ panic(err)+ }+ t.rw.Header().Set("Content-Type", ct)++ if t.code == 0 {+ t.code = StatusOK+ }+ t.rw.WriteHeader(int(t.code))++ if err := t.cfg.Dispatcher.Write(t.rw, resp); err != nil {+ panic(err)+ }+ return Result{}+}++// NoContent responds with a 204 No Content response.+//+// If the ResponseWriter has already been written to, then this method will panic.+func (t *Task) NoContent() Result {+ if t.written {+ panic("ResponseWriter was already written to")+ }+ t.written = true+ t.commitPhase(NoContentResponse{})+ t.rw.WriteHeader(int(StatusNoContent))+ return Result{}+}
Question for another PR: should we maybe make NoContent a type to pass to Write instead?
comment created time in 17 days
Pull request review commentgoogle/go-safeweb
func (s *ServeMuxConfig) Intercept(i Interceptor) { // Mux returns the ServeMux with a copy of the current configuration. func (s *ServeMuxConfig) Mux() *ServeMux {- m := newServeMux(s.dispatcher)- for _, it := range s.interceptors {- m.install(it)+ dispatcher := s.dispatcher+ if dispatcher == nil {+ dispatcher = DefaultDispatcher{} }+ // pattern -> method -> HandlerConfig+ handlers := map[string]map[string]HandlerConfig{} for _, hr := range s.handlers {- m.handle(hr.pattern, hr.method, hr.handler, hr.cfgs...)+ if handlers[hr.pattern] == nil {+ handlers[hr.pattern] = map[string]HandlerConfig{}+ }+ if _, ok := handlers[hr.pattern][hr.method]; ok {+ panic(fmt.Sprintf("double registration of (pattern = %q, method = %q)", hr.pattern, hr.method))+ }+ handlers[hr.pattern][hr.method] =+ HandlerConfig{+ Dispatcher: dispatcher,+ Handler: hr.handler,+ Interceptors: configureInterceptors(s.interceptors, hr.cfgs),+ } }- return m+ m := http.NewServeMux()+ registerHandlers(m, handlers)+ return &ServeMux{mux: m}+}++func configureInterceptors(interceptors []Interceptor, cfgs []InterceptorConfig) []ConfiguredInterceptor {+ var its []ConfiguredInterceptor+ for _, it := range interceptors {+ var cfg InterceptorConfig+ for _, c := range cfgs {+ if c.Match(it) {
Shouldn't we validate there is at most one cfg per interceptor?
comment created time in 17 days
Pull request review commentgoogle/go-safeweb
+// Copyright 2020 Google LLC+//+// Licensed under the Apache License, Version 2.0 (the "License");+// you may not use this file except in compliance with the License.+// You may obtain a copy of the License at+//+// https://www.apache.org/licenses/LICENSE-2.0+//+// Unless required by applicable law or agreed to in writing, software+// distributed under the License is distributed on an "AS IS" BASIS,+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+// See the License for the specific language governing permissions and+// limitations under the License.++package safehttp++import (+ "net/http"+)++type Task struct {
I am not sure I like the "Task" name but I kinda like that we wrapped an HTTP roundtrip into a single thing/action.
I think we should still name it along the lines of ResponseWriter or users will get confused/alienated at first.
comment created time in 17 days
Pull request review commentgoogle/go-safeweb
func (s *ServeMuxConfig) Intercept(i Interceptor) { // Mux returns the ServeMux with a copy of the current configuration. func (s *ServeMuxConfig) Mux() *ServeMux {- m := newServeMux(s.dispatcher)- for _, it := range s.interceptors {- m.install(it)+ dispatcher := s.dispatcher+ if dispatcher == nil {+ dispatcher = DefaultDispatcher{} }+ // pattern -> method -> HandlerConfig+ handlers := map[string]map[string]HandlerConfig{} for _, hr := range s.handlers {- m.handle(hr.pattern, hr.method, hr.handler, hr.cfgs...)+ if handlers[hr.pattern] == nil {+ handlers[hr.pattern] = map[string]HandlerConfig{}+ }+ if _, ok := handlers[hr.pattern][hr.method]; ok {+ panic(fmt.Sprintf("double registration of (pattern = %q, method = %q)", hr.pattern, hr.method))
Can't we check this at registration time?
comment created time in 17 days
Pull request review commentgoogle/go-safeweb
const ( // Multiple handlers can be registered for a single pattern, as long as they // handle different HTTP methods. type ServeMux struct {- mux *http.ServeMux- disp Dispatcher-- // Maps patterns to handlers supporting multiple HTTP methods.- handlers map[string]standardHandler- interceps []Interceptor-}--// newServeMux allocates and returns a new ServeMux. If the provided dispatcher-// is nil, the DefaultDispatcher is used.-func newServeMux(d Dispatcher) *ServeMux {- if d == nil {- d = DefaultDispatcher{}- }- return &ServeMux{- mux: http.NewServeMux(),- disp: d,- handlers: map[string]standardHandler{},- }-}--func (m *ServeMux) handle(pattern string, method string, h Handler, cfgs ...InterceptorConfig) {- var interceps []ConfiguredInterceptor- for _, it := range m.interceps {- var cfg InterceptorConfig- for _, c := range cfgs {- if c.Match(it) {- cfg = c- break+ mux *http.ServeMux+}++func registerHandlers(mux *http.ServeMux, handlers map[string]map[string]HandlerConfig) {+ for pattern, handlersPerMethod := range handlers {+ pattern := pattern
This is not needed.
comment created time in 17 days