2019-08-09 13:05:47 -05:00
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// 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
//
// http://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 headers
import (
2020-11-20 14:38:16 -05:00
"fmt"
2019-08-09 13:05:47 -05:00
"net/http"
2020-11-20 14:38:16 -05:00
"reflect"
2019-08-09 13:05:47 -05:00
"strings"
2019-08-09 13:19:56 -05:00
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
2019-08-21 11:46:35 -05:00
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
2019-08-09 13:05:47 -05:00
)
2019-08-21 11:46:35 -05:00
func init ( ) {
2020-11-20 14:38:16 -05:00
httpcaddyfile . RegisterDirective ( "header" , parseCaddyfile )
httpcaddyfile . RegisterDirective ( "request_header" , parseReqHdrCaddyfile )
2019-08-21 11:46:35 -05:00
}
2019-09-11 19:48:37 -05:00
// parseCaddyfile sets up the handler for response headers from
// Caddyfile tokens. Syntax:
2019-08-09 13:05:47 -05:00
//
2020-11-20 14:38:16 -05:00
// header [<matcher>] [[+|-|?]<field> [<value|regexp>] [<replacement>]] {
2019-09-20 14:13:49 -05:00
// [+]<field> [<value|regexp> [<replacement>]]
2020-11-20 14:38:16 -05:00
// ?<field> <default_value>
2019-09-20 14:13:49 -05:00
// -<field>
2020-02-04 13:05:32 -05:00
// [defer]
2019-08-09 13:05:47 -05:00
// }
//
// Either a block can be opened or a single header field can be configured
2020-02-04 13:05:32 -05:00
// 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
2020-11-20 14:38:16 -05:00
// 'defer' subdirective is used. + appends a header value, - deletes a field,
// and ? conditionally sets a value only if the header field is not already
// set.
func parseCaddyfile ( h httpcaddyfile . Helper ) ( [ ] httpcaddyfile . ConfigValue , error ) {
2020-11-30 12:20:30 -05:00
if ! h . Next ( ) {
return nil , h . ArgErr ( )
}
2020-11-20 14:38:16 -05:00
matcherSet , err := h . ExtractMatcherSet ( )
if err != nil {
return nil , err
}
makeHandler := func ( ) Handler {
return Handler {
Response : & RespHeaderOps {
HeaderOps : & HeaderOps { } ,
} ,
2019-09-20 14:13:49 -05:00
}
}
2020-11-20 14:38:16 -05:00
handler , handlerWithRequire := makeHandler ( ) , makeHandler ( )
2019-09-20 14:13:49 -05:00
2019-08-21 11:46:35 -05:00
for h . Next ( ) {
2019-08-09 13:05:47 -05:00
// first see if headers are in the initial line
var hasArgs bool
2019-08-21 11:46:35 -05:00
if h . NextArg ( ) {
2019-08-09 13:05:47 -05:00
hasArgs = true
2019-08-21 11:46:35 -05:00
field := h . Val ( )
2019-09-20 14:13:49 -05:00
var value , replacement string
2019-09-11 19:48:37 -05:00
if h . NextArg ( ) {
value = h . Val ( )
}
2019-09-20 14:13:49 -05:00
if h . NextArg ( ) {
replacement = h . Val ( )
}
2020-11-20 14:38:16 -05:00
err := applyHeaderOp (
handler . Response . HeaderOps ,
handler . Response ,
field ,
value ,
replacement ,
)
if err != nil {
return nil , h . Err ( err . Error ( ) )
}
if len ( handler . Response . HeaderOps . Delete ) > 0 {
handler . Response . Deferred = true
2020-02-04 13:05:32 -05:00
}
2019-08-09 13:05:47 -05:00
}
// if not, they should be in a block
2019-09-10 20:21:52 -05:00
for h . NextBlock ( 0 ) {
2020-02-04 13:05:32 -05:00
field := h . Val ( )
if field == "defer" {
2020-11-20 14:38:16 -05:00
handler . Response . Deferred = true
2020-02-04 13:05:32 -05:00
continue
}
2019-08-09 13:05:47 -05:00
if hasArgs {
2020-11-20 14:38:16 -05:00
return nil , h . Err ( "cannot specify headers in both arguments and block" ) // because it would be weird
2019-08-09 13:05:47 -05:00
}
2020-11-20 14:38:16 -05:00
// sometimes it is habitual for users to suffix a field name with a colon,
// as if they were writing a curl command or something; see
// https://caddy.community/t/v2-reverse-proxy-please-add-cors-example-to-the-docs/7349/19
field = strings . TrimSuffix ( field , ":" )
2019-09-20 14:13:49 -05:00
var value , replacement string
2019-08-21 11:46:35 -05:00
if h . NextArg ( ) {
value = h . Val ( )
2019-08-09 13:05:47 -05:00
}
2019-09-20 14:13:49 -05:00
if h . NextArg ( ) {
replacement = h . Val ( )
}
2020-11-20 14:38:16 -05:00
handlerToUse := handler
if strings . HasPrefix ( field , "?" ) {
handlerToUse = handlerWithRequire
}
err := applyHeaderOp (
handlerToUse . Response . HeaderOps ,
handlerToUse . Response ,
field ,
value ,
replacement ,
)
if err != nil {
return nil , h . Err ( err . Error ( ) )
2020-02-04 13:05:32 -05:00
}
2019-08-09 13:05:47 -05:00
}
}
2019-09-20 14:13:49 -05:00
2020-11-20 14:38:16 -05:00
var configValues [ ] httpcaddyfile . ConfigValue
if ! reflect . DeepEqual ( handler , makeHandler ( ) ) {
configValues = append ( configValues , h . NewRoute ( matcherSet , handler ) ... )
}
if ! reflect . DeepEqual ( handlerWithRequire , makeHandler ( ) ) {
configValues = append ( configValues , h . NewRoute ( matcherSet , handlerWithRequire ) ... )
}
return configValues , nil
2019-08-09 13:05:47 -05:00
}
2019-09-11 19:48:37 -05:00
// parseReqHdrCaddyfile sets up the handler for request headers
// from Caddyfile tokens. Syntax:
//
2019-09-20 14:13:49 -05:00
// request_header [<matcher>] [[+|-]<field> [<value|regexp>] [<replacement>]]
2019-09-11 19:48:37 -05:00
//
2020-11-20 14:38:16 -05:00
func parseReqHdrCaddyfile ( h httpcaddyfile . Helper ) ( [ ] httpcaddyfile . ConfigValue , error ) {
matcherSet , err := h . ExtractMatcherSet ( )
if err != nil {
return nil , err
}
configValues := [ ] httpcaddyfile . ConfigValue { }
2019-09-11 19:48:37 -05:00
for h . Next ( ) {
if ! h . NextArg ( ) {
return nil , h . ArgErr ( )
}
field := h . Val ( )
2020-03-30 12:52:11 -05:00
2020-11-20 14:38:16 -05:00
hdr := Handler {
Request : & HeaderOps { } ,
}
2020-03-30 12:52:11 -05:00
// sometimes it is habitual for users to suffix a field name with a colon,
// as if they were writing a curl command or something; see
2020-11-20 14:38:16 -05:00
// https://caddy.community/t/v2-reverse-proxy-please-add-cors-example-to-the-docs/7349/19
2020-03-30 12:52:11 -05:00
field = strings . TrimSuffix ( field , ":" )
2019-09-20 14:13:49 -05:00
var value , replacement string
2019-09-11 19:48:37 -05:00
if h . NextArg ( ) {
value = h . Val ( )
}
2019-09-20 14:13:49 -05:00
if h . NextArg ( ) {
replacement = h . Val ( )
2020-01-06 10:10:20 -05:00
if h . NextArg ( ) {
return nil , h . ArgErr ( )
}
2019-09-20 14:13:49 -05:00
}
2019-09-11 19:48:37 -05:00
if hdr . Request == nil {
hdr . Request = new ( HeaderOps )
}
2020-11-20 14:38:16 -05:00
if err := CaddyfileHeaderOp ( hdr . Request , field , value , replacement ) ; err != nil {
return nil , h . Err ( err . Error ( ) )
}
configValues = append ( configValues , h . NewRoute ( matcherSet , hdr ) ... )
2019-09-11 19:48:37 -05:00
if h . NextArg ( ) {
return nil , h . ArgErr ( )
}
}
2020-11-20 14:38:16 -05:00
return configValues , nil
2019-09-11 19:48:37 -05:00
}
2019-09-20 14:13:49 -05:00
// 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-empty, 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.
2020-11-20 14:38:16 -05:00
func CaddyfileHeaderOp ( ops * HeaderOps , field , value , replacement string ) error {
return applyHeaderOp ( ops , nil , field , value , replacement )
}
func applyHeaderOp ( ops * HeaderOps , respHeaderOps * RespHeaderOps , field , value , replacement string ) error {
switch {
case strings . HasPrefix ( field , "+" ) : // append
2019-09-20 14:13:49 -05:00
if ops . Add == nil {
ops . Add = make ( http . Header )
2019-08-09 13:05:47 -05:00
}
2019-09-20 14:13:49 -05:00
ops . Add . Set ( field [ 1 : ] , value )
2020-11-20 14:38:16 -05:00
case strings . HasPrefix ( field , "-" ) : // delete
2019-09-20 14:13:49 -05:00
ops . Delete = append ( ops . Delete , field [ 1 : ] )
2020-11-20 14:38:16 -05:00
if respHeaderOps != nil {
respHeaderOps . Deferred = true
}
case strings . HasPrefix ( field , "?" ) : // default (conditional on not existing) - response headers only
if respHeaderOps == nil {
return fmt . Errorf ( "%v: the default header modifier ('?') can only be used on response headers; for conditional manipulation of request headers, use matchers" , field )
}
if respHeaderOps . Require == nil {
respHeaderOps . Require = & caddyhttp . ResponseMatcher {
Headers : make ( http . Header ) ,
2019-09-20 14:13:49 -05:00
}
2019-08-09 13:05:47 -05:00
}
2020-11-20 14:38:16 -05:00
field = strings . TrimPrefix ( field , "?" )
respHeaderOps . Require . Headers [ field ] = nil
if respHeaderOps . Set == nil {
respHeaderOps . Set = make ( http . Header )
}
respHeaderOps . Set . Set ( field , value )
case replacement != "" : // replace
if ops . Replace == nil {
ops . Replace = make ( map [ string ] [ ] Replacement )
}
field = strings . TrimLeft ( field , "+-?" )
ops . Replace [ field ] = append (
ops . Replace [ field ] ,
Replacement {
SearchRegexp : value ,
Replace : replacement ,
} ,
)
default : // set (overwrite)
if ops . Set == nil {
ops . Set = make ( http . Header )
}
ops . Set . Set ( field , value )
2019-08-09 13:05:47 -05:00
}
2020-11-20 14:38:16 -05:00
return nil
2019-08-09 13:05:47 -05:00
}