// 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 caddyhttp import ( "context" "crypto/tls" "crypto/x509" "encoding/pem" "net" "net/http" "net/http/httptest" "testing" "github.com/caddyserver/caddy/v2" ) func TestHTTPVarReplacement(t *testing.T) { req, _ := http.NewRequest(http.MethodGet, "/foo/bar.tar.gz", nil) repl := caddy.NewReplacer() localAddr, _ := net.ResolveTCPAddr("tcp", "192.168.159.1:80") ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl) ctx = context.WithValue(ctx, http.LocalAddrContextKey, localAddr) req = req.WithContext(ctx) req.Host = "example.com:80" req.RemoteAddr = "192.168.159.32:1234" clientCert := []byte(`-----BEGIN CERTIFICATE----- MIIB9jCCAV+gAwIBAgIBAjANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDDA1DYWRk eSBUZXN0IENBMB4XDTE4MDcyNDIxMzUwNVoXDTI4MDcyMTIxMzUwNVowHTEbMBkG A1UEAwwSY2xpZW50LmxvY2FsZG9tYWluMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB iQKBgQDFDEpzF0ew68teT3xDzcUxVFaTII+jXH1ftHXxxP4BEYBU4q90qzeKFneF z83I0nC0WAQ45ZwHfhLMYHFzHPdxr6+jkvKPASf0J2v2HDJuTM1bHBbik5Ls5eq+ fVZDP8o/VHKSBKxNs8Goc2NTsr5b07QTIpkRStQK+RJALk4x9QIDAQABo0swSTAJ BgNVHRMEAjAAMAsGA1UdDwQEAwIHgDAaBgNVHREEEzARgglsb2NhbGhvc3SHBH8A AAEwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDQYJKoZIhvcNAQELBQADgYEANSjz2Sk+ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV 3Q9fgDkiUod+uIK0IynzIKvw+Cjg+3nx6NQ0IM0zo8c7v398RzB4apbXKZyeeqUH 9fNwfEi+OoXR6s+upSKobCmLGLGi9Na5s5g= -----END CERTIFICATE-----`) block, _ := pem.Decode(clientCert) if block == nil { t.Fatalf("failed to decode PEM certificate") } cert, err := x509.ParseCertificate(block.Bytes) if err != nil { t.Fatalf("failed to decode PEM certificate: %v", err) } req.TLS = &tls.ConnectionState{ Version: tls.VersionTLS13, HandshakeComplete: true, ServerName: "example.com", CipherSuite: tls.TLS_AES_256_GCM_SHA384, PeerCertificates: []*x509.Certificate{cert}, NegotiatedProtocol: "h2", NegotiatedProtocolIsMutual: true, } res := httptest.NewRecorder() addHTTPVarsToReplacer(repl, req, res) for i, tc := range []struct { get string expect string }{ { get: "http.request.scheme", expect: "https", }, { get: "http.request.method", expect: http.MethodGet, }, { get: "http.request.host", expect: "example.com", }, { get: "http.request.port", expect: "80", }, { get: "http.request.hostport", expect: "example.com:80", }, { get: "http.request.local.host", expect: "192.168.159.1", }, { get: "http.request.local.port", expect: "80", }, { get: "http.request.local", expect: "192.168.159.1:80", }, { get: "http.request.remote.host", expect: "192.168.159.32", }, { get: "http.request.remote.host/24", expect: "192.168.159.0/24", }, { get: "http.request.remote.host/24,32", expect: "192.168.159.0/24", }, { get: "http.request.remote.host/999", expect: "", }, { get: "http.request.remote.port", expect: "1234", }, { get: "http.request.host.labels.0", expect: "com", }, { get: "http.request.host.labels.1", expect: "example", }, { get: "http.request.host.labels.2", expect: "", }, { get: "http.request.uri.path.file", expect: "bar.tar.gz", }, { get: "http.request.uri.path.file.base", expect: "bar.tar", }, { // not ideal, but also most correct, given that files can have dots (example: index..html) TODO: maybe this isn't right.. get: "http.request.uri.path.file.ext", expect: ".gz", }, { get: "http.request.tls.cipher_suite", expect: "TLS_AES_256_GCM_SHA384", }, { get: "http.request.tls.proto", expect: "h2", }, { get: "http.request.tls.proto_mutual", expect: "true", }, { get: "http.request.tls.resumed", expect: "false", }, { get: "http.request.tls.server_name", expect: "example.com", }, { get: "http.request.tls.version", expect: "tls1.3", }, { get: "http.request.tls.client.fingerprint", expect: "9f57b7b497cceacc5459b76ac1c3afedbc12b300e728071f55f84168ff0f7702", }, { get: "http.request.tls.client.issuer", expect: "CN=Caddy Test CA", }, { get: "http.request.tls.client.serial", expect: "2", }, { get: "http.request.tls.client.subject", expect: "CN=client.localdomain", }, { get: "http.request.tls.client.san.dns_names", expect: "[localhost]", }, { get: "http.request.tls.client.san.dns_names.0", expect: "localhost", }, { get: "http.request.tls.client.san.dns_names.1", expect: "", }, { get: "http.request.tls.client.san.ips", expect: "[127.0.0.1]", }, { get: "http.request.tls.client.san.ips.0", expect: "127.0.0.1", }, { get: "http.request.tls.client.certificate_pem", expect: string(clientCert) + "\n", // returned value comes with a newline appended to it }, } { actual, got := repl.GetString(tc.get) if !got { t.Errorf("Test %d: Expected to recognize the placeholder name, but didn't", i) } if actual != tc.expect { t.Errorf("Test %d: Expected %s to be '%s' but got '%s'", i, tc.get, tc.expect, actual) } } }