Go API Documentation

github.com/caddyserver/caddy/v2/modules/caddyhttp/headers

No package summary is available.

Package

Files: 2. Third party imports: 0. Imports from organisation: 0. Tests: 0. Benchmarks: 0.

Vars

var (
	_	caddy.Provisioner		= (*Handler)(nil)
	_	caddyhttp.MiddlewareHandler	= (*Handler)(nil)
	_	http.ResponseWriter		= (*responseWriterWrapper)(nil)
)

Types

Handler

Handler is a middleware which modifies request and response headers.

Changes to headers are applied immediately, except for the response headers when Deferred is true or when Required is set. In those cases, the changes are applied when the headers are written to the response. Note that deferred changes do not take effect if an error occurs later in the middleware chain.

Properties in this module accept placeholders.

Response header operations can be conditioned upon response status code and/or other header values.

type Handler struct {
	Request		*HeaderOps	`json:"request,omitempty"`
	Response	*RespHeaderOps	`json:"response,omitempty"`
}

HeaderOps

HeaderOps defines manipulations for HTTP headers.

type HeaderOps struct {
	// Adds HTTP headers; does not replace any existing header fields.
	Add	http.Header	`json:"add,omitempty"`

	// Sets HTTP headers; replaces existing header fields.
	Set	http.Header	`json:"set,omitempty"`

	// Names of HTTP header fields to delete. Basic wildcards are supported:
	//
	// - Start with `*` for all field names with the given suffix;
	// - End with `*` for all field names with the given prefix;
	// - Start and end with `*` for all field names containing a substring.
	Delete	[]string	`json:"delete,omitempty"`

	// Performs in-situ substring replacements of HTTP headers.
	// Keys are the field names on which to perform the associated replacements.
	// If the field name is `*`, the replacements are performed on all header fields.
	Replace	map[string][]Replacement	`json:"replace,omitempty"`
}

Replacement

Replacement describes a string replacement, either a simple and fast substring search or a slower but more powerful regex search.

type Replacement struct {
	// The substring to search for.
	Search	string	`json:"search,omitempty"`

	// The regular expression to search with.
	SearchRegexp	string	`json:"search_regexp,omitempty"`

	// The string with which to replace matches.
	Replace	string	`json:"replace,omitempty"`

	re	*regexp.Regexp
}

RespHeaderOps

RespHeaderOps defines manipulations for response headers.

type RespHeaderOps struct {
	*HeaderOps

	// If set, header operations will be deferred until
	// they are written out and only performed if the
	// response matches these criteria.
	Require	*caddyhttp.ResponseMatcher	`json:"require,omitempty"`

	// If true, header operations will be deferred until
	// they are written out. Superseded if Require is set.
	// Usually you will need to set this to true if any
	// fields are being deleted.
	Deferred	bool	`json:"deferred,omitempty"`
}

responseWriterWrapper

responseWriterWrapper defers response header operations until WriteHeader is called.

type responseWriterWrapper struct {
	*caddyhttp.ResponseWriterWrapper
	replacer	*caddy.Replacer
	require		*caddyhttp.ResponseMatcher
	headerOps	*HeaderOps
	wroteHeader	bool
}

Functions

func CaddyfileHeaderOp

CaddyfileHeaderOp applies a new header operation according to field, value, and replacement. The field can be prefixed with "+" or "-" to specify adding or removing; otherwise, the value will be set (overriding any previous value). If replacement is non-nil, value will be treated as a regular expression which will be used to search and then replacement will be used to complete the substring replacement; in that case, any + or - prefix to field will be ignored.

func CaddyfileHeaderOp(ops *HeaderOps, field, value string, replacement *string) error {
	return applyHeaderOp(ops, nil, field, value, replacement)
}

Cognitive complexity: 0, Cyclomatic complexity: 1

func (*Handler) Provision

Provision sets up h's configuration.

func (h *Handler) Provision(ctx caddy.Context) error {
	if h.Request != nil {
		err := h.Request.Provision(ctx)
		if err != nil {
			return err
		}
	}
	if h.Response != nil {
		err := h.Response.Provision(ctx)
		if err != nil {
			return err
		}
	}
	return nil
}

Cognitive complexity: 8, Cyclomatic complexity: 5

func (*responseWriterWrapper) Write

func (rww *responseWriterWrapper) Write(d []byte) (int, error) {
	if !rww.wroteHeader {
		rww.WriteHeader(http.StatusOK)
	}
	return rww.ResponseWriterWrapper.Write(d)
}

Cognitive complexity: 2, Cyclomatic complexity: 2

Uses: http.StatusOK.

func (*responseWriterWrapper) WriteHeader

func (rww *responseWriterWrapper) WriteHeader(status int) {
	if rww.wroteHeader {
		return
	}
	// 1xx responses aren't final; just informational
	if status < 100 || status > 199 {
		rww.wroteHeader = true
	}
	if rww.require == nil || rww.require.Match(status, rww.ResponseWriterWrapper.Header()) {
		if rww.headerOps != nil {
			rww.headerOps.ApplyTo(rww.ResponseWriterWrapper.Header(), rww.replacer)
		}
	}
	rww.ResponseWriterWrapper.WriteHeader(status)
}

Cognitive complexity: 8, Cyclomatic complexity: 7

func (Handler) CaddyModule

CaddyModule returns the Caddy module information.

func (Handler) CaddyModule() caddy.ModuleInfo {
	return caddy.ModuleInfo{
		ID:	"http.handlers.headers",
		New:	func() caddy.Module { return new(Handler) },
	}
}

Cognitive complexity: 2, Cyclomatic complexity: 1

func (Handler) ServeHTTP

func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
	repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)

	if h.Request != nil {
		h.Request.ApplyToRequest(r)
	}

	if h.Response != nil {
		if h.Response.Deferred || h.Response.Require != nil {
			w = &responseWriterWrapper{
				ResponseWriterWrapper:	&caddyhttp.ResponseWriterWrapper{ResponseWriter: w},
				replacer:		repl,
				require:		h.Response.Require,
				headerOps:		h.Response.HeaderOps,
			}
		} else {
			h.Response.ApplyTo(w.Header(), repl)
		}
	}

	return next.ServeHTTP(w, r)
}

Cognitive complexity: 10, Cyclomatic complexity: 5

Uses: caddyhttp.ResponseWriterWrapper.

func (Handler) Validate

Validate ensures h's configuration is valid.

func (h Handler) Validate() error {
	if h.Request != nil {
		err := h.Request.validate()
		if err != nil {
			return err
		}
	}
	if h.Response != nil {
		err := h.Response.validate()
		if err != nil {
			return err
		}
	}
	return nil
}

Cognitive complexity: 8, Cyclomatic complexity: 5

func (HeaderOps) ApplyTo

ApplyTo applies ops to hdr using repl.

func (ops HeaderOps) ApplyTo(hdr http.Header, repl *caddy.Replacer) {
	// before manipulating headers in other ways, check if there
	// is configuration to delete all headers, and do that first
	// because if a header is to be added, we don't want to delete
	// it also
	for _, fieldName := range ops.Delete {
		fieldName = repl.ReplaceKnown(fieldName, "")
		if fieldName == "*" {
			clear(hdr)
		}
	}

	// add
	for fieldName, vals := range ops.Add {
		fieldName = repl.ReplaceKnown(fieldName, "")
		for _, v := range vals {
			hdr.Add(fieldName, repl.ReplaceKnown(v, ""))
		}
	}

	// set
	for fieldName, vals := range ops.Set {
		fieldName = repl.ReplaceKnown(fieldName, "")
		var newVals []string
		for i := range vals {
			// append to new slice so we don't overwrite
			// the original values in ops.Set
			newVals = append(newVals, repl.ReplaceKnown(vals[i], ""))
		}
		hdr.Set(fieldName, strings.Join(newVals, ","))
	}

	// delete
	for _, fieldName := range ops.Delete {
		fieldName = strings.ToLower(repl.ReplaceKnown(fieldName, ""))
		if fieldName == "*" {
			continue	// handled above
		}
		switch {
		case strings.HasPrefix(fieldName, "*") && strings.HasSuffix(fieldName, "*"):
			for existingField := range hdr {
				if strings.Contains(strings.ToLower(existingField), fieldName[1:len(fieldName)-1]) {
					delete(hdr, existingField)
				}
			}
		case strings.HasPrefix(fieldName, "*"):
			for existingField := range hdr {
				if strings.HasSuffix(strings.ToLower(existingField), fieldName[1:]) {
					delete(hdr, existingField)
				}
			}
		case strings.HasSuffix(fieldName, "*"):
			for existingField := range hdr {
				if strings.HasPrefix(strings.ToLower(existingField), fieldName[:len(fieldName)-1]) {
					delete(hdr, existingField)
				}
			}
		default:
			hdr.Del(fieldName)
		}
	}

	// replace
	for fieldName, replacements := range ops.Replace {
		fieldName = http.CanonicalHeaderKey(repl.ReplaceKnown(fieldName, ""))

		// all fields...
		if fieldName == "*" {
			for _, r := range replacements {
				search := repl.ReplaceKnown(r.Search, "")
				replace := repl.ReplaceKnown(r.Replace, "")
				for fieldName, vals := range hdr {
					for i := range vals {
						if r.re != nil {
							hdr[fieldName][i] = r.re.ReplaceAllString(hdr[fieldName][i], replace)
						} else {
							hdr[fieldName][i] = strings.ReplaceAll(hdr[fieldName][i], search, replace)
						}
					}
				}
			}
			continue
		}

		// ...or only with the named field
		for _, r := range replacements {
			search := repl.ReplaceKnown(r.Search, "")
			replace := repl.ReplaceKnown(r.Replace, "")
			for hdrFieldName, vals := range hdr {
				// see issue #4330 for why we don't simply use hdr[fieldName]
				if http.CanonicalHeaderKey(hdrFieldName) != fieldName {
					continue
				}
				for i := range vals {
					if r.re != nil {
						hdr[hdrFieldName][i] = r.re.ReplaceAllString(hdr[hdrFieldName][i], replace)
					} else {
						hdr[hdrFieldName][i] = strings.ReplaceAll(hdr[hdrFieldName][i], search, replace)
					}
				}
			}
		}
	}
}

Cognitive complexity: 75, Cyclomatic complexity: 31

Uses: http.CanonicalHeaderKey, strings.Contains, strings.HasPrefix, strings.HasSuffix, strings.Join, strings.ReplaceAll, strings.ToLower.

func (HeaderOps) ApplyToRequest

ApplyToRequest applies ops to r, specially handling the Host header which the standard library does not include with the header map with all the others. This method mutates r.Host.

func (ops HeaderOps) ApplyToRequest(r *http.Request) {
	repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)

	// capture the current Host header so we can
	// reset to it when we're done
	origHost, hadHost := r.Header["Host"]

	// append r.Host; this way, we know that our value
	// was last in the list, and if an Add operation
	// appended something else after it, that's probably
	// fine because it's weird to have multiple Host
	// headers anyway and presumably the one they added
	// is the one they wanted
	r.Header["Host"] = append(r.Header["Host"], r.Host)

	// apply header operations
	ops.ApplyTo(r.Header, repl)

	// retrieve the last Host value (likely the one we appended)
	if len(r.Header["Host"]) > 0 {
		r.Host = r.Header["Host"][len(r.Header["Host"])-1]
	} else {
		r.Host = ""
	}

	// reset the Host header slice
	if hadHost {
		r.Header["Host"] = origHost
	} else {
		delete(r.Header, "Host")
	}
}

Cognitive complexity: 8, Cyclomatic complexity: 3

Private functions

func applyHeaderOp

applyHeaderOp (ops *HeaderOps, respHeaderOps *RespHeaderOps, field,value string, replacement *string) error
References: caddyhttp.ResponseMatcher, fmt.Errorf, http.Header, strings.HasPrefix, strings.TrimLeft, strings.TrimPrefix.

func init

init ()
References: httpcaddyfile.RegisterDirective.

func parseCaddyfile

parseCaddyfile sets up the handler for response headers from Caddyfile tokens. Syntax:

header [<matcher>] [[+|-|?|>]<field> [<value|regexp>] [<replacement>]] {
	[+]<field> [<value|regexp> [<replacement>]]
	?<field> <default_value>
	-<field>
	><field>
	[defer]
}

Either a block can be opened or a single header field can be configured in the first line, but not both in the same directive. Header operations are deferred to write-time if any headers are being deleted or if the 'defer' subdirective is used. + appends a header value, - deletes a field, ? conditionally sets a value only if the header field is not already set, and > sets a field with defer enabled.

parseCaddyfile (h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error)
References: httpcaddyfile.ConfigValue, reflect.DeepEqual, strings.HasPrefix, strings.TrimSuffix.

func parseReqHdrCaddyfile

parseReqHdrCaddyfile sets up the handler for request headers from Caddyfile tokens. Syntax:

request_header [<matcher>] [[+|-]<field> [<value|regexp>] [<replacement>]]

parseReqHdrCaddyfile (h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error)
References: httpcaddyfile.ConfigValue, strings.TrimSuffix.

func validate

validate () error
References: fmt.Errorf.


Tests

Files: 1. Third party imports: 0. Imports from organisation: 0. Tests: 1. Benchmarks: 0.

Types

nextHandler

This type doesn't have documentation.

type nextHandler func(http.ResponseWriter, *http.Request) error

Test functions

TestHandler

References: caddyhttp.ResponseMatcher, context.WithValue, fmt.Errorf, http.Header, http.Request, http.ResponseWriter, httptest.NewRecorder, reflect.DeepEqual.