Showing preview only (3,410K chars total). Download the full file or copy to clipboard to get everything.
Repository: caddyserver/caddy
Branch: master
Commit: df65455b1f0d
Files: 588
Total size: 3.1 MB
Directory structure:
gitextract_8o2w0qyu/
├── .editorconfig
├── .gitattributes
├── .github/
│ ├── CONTRIBUTING.md
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── ISSUE.yml
│ │ └── config.yml
│ ├── SECURITY.md
│ ├── dependabot.yml
│ ├── pull_request_template.md
│ └── workflows/
│ ├── ai.yml
│ ├── auto-release-pr.yml
│ ├── ci.yml
│ ├── cross-build.yml
│ ├── lint.yml
│ ├── release-proposal.yml
│ ├── release.yml
│ ├── release_published.yml
│ └── scorecard.yml
├── .gitignore
├── .golangci.yml
├── .goreleaser.yml
├── .pre-commit-config.yaml
├── AUTHORS
├── LICENSE
├── README.md
├── admin.go
├── admin_test.go
├── caddy.go
├── caddy_test.go
├── caddyconfig/
│ ├── caddyfile/
│ │ ├── adapter.go
│ │ ├── dispenser.go
│ │ ├── dispenser_test.go
│ │ ├── formatter.go
│ │ ├── formatter_fuzz.go
│ │ ├── formatter_test.go
│ │ ├── importargs.go
│ │ ├── importgraph.go
│ │ ├── lexer.go
│ │ ├── lexer_fuzz.go
│ │ ├── lexer_test.go
│ │ ├── parse.go
│ │ ├── parse_test.go
│ │ └── testdata/
│ │ ├── empty.txt
│ │ ├── glob/
│ │ │ ├── .dotfile.txt
│ │ │ └── import_test1.txt
│ │ ├── import_args0.txt
│ │ ├── import_args1.txt
│ │ ├── import_glob0.txt
│ │ ├── import_glob1.txt
│ │ ├── import_glob2.txt
│ │ ├── import_recursive0.txt
│ │ ├── import_recursive1.txt
│ │ ├── import_recursive2.txt
│ │ ├── import_recursive3.txt
│ │ ├── import_test1.txt
│ │ ├── import_test2.txt
│ │ └── only_white_space.txt
│ ├── configadapters.go
│ ├── httpcaddyfile/
│ │ ├── addresses.go
│ │ ├── addresses_fuzz.go
│ │ ├── addresses_test.go
│ │ ├── builtins.go
│ │ ├── builtins_test.go
│ │ ├── directives.go
│ │ ├── directives_test.go
│ │ ├── httptype.go
│ │ ├── httptype_test.go
│ │ ├── options.go
│ │ ├── options_test.go
│ │ ├── pkiapp.go
│ │ ├── pkiapp_test.go
│ │ ├── serveroptions.go
│ │ ├── shorthands.go
│ │ ├── testdata/
│ │ │ ├── import_variadic.txt
│ │ │ ├── import_variadic_snippet.txt
│ │ │ └── import_variadic_with_import.txt
│ │ ├── tlsapp.go
│ │ └── tlsapp_test.go
│ ├── httploader.go
│ └── load.go
├── caddytest/
│ ├── a.caddy.localhost.crt
│ ├── a.caddy.localhost.key
│ ├── caddy.ca.cer
│ ├── caddy.localhost.crt
│ ├── caddy.localhost.key
│ ├── caddytest.go
│ ├── caddytest_test.go
│ ├── integration/
│ │ ├── acme_test.go
│ │ ├── acmeserver_test.go
│ │ ├── autohttps_test.go
│ │ ├── caddyfile_adapt/
│ │ │ ├── acme_dns_configured.caddyfiletest
│ │ │ ├── acme_dns_naked_use_dns_defaults.caddyfiletest
│ │ │ ├── acme_dns_naked_without_dns.caddyfiletest
│ │ │ ├── acme_server_custom_challenges.caddyfiletest
│ │ │ ├── acme_server_default_challenges.caddyfiletest
│ │ │ ├── acme_server_lifetime.caddyfiletest
│ │ │ ├── acme_server_multi_custom_challenges.caddyfiletest
│ │ │ ├── acme_server_policy-allow.caddyfiletest
│ │ │ ├── acme_server_policy-both.caddyfiletest
│ │ │ ├── acme_server_policy-deny.caddyfiletest
│ │ │ ├── acme_server_sign_with_root.caddyfiletest
│ │ │ ├── ambiguous_site_definition.caddyfiletest
│ │ │ ├── ambiguous_site_definition_duplicate_key.caddyfiletest
│ │ │ ├── auto_https_disable_redirects.caddyfiletest
│ │ │ ├── auto_https_ignore_loaded_certs.caddyfiletest
│ │ │ ├── auto_https_off.caddyfiletest
│ │ │ ├── bind_fd_fdgram_h123.caddyfiletest
│ │ │ ├── bind_ipv6.caddyfiletest
│ │ │ ├── directive_as_site_address.caddyfiletest
│ │ │ ├── duplicate_listener_address_global.caddyfiletest
│ │ │ ├── enable_tls_for_catch_all_site.caddyfiletest
│ │ │ ├── encode_options.caddyfiletest
│ │ │ ├── error_example.caddyfiletest
│ │ │ ├── error_multi_site_blocks.caddyfiletest
│ │ │ ├── error_range_codes.caddyfiletest
│ │ │ ├── error_range_simple_codes.caddyfiletest
│ │ │ ├── error_simple_codes.caddyfiletest
│ │ │ ├── error_sort.caddyfiletest
│ │ │ ├── error_subhandlers.caddyfiletest
│ │ │ ├── expression_quotes.caddyfiletest
│ │ │ ├── file_server_disable_canonical_uris.caddyfiletest
│ │ │ ├── file_server_etag_file_extensions.caddyfiletest
│ │ │ ├── file_server_file_limit.caddyfiletest
│ │ │ ├── file_server_pass_thru.caddyfiletest
│ │ │ ├── file_server_precompressed.caddyfiletest
│ │ │ ├── file_server_sort.caddyfiletest
│ │ │ ├── file_server_status.caddyfiletest
│ │ │ ├── forward_auth_authelia.caddyfiletest
│ │ │ ├── forward_auth_copy_headers_strip.caddyfiletest
│ │ │ ├── forward_auth_rename_headers.caddyfiletest
│ │ │ ├── global_options.caddyfiletest
│ │ │ ├── global_options_acme.caddyfiletest
│ │ │ ├── global_options_admin.caddyfiletest
│ │ │ ├── global_options_admin_with_persist_config_off.caddyfiletest
│ │ │ ├── global_options_debug_with_access_log.caddyfiletest
│ │ │ ├── global_options_default_bind.caddyfiletest
│ │ │ ├── global_options_log_and_site.caddyfiletest
│ │ │ ├── global_options_log_basic.caddyfiletest
│ │ │ ├── global_options_log_custom.caddyfiletest
│ │ │ ├── global_options_log_multi.caddyfiletest
│ │ │ ├── global_options_log_sampling.caddyfiletest
│ │ │ ├── global_options_persist_config.caddyfiletest
│ │ │ ├── global_options_preferred_chains.caddyfiletest
│ │ │ ├── global_options_resolvers.caddyfiletest
│ │ │ ├── global_options_resolvers_http_challenge.caddyfiletest
│ │ │ ├── global_options_resolvers_local_dns_inherit.caddyfiletest
│ │ │ ├── global_options_resolvers_local_override.caddyfiletest
│ │ │ ├── global_options_resolvers_mixed.caddyfiletest
│ │ │ ├── global_options_skip_install_trust.caddyfiletest
│ │ │ ├── global_server_options_multi.caddyfiletest
│ │ │ ├── global_server_options_single.caddyfiletest
│ │ │ ├── handle_nested_in_route.caddyfiletest
│ │ │ ├── handle_path.caddyfiletest
│ │ │ ├── handle_path_sorting.caddyfiletest
│ │ │ ├── header.caddyfiletest
│ │ │ ├── header_placeholder_search.caddyfiletest
│ │ │ ├── heredoc.caddyfiletest
│ │ │ ├── heredoc_extra_indentation.caddyfiletest
│ │ │ ├── heredoc_incomplete.caddyfiletest
│ │ │ ├── heredoc_invalid_marker.caddyfiletest
│ │ │ ├── heredoc_mismatched_whitespace.caddyfiletest
│ │ │ ├── heredoc_missing_marker.caddyfiletest
│ │ │ ├── heredoc_too_many_angle_brackets.caddyfiletest
│ │ │ ├── http_only_hostnames.caddyfiletest
│ │ │ ├── http_only_on_any_address.caddyfiletest
│ │ │ ├── http_only_on_domain.caddyfiletest
│ │ │ ├── http_only_on_hostless_block.caddyfiletest
│ │ │ ├── http_only_on_localhost.caddyfiletest
│ │ │ ├── http_only_on_non_standard_port.caddyfiletest
│ │ │ ├── http_valid_directive_like_site_address.caddyfiletest
│ │ │ ├── https_on_domain.caddyfiletest
│ │ │ ├── import_args_file.caddyfiletest
│ │ │ ├── import_args_snippet.caddyfiletest
│ │ │ ├── import_args_snippet_env_placeholder.caddyfiletest
│ │ │ ├── import_block_anonymous.caddyfiletest
│ │ │ ├── import_block_snippet.caddyfiletest
│ │ │ ├── import_block_snippet_args.caddyfiletest
│ │ │ ├── import_block_snippet_non_replaced_block.caddyfiletest
│ │ │ ├── import_block_snippet_non_replaced_block_from_separate_file.caddyfiletest
│ │ │ ├── import_block_snippet_non_replaced_key_block.caddyfiletest
│ │ │ ├── import_block_with_site_block.caddyfiletest
│ │ │ ├── import_blocks_snippet.caddyfiletest
│ │ │ ├── import_blocks_snippet_nested.caddyfiletest
│ │ │ ├── import_cycle.caddyfiletest
│ │ │ ├── intercept_response.caddyfiletest
│ │ │ ├── invoke_named_routes.caddyfiletest
│ │ │ ├── invoke_undefined_named_route.caddyfiletest
│ │ │ ├── log_add.caddyfiletest
│ │ │ ├── log_append_encoder.caddyfiletest
│ │ │ ├── log_except_catchall_blocks.caddyfiletest
│ │ │ ├── log_filter_no_wrap.caddyfiletest
│ │ │ ├── log_filter_with_header.txt
│ │ │ ├── log_filters.caddyfiletest
│ │ │ ├── log_multi_logger_name.caddyfiletest
│ │ │ ├── log_multiple_regexp_filters.caddyfiletest
│ │ │ ├── log_override_hostname.caddyfiletest
│ │ │ ├── log_override_name_multiaccess.caddyfiletest
│ │ │ ├── log_override_name_multiaccess_debug.caddyfiletest
│ │ │ ├── log_roll_days.caddyfiletest
│ │ │ ├── log_sampling.caddyfiletest
│ │ │ ├── log_skip_hosts.caddyfiletest
│ │ │ ├── map_and_vars_with_raw_types.caddyfiletest
│ │ │ ├── matcher_outside_site_block.caddyfiletest
│ │ │ ├── matcher_syntax.caddyfiletest
│ │ │ ├── matchers_in_route.caddyfiletest
│ │ │ ├── method_directive.caddyfiletest
│ │ │ ├── metrics_disable_om.caddyfiletest
│ │ │ ├── metrics_merge_options.caddyfiletest
│ │ │ ├── metrics_perhost.caddyfiletest
│ │ │ ├── metrics_syntax.caddyfiletest
│ │ │ ├── not_block_merging.caddyfiletest
│ │ │ ├── php_fastcgi_expanded_form.caddyfiletest
│ │ │ ├── php_fastcgi_handle_response.caddyfiletest
│ │ │ ├── php_fastcgi_index_off.caddyfiletest
│ │ │ ├── php_fastcgi_matcher.caddyfiletest
│ │ │ ├── php_fastcgi_subdirectives.caddyfiletest
│ │ │ ├── php_fastcgi_try_files_override.caddyfiletest
│ │ │ ├── php_fastcgi_try_files_override_no_dir_index.caddyfiletest
│ │ │ ├── portless_upstream.caddyfiletest
│ │ │ ├── push.caddyfiletest
│ │ │ ├── renewal_window_ratio_global.caddyfiletest
│ │ │ ├── renewal_window_ratio_tls_directive.caddyfiletest
│ │ │ ├── replaceable_upstream.caddyfiletest
│ │ │ ├── replaceable_upstream_partial_port.caddyfiletest
│ │ │ ├── replaceable_upstream_port.caddyfiletest
│ │ │ ├── request_body.caddyfiletest
│ │ │ ├── request_header.caddyfiletest
│ │ │ ├── reverse_proxy_buffers.caddyfiletest
│ │ │ ├── reverse_proxy_dynamic_upstreams.caddyfiletest
│ │ │ ├── reverse_proxy_dynamic_upstreams_grace_period.caddyfiletest
│ │ │ ├── reverse_proxy_empty_non_http_transport.caddyfiletest
│ │ │ ├── reverse_proxy_h2c_shorthand.caddyfiletest
│ │ │ ├── reverse_proxy_handle_response.caddyfiletest
│ │ │ ├── reverse_proxy_health_headers.caddyfiletest
│ │ │ ├── reverse_proxy_health_method.caddyfiletest
│ │ │ ├── reverse_proxy_health_path_query.caddyfiletest
│ │ │ ├── reverse_proxy_health_reqbody.caddyfiletest
│ │ │ ├── reverse_proxy_http_transport_forward_proxy_url.txt
│ │ │ ├── reverse_proxy_http_transport_none_proxy.txt
│ │ │ ├── reverse_proxy_http_transport_tls_file_cert.txt
│ │ │ ├── reverse_proxy_http_transport_tls_inline_cert.txt
│ │ │ ├── reverse_proxy_http_transport_url_proxy.txt
│ │ │ ├── reverse_proxy_load_balance.caddyfiletest
│ │ │ ├── reverse_proxy_load_balance_wrr.caddyfiletest
│ │ │ ├── reverse_proxy_localaddr.caddyfiletest
│ │ │ ├── reverse_proxy_options.caddyfiletest
│ │ │ ├── reverse_proxy_port_range.caddyfiletest
│ │ │ ├── reverse_proxy_trusted_proxies.caddyfiletest
│ │ │ ├── reverse_proxy_trusted_proxies_unix.caddyfiletest
│ │ │ ├── reverse_proxy_upstream_placeholder.caddyfiletest
│ │ │ ├── rewrite_directive_permutations.caddyfiletest
│ │ │ ├── root_directive_permutations.caddyfiletest
│ │ │ ├── server_names.caddyfiletest
│ │ │ ├── shorthand_parameterized_placeholders.caddyfiletest
│ │ │ ├── site_address_invalid_port.caddyfiletest
│ │ │ ├── site_address_negative_port.caddyfiletest
│ │ │ ├── site_address_unsupported_scheme.caddyfiletest
│ │ │ ├── site_address_wss_invalid_port.caddyfiletest
│ │ │ ├── site_address_wss_scheme.caddyfiletest
│ │ │ ├── site_block_sorting.caddyfiletest
│ │ │ ├── sort_directives_with_any_matcher_first.caddyfiletest
│ │ │ ├── sort_directives_within_handle.caddyfiletest
│ │ │ ├── sort_vars_in_reverse.caddyfiletest
│ │ │ ├── tls_acme_dns_override_global_dns.caddyfiletest
│ │ │ ├── tls_acme_preferred_chains.caddyfiletest
│ │ │ ├── tls_automation_policies_1.caddyfiletest
│ │ │ ├── tls_automation_policies_10.caddyfiletest
│ │ │ ├── tls_automation_policies_11.caddyfiletest
│ │ │ ├── tls_automation_policies_2.caddyfiletest
│ │ │ ├── tls_automation_policies_3.caddyfiletest
│ │ │ ├── tls_automation_policies_4.caddyfiletest
│ │ │ ├── tls_automation_policies_5.caddyfiletest
│ │ │ ├── tls_automation_policies_6.caddyfiletest
│ │ │ ├── tls_automation_policies_7.caddyfiletest
│ │ │ ├── tls_automation_policies_8.caddyfiletest
│ │ │ ├── tls_automation_policies_9.caddyfiletest
│ │ │ ├── tls_automation_policies_global_email_localhost.caddyfiletest
│ │ │ ├── tls_automation_wildcard_force_automate.caddyfiletest
│ │ │ ├── tls_automation_wildcard_shadowing.caddyfiletest
│ │ │ ├── tls_client_auth_cert_file-legacy-with-verifier.caddyfiletest
│ │ │ ├── tls_client_auth_cert_file-legacy.caddyfiletest
│ │ │ ├── tls_client_auth_cert_file.caddyfiletest
│ │ │ ├── tls_client_auth_inline_cert-legacy.caddyfiletest
│ │ │ ├── tls_client_auth_inline_cert.caddyfiletest
│ │ │ ├── tls_client_auth_inline_cert_with_leaf_trust.caddyfiletest
│ │ │ ├── tls_client_auth_leaf_verifier_file_loader_block.caddyfiletest
│ │ │ ├── tls_client_auth_leaf_verifier_file_loader_inline.caddyfiletest
│ │ │ ├── tls_client_auth_leaf_verifier_file_loader_multi-in-block.caddyfiletest
│ │ │ ├── tls_client_auth_leaf_verifier_folder_loader_block.caddyfiletest
│ │ │ ├── tls_client_auth_leaf_verifier_folder_loader_inline.caddyfiletest
│ │ │ ├── tls_client_auth_leaf_verifier_folder_loader_multi-in-block.caddyfiletest
│ │ │ ├── tls_conn_policy_consolidate.caddyfiletest
│ │ │ ├── tls_dns_multiple_options_without_provider.caddyfiletest
│ │ │ ├── tls_dns_override_acme_dns.caddyfiletest
│ │ │ ├── tls_dns_override_global_dns.caddyfiletest
│ │ │ ├── tls_dns_propagation_timeout_without_provider.caddyfiletest
│ │ │ ├── tls_dns_propagation_without_provider.caddyfiletest
│ │ │ ├── tls_dns_resolvers_with_global_provider.caddyfiletest
│ │ │ ├── tls_dns_ttl.caddyfiletest
│ │ │ ├── tls_explicit_issuer_dns_ttl.caddyfiletest
│ │ │ ├── tls_explicit_issuer_propagation_options.caddyfiletest
│ │ │ ├── tls_internal_options.caddyfiletest
│ │ │ ├── tls_propagation_options.caddyfiletest
│ │ │ ├── tracing.caddyfiletest
│ │ │ ├── uri_query_operations.caddyfiletest
│ │ │ ├── uri_replace_brace_escape.caddyfiletest
│ │ │ └── wildcard_pattern.caddyfiletest
│ │ ├── caddyfile_adapt_test.go
│ │ ├── caddyfile_test.go
│ │ ├── forwardauth_test.go
│ │ ├── h2listener_test.go
│ │ ├── handler_test.go
│ │ ├── intercept_test.go
│ │ ├── leafcertloaders_test.go
│ │ ├── listener_test.go
│ │ ├── map_test.go
│ │ ├── mockdns_test.go
│ │ ├── pki_test.go
│ │ ├── proxyprotocol_test.go
│ │ ├── reverseproxy_test.go
│ │ ├── sni_test.go
│ │ ├── stream_test.go
│ │ └── testdata/
│ │ ├── cookie.html
│ │ ├── foo.txt
│ │ ├── foo_with_multiple_trailing_newlines.txt
│ │ ├── foo_with_trailing_newline.txt
│ │ ├── import_respond.txt
│ │ ├── index.localhost.html
│ │ └── issue_7518_unused_block_panic_snippets.conf
│ └── leafcert.pem
├── cmd/
│ ├── caddy/
│ │ ├── main.go
│ │ └── setcap.sh
│ ├── cobra.go
│ ├── commandfactory.go
│ ├── commandfuncs.go
│ ├── commands.go
│ ├── commands_test.go
│ ├── main.go
│ ├── main_test.go
│ ├── packagesfuncs.go
│ ├── removebinary.go
│ ├── removebinary_windows.go
│ ├── storagefuncs.go
│ └── x509rootsfallback.go
├── context.go
├── context_test.go
├── duration_fuzz.go
├── filepath.go
├── filepath_windows.go
├── filesystem.go
├── go.mod
├── go.sum
├── internal/
│ ├── filesystems/
│ │ ├── map.go
│ │ └── os.go
│ ├── logbuffer.go
│ ├── logs.go
│ ├── metrics/
│ │ ├── metrics.go
│ │ └── metrics_test.go
│ ├── ranges.go
│ ├── sockets.go
│ └── testmocks/
│ └── dummyverifier.go
├── listen.go
├── listen_unix.go
├── listen_unix_setopt.go
├── listen_unix_setopt_freebsd.go
├── listeners.go
├── listeners_fuzz.go
├── listeners_test.go
├── logging.go
├── logging_test.go
├── metrics.go
├── modules/
│ ├── caddyevents/
│ │ ├── app.go
│ │ └── eventsconfig/
│ │ └── caddyfile.go
│ ├── caddyfs/
│ │ └── filesystem.go
│ ├── caddyhttp/
│ │ ├── app.go
│ │ ├── autohttps.go
│ │ ├── caddyauth/
│ │ │ ├── argon2id.go
│ │ │ ├── basicauth.go
│ │ │ ├── bcrypt.go
│ │ │ ├── caddyauth.go
│ │ │ ├── caddyfile.go
│ │ │ └── command.go
│ │ ├── caddyhttp.go
│ │ ├── caddyhttp_test.go
│ │ ├── celmatcher.go
│ │ ├── celmatcher_test.go
│ │ ├── encode/
│ │ │ ├── brotli/
│ │ │ │ └── brotli_precompressed.go
│ │ │ ├── caddyfile.go
│ │ │ ├── encode.go
│ │ │ ├── encode_test.go
│ │ │ ├── gzip/
│ │ │ │ ├── gzip.go
│ │ │ │ └── gzip_precompressed.go
│ │ │ └── zstd/
│ │ │ ├── zstd.go
│ │ │ └── zstd_precompressed.go
│ │ ├── errors.go
│ │ ├── fileserver/
│ │ │ ├── browse.go
│ │ │ ├── browse.html
│ │ │ ├── browsetplcontext.go
│ │ │ ├── browsetplcontext_test.go
│ │ │ ├── caddyfile.go
│ │ │ ├── command.go
│ │ │ ├── matcher.go
│ │ │ ├── matcher_test.go
│ │ │ ├── staticfiles.go
│ │ │ ├── staticfiles_test.go
│ │ │ └── testdata/
│ │ │ ├── %D9%85%D9%84%D9%81.txt
│ │ │ ├── foo.php.php/
│ │ │ │ └── index.php
│ │ │ ├── foo.txt
│ │ │ ├── foodir/
│ │ │ │ ├── bar.txt
│ │ │ │ └── foo.txt
│ │ │ ├── index.php
│ │ │ ├── large.txt
│ │ │ ├── notphp.php.txt
│ │ │ ├── remote.php
│ │ │ └── ملف.txt
│ │ ├── headers/
│ │ │ ├── caddyfile.go
│ │ │ ├── headers.go
│ │ │ └── headers_test.go
│ │ ├── http2listener.go
│ │ ├── httpredirectlistener.go
│ │ ├── intercept/
│ │ │ └── intercept.go
│ │ ├── invoke.go
│ │ ├── ip_matchers.go
│ │ ├── ip_range.go
│ │ ├── logging/
│ │ │ ├── caddyfile.go
│ │ │ └── logappend.go
│ │ ├── logging.go
│ │ ├── map/
│ │ │ ├── caddyfile.go
│ │ │ ├── map.go
│ │ │ └── map_test.go
│ │ ├── marshalers.go
│ │ ├── matchers.go
│ │ ├── matchers_test.go
│ │ ├── metrics.go
│ │ ├── metrics_test.go
│ │ ├── proxyprotocol/
│ │ │ ├── listenerwrapper.go
│ │ │ ├── module.go
│ │ │ └── policy.go
│ │ ├── push/
│ │ │ ├── caddyfile.go
│ │ │ ├── handler.go
│ │ │ ├── link.go
│ │ │ └── link_test.go
│ │ ├── replacer.go
│ │ ├── replacer_test.go
│ │ ├── requestbody/
│ │ │ ├── caddyfile.go
│ │ │ └── requestbody.go
│ │ ├── responsematchers.go
│ │ ├── responsematchers_test.go
│ │ ├── responsewriter.go
│ │ ├── responsewriter_test.go
│ │ ├── reverseproxy/
│ │ │ ├── addresses.go
│ │ │ ├── addresses_test.go
│ │ │ ├── admin.go
│ │ │ ├── admin_test.go
│ │ │ ├── ascii.go
│ │ │ ├── ascii_test.go
│ │ │ ├── buffering_test.go
│ │ │ ├── caddyfile.go
│ │ │ ├── command.go
│ │ │ ├── copyresponse.go
│ │ │ ├── dynamic_upstreams_test.go
│ │ │ ├── fastcgi/
│ │ │ │ ├── caddyfile.go
│ │ │ │ ├── client.go
│ │ │ │ ├── client_test.go
│ │ │ │ ├── fastcgi.go
│ │ │ │ ├── fastcgi_test.go
│ │ │ │ ├── header.go
│ │ │ │ ├── pool.go
│ │ │ │ ├── reader.go
│ │ │ │ ├── record.go
│ │ │ │ └── writer.go
│ │ │ ├── forwardauth/
│ │ │ │ └── caddyfile.go
│ │ │ ├── headers_test.go
│ │ │ ├── healthchecks.go
│ │ │ ├── hosts.go
│ │ │ ├── httptransport.go
│ │ │ ├── httptransport_test.go
│ │ │ ├── metrics.go
│ │ │ ├── passive_health_test.go
│ │ │ ├── retries_test.go
│ │ │ ├── reverseproxy.go
│ │ │ ├── selectionpolicies.go
│ │ │ ├── selectionpolicies_test.go
│ │ │ ├── streaming.go
│ │ │ ├── streaming_test.go
│ │ │ ├── upstreams.go
│ │ │ └── upstreams_test.go
│ │ ├── rewrite/
│ │ │ ├── caddyfile.go
│ │ │ ├── rewrite.go
│ │ │ └── rewrite_test.go
│ │ ├── routes.go
│ │ ├── server.go
│ │ ├── server_test.go
│ │ ├── standard/
│ │ │ └── imports.go
│ │ ├── staticerror.go
│ │ ├── staticresp.go
│ │ ├── staticresp_test.go
│ │ ├── subroute.go
│ │ ├── templates/
│ │ │ ├── caddyfile.go
│ │ │ ├── frontmatter.go
│ │ │ ├── frontmatter_fuzz.go
│ │ │ ├── templates.go
│ │ │ ├── tplcontext.go
│ │ │ └── tplcontext_test.go
│ │ ├── tracing/
│ │ │ ├── module.go
│ │ │ ├── module_test.go
│ │ │ ├── tracer.go
│ │ │ ├── tracer_test.go
│ │ │ ├── tracerprovider.go
│ │ │ └── tracerprovider_test.go
│ │ └── vars.go
│ ├── caddypki/
│ │ ├── acmeserver/
│ │ │ ├── acmeserver.go
│ │ │ ├── acmeserver_test.go
│ │ │ ├── caddyfile.go
│ │ │ ├── challenges.go
│ │ │ ├── policy.go
│ │ │ └── policy_test.go
│ │ ├── adminapi.go
│ │ ├── ca.go
│ │ ├── certificates.go
│ │ ├── command.go
│ │ ├── crypto.go
│ │ ├── crypto_test.go
│ │ ├── maintain.go
│ │ ├── maintain_test.go
│ │ └── pki.go
│ ├── caddytls/
│ │ ├── acmeissuer.go
│ │ ├── automation.go
│ │ ├── capools.go
│ │ ├── capools_test.go
│ │ ├── certmanagers.go
│ │ ├── certselection.go
│ │ ├── connpolicy.go
│ │ ├── connpolicy_test.go
│ │ ├── distributedstek/
│ │ │ └── distributedstek.go
│ │ ├── ech.go
│ │ ├── fileloader.go
│ │ ├── folderloader.go
│ │ ├── internalissuer.go
│ │ ├── internalissuer_test.go
│ │ ├── leaffileloader.go
│ │ ├── leaffileloader_test.go
│ │ ├── leaffolderloader.go
│ │ ├── leaffolderloader_test.go
│ │ ├── leafpemloader.go
│ │ ├── leafpemloader_test.go
│ │ ├── leafstorageloader.go
│ │ ├── matchers.go
│ │ ├── matchers_test.go
│ │ ├── ondemand.go
│ │ ├── pemloader.go
│ │ ├── sessiontickets.go
│ │ ├── standardstek/
│ │ │ └── stek.go
│ │ ├── storageloader.go
│ │ ├── tls.go
│ │ ├── values.go
│ │ └── zerosslissuer.go
│ ├── filestorage/
│ │ └── filestorage.go
│ ├── internal/
│ │ └── network/
│ │ └── networkproxy.go
│ ├── logging/
│ │ ├── appendencoder.go
│ │ ├── cores.go
│ │ ├── encoders.go
│ │ ├── filewriter.go
│ │ ├── filewriter_test.go
│ │ ├── filewriter_test_windows.go
│ │ ├── filterencoder.go
│ │ ├── filters.go
│ │ ├── filters_test.go
│ │ ├── netwriter.go
│ │ └── nopencoder.go
│ ├── metrics/
│ │ ├── adminmetrics.go
│ │ ├── metrics.go
│ │ └── metrics_test.go
│ └── standard/
│ └── imports.go
├── modules.go
├── modules_test.go
├── notify/
│ ├── notify_linux.go
│ ├── notify_other.go
│ └── notify_windows.go
├── replacer.go
├── replacer_fuzz.go
├── replacer_test.go
├── service_windows.go
├── sigtrap.go
├── sigtrap_nonposix.go
├── sigtrap_posix.go
├── storage.go
└── usagepool.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
[*]
end_of_line = lf
[caddytest/integration/caddyfile_adapt/*.caddyfiletest]
indent_style = tab
================================================
FILE: .gitattributes
================================================
*.go text eol=lf
================================================
FILE: .github/CONTRIBUTING.md
================================================
Contributing to Caddy
=====================
Welcome! Thank you for choosing to be a part of our community. Caddy wouldn't be nearly as excellent without your involvement!
For starters, we invite you to join [the Caddy forum](https://caddy.community) where you can hang out with other Caddy users and developers.
## Common Tasks
- [Contributing code](#contributing-code)
- [Writing a Caddy module](#writing-a-caddy-module)
- [Asking or answering questions for help using Caddy](#getting-help-using-caddy)
- [Reporting a bug](#reporting-bugs)
- [Suggesting an enhancement or a new feature](#suggesting-features)
- [Improving documentation](#improving-documentation)
Other menu items:
- [Values](#values)
- [Coordinated Disclosure](#coordinated-disclosure)
- [Thank You](#thank-you)
### Contributing code
You can have a huge impact on the project by helping with its code. To contribute code to Caddy, first submit or comment in an issue to discuss your contribution, then open a [pull request](https://github.com/caddyserver/caddy/pulls) (PR). If you're new to our community, that's okay: **we gladly welcome pull requests from anyone, regardless of your native language or coding experience.** You can get familiar with Caddy's code base by using [code search at Sourcegraph](https://sourcegraph.com/github.com/caddyserver/caddy).
We hold contributions to a high standard for quality :bowtie:, so don't be surprised if we ask for revisions—even if it seems small or insignificant. Please don't take it personally. :blue_heart: If your change is on the right track, we can guide you to make it mergeable.
Here are some of the expectations we have of contributors:
- **Open an issue to propose your change first.** This way we can avoid confusion, coordinate what everyone is working on, and ensure that any changes are in-line with the project's goals and the best interests of its users. We can also discuss the best possible implementation. If there's already an issue about it, comment on the existing issue to claim it. A lot of valuable time can be saved by discussing a proposal first.
- **Keep pull requests small.** Smaller PRs are more likely to be merged because they are easier to review! We might ask you to break up large PRs into smaller ones. [An example of what we want to avoid.](https://twitter.com/iamdevloper/status/397664295875805184)
- **Keep related commits together in a PR.** We do want pull requests to be small, but you should also keep multiple related commits in the same PR if they rely on each other.
- **Write tests.** Good, automated tests are very valuable! Written properly, they ensure your change works, and that other changes in the future won't break your change. CI checks should pass.
- **Benchmarks should be included for optimizations.** Optimizations sometimes make code harder to read or have changes that are less than obvious. They should be proven with benchmarks and profiling.
- **[Squash](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html) insignificant commits.** Every commit should be significant. Commits which merely rewrite a comment or fix a typo can be combined into another commit that has more substance. Interactive rebase can do this, or a simpler way is `git reset --soft <diverging-commit>` then `git commit -s`.
- **Be responsible for and maintain your contributions.** Caddy is a growing project, and it's much better when individual contributors help maintain their change after it is merged.
- **Use comments properly.** We expect good godoc comments for package-level functions, types, and values. Comments are also useful whenever the purpose for a line of code is not obvious.
- **Pull requests may still get closed.** The longer a PR stays open and idle, the more likely it is to be closed. If we haven't reviewed it in a while, it probably means the change is not a priority. Please don't take this personally, we're trying to balance a lot of tasks! If nobody else has commented or reacted to the PR, it likely means your change is useful only to you. The reality is this happens quite a lot. We don't tend to accept PRs that aren't generally helpful. For these reasons or others, the PR may get closed even after a review. We are not obligated to accept all proposed changes, even if the best justification we can give is something vague like, "It doesn't sit right." Sometimes PRs are just the wrong thing or the wrong time. Because it is open source, you can always build your own modified version of Caddy with a change you need, even if we reject it in the official repo. Plus, because Caddy is extensible, it's possible your feature could make a great plugin instead!
- **You certify that you wrote and comprehend the code you submit.** The Caddy project welcomes original contributions that comply with [our CLA](https://cla-assistant.io/caddyserver/caddy), meaning that authors must be able to certify that they created or have rights to the code they are contributing. In addition, we require that code is not simply copy-pasted from Q/A sites or AI language models without full comprehension and rigorous testing. In other words: contributors are allowed to refer to communities for assistance and use AI tools such as language models for inspiration, but code which originates from or is assisted by these resources MUST be:
- Licensed for you to freely share
- Fully comprehended by you (be able to explain every line of code)
- Verified by automated tests when feasible, or thorough manual tests otherwise
We have found that current language models (LLMs, like ChatGPT) may understand code syntax and even problem spaces to an extent, but often fail in subtle ways to convey true knowledge and produce correct algorithms. Integrated tools such as GitHub Copilot and Sourcegraph Cody may be used for inspiration, but code generated by these tools still needs to meet our criteria for licensing, human comprehension, and testing. These tools may be used to help write code comments and tests as long as you can certify they are accurate and correct. Note that it is often more trouble than it's worth to certify that Copilot (for example) is not giving you code that is possibly plagiarised, unlicensed, or licensed with incompatible terms -- as the Caddy project cannot accept such contributions. If that's too difficult for you (or impossible), then we recommend using these resources only for inspiration and write your own code. Ultimately, you (the contributor) are responsible for the code you're submitting.
As a courtesy to reviewers, we kindly ask that you disclose when contributing code that was generated by an AI tool or copied from another website so we can be aware of what to look for in code review.
We often grant [collaborator status](#collaborator-instructions) to contributors who author one or more significant, high-quality PRs that are merged into the code base.
#### HOW TO MAKE A PULL REQUEST TO CADDY
Contributing to Go projects on GitHub is fun and easy. After you have proposed your change in an issue, we recommend the following workflow:
1. [Fork this repo](https://github.com/caddyserver/caddy). This makes a copy of the code you can write to.
2. If you don't already have this repo (caddyserver/caddy.git) repo on your computer, clone it down: `git clone https://github.com/caddyserver/caddy.git`
3. Tell git that it can push the caddyserver/caddy.git repo to your fork by adding a remote: `git remote add myfork https://github.com/<your-username>/caddy.git`
4. Make your changes in the caddyserver/caddy.git repo on your computer.
5. Push your changes to your fork: `git push myfork`
6. [Create a pull request](https://github.com/caddyserver/caddy/pull/new/master) to merge your changes into caddyserver/caddy @ master. (Click "compare across forks" and change the head fork.)
This workflow is nice because you don't have to change import paths. You can get fancier by using different branches if you want.
### Writing a Caddy module
Caddy can do more with modules! Anyone can write one. Caddy modules are Go libraries that get compiled into Caddy, extending its feature set. They can add directives to the Caddyfile, add new configuration adapters, and even implement new server types (e.g. HTTP, DNS).
[Learn how to write a module here](https://caddyserver.com/docs/extending-caddy). You should also share and discuss your module idea [on the forums](https://caddy.community) to have people test it out. We don't use the Caddy issue tracker for third-party modules.
### Getting help using Caddy
If you have a question about using Caddy, [ask on our forum](https://caddy.community)! There will be more people there who can help you than just the Caddy developers who follow our issue tracker. Issues are not the place for usage questions.
Many people on the forums could benefit from your experience and expertise, too. Once you've been helped, consider giving back by answering other people's questions and participating in other discussions.
### Reporting bugs
Like every software, Caddy has its flaws. If you find one, [search the issues](https://github.com/caddyserver/caddy/issues) to see if it has already been reported. If not, [open a new issue](https://github.com/caddyserver/caddy/issues/new) and describe the bug, and somebody will look into it! (This repository is only for Caddy and its standard modules.)
**You can help us fix bugs!** Speed up the patch by identifying the bug in the code. This can sometimes be done by adding `fmt.Println()` statements (or similar) in relevant code paths to narrow down where the problem may be. It's a good way to [introduce yourself to the Go language](https://tour.golang.org), too.
We may reply with an issue template. Please follow the template so we have all the needed information. Unredacted—yes, actual values matter. We need to be able to repeat the bug using your instructions. Please simplify the issue as much as possible. If you don't, we might close your report. The burden is on you to make it easily reproducible and to convince us that it is actually a bug in Caddy. This is easiest to do when you write clear, concise instructions so we can reproduce the behavior (even if it seems obvious). The more detailed and specific you are, the faster we will be able to help you!
We suggest reading [How to Report Bugs Effectively](http://www.chiark.greenend.org.uk/~sgtatham/bugs.html).
Please be kind. :smile: Remember that Caddy comes at no cost to you, and you're getting free support when we fix your issues. If we helped you, please consider helping someone else!
#### Bug reporting expectations
Maintainers---or more generally, developers---need three things to act on bugs:
1. To agree or be convinced that it's a bug (reporter's responsibility).
- A bug is unintentional, undesired, or surprising behavior which violates documentation or relevant spec. It might be either a mistake in the documentation or a bug in the code.
- This project usually does not work around bugs in other software, systems, and dependencies; instead, we recommend that those bugs are fixed at their source. This sometimes means we close issues or reject PRs that attempt to fix, workaround, or hide bugs in other projects.
2. To be able to understand what is happening (mostly reporter's responsibility).
- If the reporter can provide satisfactory instructions such that a developer can reproduce the bug, the developer will likely be able to understand the bug, write a test case, and implement a fix. This is the least amount of work for everyone and path to the fastest resolution.
- Otherwise, the burden is on the reporter to test possible solutions. This is less preferable because it loosens the feedback loop, slows down debugging efforts, obscures the true nature of the problem from the developers, and is unlikely to result in new test cases.
3. A solution, or ideas toward a solution (mostly maintainer's responsibility).
- Sometimes the best solution is a documentation change.
- Usually the developers have the best domain knowledge for inventing a solution, but reporters may have ideas or preferences for how they would like the software to work.
- Security, correctness, and project goals/vision all take priority over a user's preferences.
- It's simply good business to yield a solution that satisfies the users, and it's even better business to leave them impressed.
Thus, at the very least, the reporter is expected to:
1. Convince the reader that it's a bug in Caddy (if it's not obvious).
2. Reduce the problem down to the minimum specific steps required to reproduce it.
The maintainer is usually able to do the rest; but of course the reporter may invest additional effort to speed up the process.
### Suggesting features
First, [search to see if your feature has already been requested](https://github.com/caddyserver/caddy/issues). If it has, you can add a :+1: reaction to vote for it. If your feature idea is new, open an issue to request the feature. Please describe your idea thoroughly so that we know how to implement it! Really vague requests may not be helpful or actionable and, without clarification, will have to be closed.
While we really do value your requests and implement many of them, not all features are a good fit for Caddy. Most of those [make good modules](#writing-a-caddy-module), which can be made by anyone! But if a feature is not in the best interest of the Caddy project or its users in general, we may politely decline to implement it into Caddy core. Additionally, some features are bad ideas altogether (for either obvious or non-obvious reasons) which may be rejected. We'll try to explain why we reject a feature, but sometimes the best we can do is, "It's not a good fit for the project."
### Improving documentation
Caddy's documentation is available at [https://caddyserver.com/docs](https://caddyserver.com/docs) and its source is in the [website repo](https://github.com/caddyserver/website). If you would like to make a fix to the docs, please submit an issue there describing the change to make.
Note that third-party module documentation is not hosted by the Caddy website, other than basic usage examples. They are managed by the individual module authors, and you will have to contact them to change their documentation.
Our documentation is scoped to the Caddy project only: it is not for describing how other software or systems work, even if they relate to Caddy or web servers. That kind of content [can be found in our community wiki](https://caddy.community/c/wiki/13), however.
## Collaborator Instructions
Collaborators have push rights to the repository. We grant this permission after one or more successful, high-quality PRs are merged! We thank them for their help. The expectations we have of collaborators are:
- **Help review pull requests.** Be meticulous, but also kind. We love our contributors, but we critique the contribution to make it better. Multiple, thorough reviews make for the best contributions! Here are some questions to consider:
- Can the change be made more elegant?
- Is this a maintenance burden?
- What assumptions does the code make?
- Is it well-tested?
- Is the change a good fit for the project?
- Does it actually fix the problem or is it creating a special case instead?
- Does the change incur any new dependencies? (Avoid these!)
- **Answer issues.** If every collaborator helped out with issues, we could count the number of open issues on two hands. This means getting involved in the discussion, investigating the code, and yes, debugging it. It's fun. Really! :smile: Please, please help with open issues. Granted, some issues need to be done before others. And of course some are larger than others: you don't have to do it all yourself. Work with other collaborators as a team!
- **Do not merge pull requests until they have been approved by one or two other collaborators.** If a project owner approves the PR, it can be merged (as long as the conversation has finished too).
- **Prefer squashed commits over a messy merge.** If there are many little commits, please [squash the commits](https://stackoverflow.com/a/11732910/1048862) so we don't clutter the commit history.
- **Don't accept new dependencies lightly.** Dependencies can make the world crash and burn, but they are sometimes necessary. Choose carefully. Extremely small dependencies (a few lines of code) can be inlined. The rest may not be needed. For those that are, Caddy uses [go modules](https://github.com/golang/go/wiki/Modules). All external dependencies must be installed as modules, and _Caddy must not export any types defined by those dependencies_. Check this diligently!
- **Be extra careful in some areas of the code.** There are some critical areas in the Caddy code base that we review extra meticulously: the `caddyhttp` and `caddytls` packages especially.
- **Make sure tests test the actual thing.** Double-check that the tests fail without the change, and pass with it. It's important that they assert what they're purported to assert.
- **Recommended reading**
- [CodeReviewComments](https://github.com/golang/go/wiki/CodeReviewComments) for an idea of what we look for in good, clean Go code
- [Linus Torvalds describes a good commit message](https://gist.github.com/matthewhudson/1475276)
- [Best Practices for Maintainers](https://opensource.guide/best-practices/)
- [Shrinking Code Review](https://alexgaynor.net/2015/dec/29/shrinking-code-review/)
## Values (WIP)
- A person is always more important than code. People don't like being handled "efficiently". But we can still process issues and pull requests efficiently while being kind, patient, and considerate.
- The ends justify the means, if the means are good. A good tree won't produce bad fruit. But if we cut corners or are hasty in our process, the end result will not be good.
## Security Policy
If you think you've found a security vulnerability, please refer to our [Security Policy](https://github.com/caddyserver/caddy/security/policy) document.
## Thank you
Thanks for your help! Caddy would not be what it is today without your contributions.
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: [mholt] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
================================================
FILE: .github/ISSUE_TEMPLATE/ISSUE.yml
================================================
name: Issue
description: An actionable development item, like a bug report or feature request
body:
- type: markdown
attributes:
value: |
Thank you for opening an issue! This is for actionable development items like bug reports and feature requests.
If you have a question about using Caddy, please [post on our forums](https://caddy.community) instead.
- type: textarea
id: content
attributes:
label: Issue Details
placeholder: Describe the issue here. Be specific by providing complete logs and minimal instructions to reproduce, or a thoughtful proposal, etc.
validations:
required: true
- type: dropdown
id: assistance-disclosure
attributes:
label: Assistance Disclosure
description: "Our project allows assistance by AI/LLM tools as long as it is disclosed and described so we can better respond. Please certify whether you have used any such tooling related to this issue:"
options:
-
- AI used
- AI not used
validations:
required: true
- type: input
id: assistance-description
attributes:
label: If AI was used, describe the extent to which it was used.
description: 'Examples: "ChatGPT translated from my native language" or "Claude proposed this change/feature"'
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
- name: Caddy forum
url: https://caddy.community
about: If you have questions (or answers!) about using Caddy, please use our forum
================================================
FILE: .github/SECURITY.md
================================================
# Security Policy
The Caddy project would like to make sure that it stays on top of all relevant and practically-exploitable vulnerabilities.
## Supported Versions
| Version | Supported |
| ----------- | ----------|
| 2.latest | ✔️ |
| <= 2.latest | :x: |
## Acceptable Scope
A security report must demonstrate a security bug in the source code from this repository.
Some security problems are the result of interplay between different components of the Web, rather than a vulnerability in the web server itself. Please only report vulnerabilities in the web server itself, as we cannot coerce the rest of the Web to be fixed (for example, we do not consider IP spoofing, BGP hijacks, or missing/misconfigured HTTP headers a vulnerability in the Caddy web server).
Vulnerabilities caused by misconfigurations are out of scope. Yes, it is entirely possible to craft and use a configuration that is unsafe, just like with every other web server; we recommend against doing that. Similarly, external misconfigurations are out of scope. For example, an open or forwarded port from a public network to a Caddy instance intended to serve only internal clients is not a vulnerability in Caddy.
We do not accept reports if the steps imply or require a compromised system or third-party software, as we cannot control those. We expect that users secure their own systems and keep all their software patched. For example, if untrusted users are able to upload/write/host arbitrary files in the web root directory, it is NOT a security bug in Caddy if those files get served to clients; however, it _would_ be a valid report if a bug in Caddy's source code unintentionally gave unauthorized users the ability to upload unsafe files or delete files without relying on an unpatched system or piece of software.
Client-side exploits are out of scope. In other words, it is not a bug in Caddy if the web browser does something unsafe, even if the downloaded content was served by Caddy. (Those kinds of exploits can generally be mitigated by proper configuration of HTTP headers.) As a general rule, the content served by Caddy is not considered in scope because content is configurable by the site owner or the associated web application.
Security bugs in code dependencies (including Go's standard library) are out of scope. Instead, if a dependency has patched a relevant security bug, please feel free to open a public issue or pull request to update that dependency in our code.
We accept security reports and patches, but do not assign CVEs, for code that has not been released with a non-prerelease tag.
## Reporting a Vulnerability
We get a lot of difficult reports that turn out to be invalid. Clear, obvious reports tend to be the most credible (but are also rare).
First please ensure your report falls within the accepted scope of security bugs (above).
:warning: **YOU MUST DISCLOSE WHETHER YOU USED LLMs ("AI") IN ANY WAY.** Whether you are using AI for discovery, as part of writing the report or its replies, and/or testing or validating proofs and changes, we require you to mention the extent of it. **FAILURE TO INCLUDE A DISCLOSURE EVEN IF YOU DO NOT USE AI MAY LEAD TO IMMEDIATE DISMISSAL OF YOUR REPORT AND POTENTIAL BLOCKLISTING.** We will not waste our time chatting with bots. But if you're a human, pull up a chair and we'll drink some chocolate milk.
We'll need enough information to verify the bug and make a patch. To speed things up, please include:
- Most minimal possible config (without redactions!)
- Command(s)
- Precise HTTP requests (`curl -v` and its output please)
- Full log output (please enable debug mode)
- Specific minimal steps to reproduce the issue from scratch
- A working patch
Please DO NOT use containers, VMs, cloud instances or services, or any other complex infrastructure in your steps. Always prefer `curl -v` instead of web browsers.
We consider publicly-registered domain names to be public information. This necessary in order to maintain the integrity of certificate transparency, public DNS, and other public trust systems. Do not redact domain names from your reports. The actual content of your domain name affects Caddy's behavior, so we need the exact domain name(s) to reproduce with, or your report will be ignored.
It will speed things up if you suggest a working patch, such as a code diff, and explain why and how it works. Reports that are not actionable, do not contain enough information, are too pushy/demanding, or are not able to convince us that it is a viable and practical attack on the web server itself may be deferred to a later time or possibly ignored, depending on available resources. Priority will be given to credible, responsible reports that are constructive, specific, and actionable. (We get a lot of invalid reports.) Thank you for understanding.
When you are ready, please submit a [new private vulnerability report](https://github.com/caddyserver/caddy/security/advisories/new).
Please don't encrypt the message. It only makes the process more complicated.
Please also understand that due to our nature as an open source project, we do not have a budget to award security bounties. We can only thank you.
If your report is valid and a patch is released, we will not reveal your identity by default. If you wish to be credited, please give us the name to use and/or your GitHub username. If you don't provide this we can't credit you.
Thanks for responsibly helping Caddy—and thousands of websites—be more secure!
================================================
FILE: .github/dependabot.yml
================================================
---
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
open-pull-requests-limit: 1
groups:
actions-deps:
patterns:
- "*"
schedule:
interval: "monthly"
- package-ecosystem: "gomod"
directory: "/"
open-pull-requests-limit: 1
groups:
all-updates:
patterns:
- "*"
schedule:
interval: "monthly"
================================================
FILE: .github/pull_request_template.md
================================================
## Assistance Disclosure
<!--
Thank you for contributing! Please note:
The use of AI/LLM tools is allowed so long as it is disclosed, so
that we can provide better code review and maintain project quality.
If you used AI/LLM tooling in any way related to this PR, please
let us know to what extent it was utilized.
Examples:
"No AI was used."
"I wrote the code, but Claude generated the tests."
"I consulted ChatGPT for a solution, but I authored/coded it myself."
"Cody generated the code, and I verified it is correct."
"Copilot provided tab completion for code and comments."
We expect that you have vetted your contributions for correctness.
Additionally, signing our CLA certifies that you have the rights to
contribute this change.
Replace the text below with your disclosure:
-->
_This PR is missing an assistance disclosure._
================================================
FILE: .github/workflows/ai.yml
================================================
name: AI Moderator
permissions: read-all
on:
issues:
types: [opened]
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
jobs:
spam-detection:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
models: read
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- uses: github/ai-moderator@81159c370785e295c97461ade67d7c33576e9319
with:
token: ${{ secrets.GITHUB_TOKEN }}
spam-label: 'spam'
ai-label: 'ai-generated'
minimize-detected-comments: true
# Built-in prompt configuration (all enabled by default)
enable-spam-detection: true
enable-link-spam-detection: true
enable-ai-detection: true
# custom-prompt-path: '.github/prompts/my-custom.prompt.yml' # Optional
================================================
FILE: .github/workflows/auto-release-pr.yml
================================================
name: Release Proposal Approval Tracker
on:
pull_request_review:
types: [submitted, dismissed]
pull_request:
types: [labeled, unlabeled, synchronize, closed]
permissions:
contents: read
pull-requests: write
issues: write
jobs:
check-approvals:
name: Track Maintainer Approvals
runs-on: ubuntu-latest
# Only run on PRs with release-proposal label
if: contains(github.event.pull_request.labels.*.name, 'release-proposal') && github.event.pull_request.state == 'open'
steps:
- name: Check approvals and update PR
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
env:
MAINTAINER_LOGINS: ${{ secrets.MAINTAINER_LOGINS }}
with:
script: |
const pr = context.payload.pull_request;
// Extract version from PR title (e.g., "Release Proposal: v1.2.3")
const versionMatch = pr.title.match(/Release Proposal:\s*(v[\d.]+(?:-[\w.]+)?)/);
const commitMatch = pr.body.match(/\*\*Target Commit:\*\*\s*`([a-f0-9]+)`/);
if (!versionMatch || !commitMatch) {
console.log('Could not extract version from title or commit from body');
return;
}
const version = versionMatch[1];
const targetCommit = commitMatch[1];
console.log(`Version: ${version}, Target Commit: ${targetCommit}`);
// Get all reviews
const reviews = await github.rest.pulls.listReviews({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number
});
// Get list of maintainers
const maintainerLoginsRaw = process.env.MAINTAINER_LOGINS || '';
const maintainerLogins = maintainerLoginsRaw
.split(/[,;]/)
.map(login => login.trim())
.filter(login => login.length > 0);
console.log(`Maintainer logins: ${maintainerLogins.join(', ')}`);
// Get the latest review from each user
const latestReviewsByUser = {};
reviews.data.forEach(review => {
const username = review.user.login;
if (!latestReviewsByUser[username] || new Date(review.submitted_at) > new Date(latestReviewsByUser[username].submitted_at)) {
latestReviewsByUser[username] = review;
}
});
// Count approvals from maintainers
const maintainerApprovals = Object.entries(latestReviewsByUser)
.filter(([username, review]) =>
maintainerLogins.includes(username) &&
review.state === 'APPROVED'
)
.map(([username, review]) => username);
const approvalCount = maintainerApprovals.length;
console.log(`Found ${approvalCount} maintainer approvals from: ${maintainerApprovals.join(', ')}`);
// Get current labels
const currentLabels = pr.labels.map(label => label.name);
const hasApprovedLabel = currentLabels.includes('approved');
const hasAwaitingApprovalLabel = currentLabels.includes('awaiting-approval');
if (approvalCount >= 2 && !hasApprovedLabel) {
console.log('✅ Quorum reached! Updating PR...');
// Remove awaiting-approval label if present
if (hasAwaitingApprovalLabel) {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
name: 'awaiting-approval'
}).catch(e => console.log('Label not found:', e.message));
}
// Add approved label
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
labels: ['approved']
});
// Add comment with tagging instructions
const approversList = maintainerApprovals.map(u => `@${u}`).join(', ');
const commentBody = [
'## ✅ Approval Quorum Reached',
'',
`This release proposal has been approved by ${approvalCount} maintainers: ${approversList}`,
'',
'### Tagging Instructions',
'',
'A maintainer should now create and push the signed tag:',
'',
'```bash',
`git checkout ${targetCommit}`,
`git tag -s ${version} -m "Release ${version}"`,
`git push origin ${version}`,
`git checkout -`,
'```',
'',
'The release workflow will automatically start when the tag is pushed.'
].join('\n');
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body: commentBody
});
console.log('Posted tagging instructions');
} else if (approvalCount < 2 && hasApprovedLabel) {
console.log('⚠️ Approval count dropped below quorum, removing approved label');
// Remove approved label
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
name: 'approved'
}).catch(e => console.log('Label not found:', e.message));
// Add awaiting-approval label
if (!hasAwaitingApprovalLabel) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
labels: ['awaiting-approval']
});
}
} else {
console.log(`⏳ Waiting for more approvals (${approvalCount}/2 required)`);
}
handle-pr-closed:
name: Handle PR Closed Without Tag
runs-on: ubuntu-latest
if: |
contains(github.event.pull_request.labels.*.name, 'release-proposal') &&
github.event.action == 'closed' && !contains(github.event.pull_request.labels.*.name, 'released')
steps:
- name: Add cancelled label and comment
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: |
const pr = context.payload.pull_request;
// Check if the release-in-progress label is present
const hasReleaseInProgress = pr.labels.some(label => label.name === 'release-in-progress');
if (hasReleaseInProgress) {
// PR was closed while release was in progress - this is unusual
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body: '⚠️ **Warning:** This PR was closed while a release was in progress. This may indicate an error. Please verify the release status.'
});
} else {
// PR was closed before tag was created - this is normal cancellation
const versionMatch = pr.title.match(/Release Proposal:\s*(v[\d.]+(?:-[\w.]+)?)/);
const version = versionMatch ? versionMatch[1] : 'unknown';
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body: `## 🚫 Release Proposal Cancelled\n\nThis release proposal for ${version} was closed without creating the tag.\n\nIf you want to proceed with this release later, you can create a new release proposal.`
});
}
// Add cancelled label
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
labels: ['cancelled']
});
// Remove other workflow labels if present
const labelsToRemove = ['awaiting-approval', 'approved', 'release-in-progress'];
for (const label of labelsToRemove) {
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
name: label
});
} catch (e) {
console.log(`Label ${label} not found or already removed`);
}
}
console.log('Added cancelled label and cleaned up workflow labels');
================================================
FILE: .github/workflows/ci.yml
================================================
# Used as inspiration: https://github.com/mvdan/github-actions-golang
name: Tests
on:
push:
branches:
- master
- 2.*
pull_request:
branches:
- master
- 2.*
env:
GOFLAGS: '-tags=nobadger,nomysql,nopgx'
# https://github.com/actions/setup-go/issues/491
GOTOOLCHAIN: local
permissions:
contents: read
jobs:
test:
strategy:
# Default is true, cancels jobs for other platforms in the matrix if one fails
fail-fast: false
matrix:
os:
- linux
- mac
- windows
go:
- '1.26'
include:
# Set the minimum Go patch version for the given Go minor
# Usable via ${{ matrix.GO_SEMVER }}
- go: '1.26'
GO_SEMVER: '~1.26.0'
# Set some variables per OS, usable via ${{ matrix.VAR }}
# OS_LABEL: the VM label from GitHub Actions (see https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-public-repositories)
# CADDY_BIN_PATH: the path to the compiled Caddy binary, for artifact publishing
# SUCCESS: the typical value for $? per OS (Windows/pwsh returns 'True')
- os: linux
OS_LABEL: ubuntu-latest
CADDY_BIN_PATH: ./cmd/caddy/caddy
SUCCESS: 0
- os: mac
OS_LABEL: macos-14
CADDY_BIN_PATH: ./cmd/caddy/caddy
SUCCESS: 0
- os: windows
OS_LABEL: windows-latest
CADDY_BIN_PATH: ./cmd/caddy/caddy.exe
SUCCESS: 'True'
runs-on: ${{ matrix.OS_LABEL }}
permissions:
contents: read
pull-requests: read
actions: write # to allow uploading artifacts and cache
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0
with:
egress-policy: audit
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install Go
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version: ${{ matrix.GO_SEMVER }}
check-latest: true
# These tools would be useful if we later decide to reinvestigate
# publishing test/coverage reports to some tool for easier consumption
# - name: Install test and coverage analysis tools
# run: |
# go get github.com/axw/gocov/gocov
# go get github.com/AlekSi/gocov-xml
# go get -u github.com/jstemmer/go-junit-report
# echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
- name: Print Go version and environment
id: vars
shell: bash
run: |
printf "Using go at: $(which go)\n"
printf "Go version: $(go version)\n"
printf "\n\nGo environment:\n\n"
go env
printf "\n\nSystem environment:\n\n"
env
printf "Git version: $(git version)\n\n"
# Calculate the short SHA1 hash of the git commit
echo "short_sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
- name: Get dependencies
run: |
go get -v -t -d ./...
# mkdir test-results
- name: Build Caddy
working-directory: ./cmd/caddy
env:
CGO_ENABLED: 0
run: |
go build -trimpath -ldflags="-w -s" -v
- name: Smoke test Caddy
working-directory: ./cmd/caddy
run: |
./caddy start
./caddy stop
- name: Publish Build Artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: caddy_${{ runner.os }}_go${{ matrix.go }}_${{ steps.vars.outputs.short_sha }}
path: ${{ matrix.CADDY_BIN_PATH }}
compression-level: 0
# Commented bits below were useful to allow the job to continue
# even if the tests fail, so we can publish the report separately
# For info about set-output, see https://stackoverflow.com/questions/57850553/github-actions-check-steps-status
- name: Run tests
# id: step_test
# continue-on-error: true
run: |
# (go test -v -coverprofile=cover-profile.out -race ./... 2>&1) > test-results/test-result.out
go test -v -coverprofile="cover-profile.out" -short -race ./...
# echo "status=$?" >> $GITHUB_OUTPUT
# Relevant step if we reinvestigate publishing test/coverage reports
# - name: Prepare coverage reports
# run: |
# mkdir coverage
# gocov convert cover-profile.out > coverage/coverage.json
# # Because Windows doesn't work with input redirection like *nix, but output redirection works.
# (cat ./coverage/coverage.json | gocov-xml) > coverage/coverage.xml
# To return the correct result even though we set 'continue-on-error: true'
# - name: Coerce correct build result
# if: matrix.os != 'windows' && steps.step_test.outputs.status != ${{ matrix.SUCCESS }}
# run: |
# echo "step_test ${{ steps.step_test.outputs.status }}\n"
# exit 1
s390x-test:
name: test (s390x on IBM Z)
permissions:
contents: read
pull-requests: read
runs-on: ubuntu-latest
if: github.event.pull_request.head.repo.full_name == 'caddyserver/caddy' && github.actor != 'dependabot[bot]'
continue-on-error: true # August 2020: s390x VM is down due to weather and power issues
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0
with:
egress-policy: audit
allowed-endpoints: ci-s390x.caddyserver.com:22
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Run Tests
run: |
set +e
mkdir -p ~/.ssh && echo -e "${SSH_KEY//_/\\n}" > ~/.ssh/id_ecdsa && chmod og-rwx ~/.ssh/id_ecdsa
# short sha is enough?
short_sha=$(git rev-parse --short HEAD)
# To shorten the following lines
ssh_opts="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
ssh_host="$CI_USER@ci-s390x.caddyserver.com"
# The environment is fresh, so there's no point in keeping accepting and adding the key.
rsync -arz -e "ssh $ssh_opts" --progress --delete --exclude '.git' . "$ssh_host":/var/tmp/"$short_sha"
ssh $ssh_opts -t "$ssh_host" bash <<EOF
cd /var/tmp/$short_sha
go version
go env
printf "\n\n"
retries=3
exit_code=0
while ((retries > 0)); do
CGO_ENABLED=0 go test -p 1 -v ./...
exit_code=$?
if ((exit_code == 0)); then
break
fi
echo "\n\nTest failed: \$exit_code, retrying..."
((retries--))
done
echo "Remote exit code: \$exit_code"
exit \$exit_code
EOF
test_result=$?
# There's no need leaving the files around
ssh $ssh_opts "$ssh_host" "rm -rf /var/tmp/'$short_sha'"
echo "Test exit code: $test_result"
exit $test_result
env:
SSH_KEY: ${{ secrets.S390X_SSH_KEY }}
CI_USER: ${{ secrets.CI_USER }}
goreleaser-check:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
if: github.event.pull_request.head.repo.full_name == 'caddyserver/caddy' && github.actor != 'dependabot[bot]'
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0
with:
egress-policy: audit
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: goreleaser/goreleaser-action@ec59f474b9834571250b370d4735c50f8e2d1e29 # v7.0.0
with:
version: latest
args: check
- name: Install Go
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version: "~1.26"
check-latest: true
- name: Install xcaddy
run: |
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
xcaddy version
- uses: goreleaser/goreleaser-action@ec59f474b9834571250b370d4735c50f8e2d1e29 # v7.0.0
with:
version: latest
args: build --single-target --snapshot
env:
TAG: ${{ github.head_ref || github.ref_name }}
================================================
FILE: .github/workflows/cross-build.yml
================================================
name: Cross-Build
on:
push:
branches:
- master
- 2.*
pull_request:
branches:
- master
- 2.*
env:
GOFLAGS: '-tags=nobadger,nomysql,nopgx'
CGO_ENABLED: '0'
# https://github.com/actions/setup-go/issues/491
GOTOOLCHAIN: local
permissions:
contents: read
jobs:
build:
strategy:
fail-fast: false
matrix:
goos:
- 'aix'
- 'linux'
- 'solaris'
- 'illumos'
- 'dragonfly'
- 'freebsd'
- 'openbsd'
- 'windows'
- 'darwin'
- 'netbsd'
go:
- '1.26'
include:
# Set the minimum Go patch version for the given Go minor
# Usable via ${{ matrix.GO_SEMVER }}
- go: '1.26'
GO_SEMVER: '~1.26.0'
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
continue-on-error: true
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0
with:
egress-policy: audit
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install Go
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version: ${{ matrix.GO_SEMVER }}
check-latest: true
- name: Print Go version and environment
id: vars
run: |
printf "Using go at: $(which go)\n"
printf "Go version: $(go version)\n"
printf "\n\nGo environment:\n\n"
go env
printf "\n\nSystem environment:\n\n"
env
- name: Run Build
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goos == 'aix' && 'ppc64' || 'amd64' }}
shell: bash
continue-on-error: true
working-directory: ./cmd/caddy
run: go build -trimpath -o caddy-"$GOOS"-$GOARCH 2> /dev/null
================================================
FILE: .github/workflows/lint.yml
================================================
name: Lint
on:
push:
branches:
- master
- 2.*
pull_request:
branches:
- master
- 2.*
permissions:
contents: read
env:
# https://github.com/actions/setup-go/issues/491
GOTOOLCHAIN: local
jobs:
# From https://github.com/golangci/golangci-lint-action
golangci:
permissions:
contents: read # for actions/checkout to fetch code
pull-requests: read # for golangci/golangci-lint-action to fetch pull requests
name: lint
strategy:
matrix:
os:
- linux
- mac
- windows
include:
- os: linux
OS_LABEL: ubuntu-latest
- os: mac
OS_LABEL: macos-14
- os: windows
OS_LABEL: windows-latest
runs-on: ${{ matrix.OS_LABEL }}
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0
with:
egress-policy: audit
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version: '~1.26'
check-latest: true
- name: golangci-lint
uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9.2.0
with:
version: latest
# Windows times out frequently after about 5m50s if we don't set a longer timeout.
args: --timeout 10m
# Optional: show only new issues if it's a pull request. The default value is `false`.
# only-new-issues: true
govulncheck:
permissions:
contents: read
pull-requests: read
runs-on: ubuntu-latest
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0
with:
egress-policy: audit
- name: govulncheck
uses: golang/govulncheck-action@b625fbe08f3bccbe446d94fbf87fcc875a4f50ee # v1.0.4
with:
go-version-input: '~1.26.0'
check-latest: true
dependency-review:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0
with:
egress-policy: audit
- name: 'Checkout Repository'
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: 'Dependency Review'
uses: actions/dependency-review-action@05fe4576374b728f0c523d6a13d64c25081e0803 # v4.8.3
with:
comment-summary-in-pr: on-failure
# https://github.com/actions/dependency-review-action/issues/430#issuecomment-1468975566
base-ref: ${{ github.event.pull_request.base.sha || 'master' }}
head-ref: ${{ github.event.pull_request.head.sha || github.ref }}
================================================
FILE: .github/workflows/release-proposal.yml
================================================
name: Release Proposal
# This workflow creates a release proposal as a PR that requires approval from maintainers
# Triggered manually by maintainers when ready to prepare a release
on:
workflow_dispatch:
inputs:
version:
description: 'Version to release (e.g., v2.8.0)'
required: true
type: string
commit_hash:
description: 'Commit hash to release from'
required: true
type: string
permissions:
contents: read
jobs:
create-proposal:
name: Create Release Proposal
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
issues: write
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0
with:
egress-policy: audit
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- name: Trim and validate inputs
id: inputs
run: |
# Trim whitespace from inputs
VERSION=$(echo "${{ inputs.version }}" | xargs)
COMMIT_HASH=$(echo "${{ inputs.commit_hash }}" | xargs)
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "commit_hash=$COMMIT_HASH" >> $GITHUB_OUTPUT
# Validate version format
if [[ ! "$VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then
echo "Error: Version must follow semver format (e.g., v2.8.0 or v2.8.0-beta.1)"
exit 1
fi
# Validate commit hash format
if [[ ! "$COMMIT_HASH" =~ ^[a-f0-9]{7,40}$ ]]; then
echo "Error: Commit hash must be a valid SHA (7-40 characters)"
exit 1
fi
# Check if commit exists
if ! git cat-file -e "$COMMIT_HASH"; then
echo "Error: Commit $COMMIT_HASH does not exist"
exit 1
fi
- name: Check if tag already exists
run: |
if git rev-parse "${{ steps.inputs.outputs.version }}" >/dev/null 2>&1; then
echo "Error: Tag ${{ steps.inputs.outputs.version }} already exists"
exit 1
fi
- name: Check for existing proposal PR
id: check_existing
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: |
const version = '${{ steps.inputs.outputs.version }}';
// Search for existing open PRs with release-proposal label that match this version
const openPRs = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
sort: 'updated',
direction: 'desc'
});
const existingOpenPR = openPRs.data.find(pr =>
pr.title.includes(version) &&
pr.labels.some(label => label.name === 'release-proposal')
);
if (existingOpenPR) {
const hasReleased = existingOpenPR.labels.some(label => label.name === 'released');
const hasReleaseInProgress = existingOpenPR.labels.some(label => label.name === 'release-in-progress');
if (hasReleased || hasReleaseInProgress) {
core.setFailed(`A release for ${version} is already in progress or completed: ${existingOpenPR.html_url}`);
} else {
core.setFailed(`An open release proposal already exists for ${version}: ${existingOpenPR.html_url}\n\nPlease use the existing PR or close it first.`);
}
return;
}
// Check for closed PRs with this version that were cancelled
const closedPRs = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'closed',
sort: 'updated',
direction: 'desc'
});
const cancelledPR = closedPRs.data.find(pr =>
pr.title.includes(version) &&
pr.labels.some(label => label.name === 'release-proposal') &&
pr.labels.some(label => label.name === 'cancelled')
);
if (cancelledPR) {
console.log(`Found previously cancelled proposal for ${version}: ${cancelledPR.html_url}`);
console.log('Creating new proposal to replace cancelled one...');
} else {
console.log(`No existing proposal found for ${version}, proceeding...`);
}
- name: Generate changelog and create branch
id: setup
run: |
VERSION="${{ steps.inputs.outputs.version }}"
COMMIT_HASH="${{ steps.inputs.outputs.commit_hash }}"
# Create a new branch for the release proposal
BRANCH_NAME="release_proposal-$VERSION"
git checkout -b "$BRANCH_NAME"
# Calculate how many commits behind HEAD
COMMITS_BEHIND=$(git rev-list --count ${COMMIT_HASH}..HEAD)
if [ "$COMMITS_BEHIND" -eq 0 ]; then
BEHIND_INFO="This is the latest commit (HEAD)"
else
BEHIND_INFO="This commit is **${COMMITS_BEHIND} commits behind HEAD**"
fi
echo "commits_behind=$COMMITS_BEHIND" >> $GITHUB_OUTPUT
echo "behind_info=$BEHIND_INFO" >> $GITHUB_OUTPUT
# Get the last tag
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
if [ -z "$LAST_TAG" ]; then
echo "No previous tag found, generating full changelog"
COMMITS=$(git log --pretty=format:"- %s (%h)" --reverse "$COMMIT_HASH")
else
echo "Generating changelog since $LAST_TAG"
COMMITS=$(git log --pretty=format:"- %s (%h)" --reverse "${LAST_TAG}..$COMMIT_HASH")
fi
# Store changelog for PR body
CLEANSED_COMMITS=$(echo "$COMMITS" | sed 's/`/\\`/g')
echo "changelog<<EOF" >> $GITHUB_OUTPUT
echo "$CLEANSED_COMMITS" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
# Create empty commit for the PR
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git commit --allow-empty -m "Release proposal for $VERSION"
# Push the branch
git push origin "$BRANCH_NAME"
echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT
- name: Create release proposal PR
id: create_pr
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: |
const changelog = `${{ steps.setup.outputs.changelog }}`;
const pr = await github.rest.pulls.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: `Release Proposal: ${{ steps.inputs.outputs.version }}`,
head: '${{ steps.setup.outputs.branch_name }}',
base: 'master',
body: `## Release Proposal: ${{ steps.inputs.outputs.version }}
**Target Commit:** \`${{ steps.inputs.outputs.commit_hash }}\`
**Requested by:** @${{ github.actor }}
**Commit Status:** ${{ steps.setup.outputs.behind_info }}
This PR proposes creating release tag \`${{ steps.inputs.outputs.version }}\` at commit \`${{ steps.inputs.outputs.commit_hash }}\`.
### Approval Process
This PR requires **approval from 2+ maintainers** before the tag can be created.
### What happens next?
1. Maintainers review this proposal
2. When 2+ maintainer approvals are received, an automated workflow will post tagging instructions
3. A maintainer manually creates and pushes the signed tag
4. The release workflow is triggered automatically by the tag push
5. Upon release completion, this PR is closed and the branch is deleted
### Changes Since Last Release
${changelog}
### Release Checklist
- [ ] All tests pass
- [ ] Security review completed
- [ ] Documentation updated
- [ ] Breaking changes documented
---
**Note:** Tag creation is manual and requires a signed tag from a maintainer.`,
draft: true
});
// Add labels
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.data.number,
labels: ['release-proposal', 'awaiting-approval']
});
console.log(`Created PR: ${pr.data.html_url}`);
return { number: pr.data.number, url: pr.data.html_url };
result-encoding: json
- name: Post summary
run: |
echo "## Release Proposal PR Created! 🚀" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Version: **${{ steps.inputs.outputs.version }}**" >> $GITHUB_STEP_SUMMARY
echo "Commit: **${{ steps.inputs.outputs.commit_hash }}**" >> $GITHUB_STEP_SUMMARY
echo "Status: ${{ steps.setup.outputs.behind_info }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "PR: ${{ fromJson(steps.create_pr.outputs.result).url }}" >> $GITHUB_STEP_SUMMARY
================================================
FILE: .github/workflows/release.yml
================================================
name: Release
on:
push:
tags:
- 'v*.*.*'
env:
# https://github.com/actions/setup-go/issues/491
GOTOOLCHAIN: local
permissions:
contents: read
jobs:
verify-tag:
name: Verify Tag Signature and Approvals
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
issues: write
outputs:
verification_passed: ${{ steps.verify.outputs.passed }}
tag_version: ${{ steps.info.outputs.version }}
proposal_issue_number: ${{ steps.find_proposal.outputs.result && fromJson(steps.find_proposal.outputs.result).number || '' }}
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
# Force fetch upstream tags -- because 65 minutes
# tl;dr: actions/checkout@v3 runs this line:
# git -c protocol.version=2 fetch --no-tags --prune --progress --no-recurse-submodules --depth=1 origin +ebc278ec98bb24f2852b61fde2a9bf2e3d83818b:refs/tags/
# which makes its own local lightweight tag, losing all the annotations in the process. Our earlier script ran:
# git fetch --prune --unshallow
# which doesn't overwrite that tag because that would be destructive.
# Credit to @francislavoie for the investigation.
# https://github.com/actions/checkout/issues/290#issuecomment-680260080
- name: Force fetch upstream tags
run: git fetch --tags --force
- name: Get tag info
id: info
run: |
echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
echo "sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
# https://github.community/t5/GitHub-Actions/How-to-get-just-the-tag-name/m-p/32167/highlight/true#M1027
- name: Print Go version and environment
id: vars
run: |
printf "Using go at: $(which go)\n"
printf "Go version: $(go version)\n"
printf "\n\nGo environment:\n\n"
go env
printf "\n\nSystem environment:\n\n"
env
echo "version_tag=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_OUTPUT
echo "short_sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
# Add "pip install" CLI tools to PATH
echo ~/.local/bin >> $GITHUB_PATH
# Parse semver
TAG=${GITHUB_REF/refs\/tags\//}
SEMVER_RE='[^0-9]*\([0-9]*\)[.]\([0-9]*\)[.]\([0-9]*\)\([0-9A-Za-z\.-]*\)'
TAG_MAJOR=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\1#"`
TAG_MINOR=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\2#"`
TAG_PATCH=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\3#"`
TAG_SPECIAL=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\4#"`
echo "tag_major=${TAG_MAJOR}" >> $GITHUB_OUTPUT
echo "tag_minor=${TAG_MINOR}" >> $GITHUB_OUTPUT
echo "tag_patch=${TAG_PATCH}" >> $GITHUB_OUTPUT
echo "tag_special=${TAG_SPECIAL}" >> $GITHUB_OUTPUT
- name: Validate commits and tag signatures
id: verify
env:
signing_keys: ${{ secrets.SIGNING_KEYS }}
run: |
# Read the string into an array, splitting by IFS
IFS=";" read -ra keys_collection <<< "$signing_keys"
# ref: https://docs.github.com/en/actions/reference/workflows-and-actions/contexts#example-usage-of-the-runner-context
touch "${{ runner.temp }}/allowed_signers"
# Iterate and print the split elements
for item in "${keys_collection[@]}"; do
# trim leading whitespaces
item="${item##*( )}"
# trim trailing whitespaces
item="${item%%*( )}"
IFS=" " read -ra key_components <<< "$item"
# git wants it in format: email address, type, public key
# ssh has it in format: type, public key, email address
echo "${key_components[2]} namespaces=\"git\" ${key_components[0]} ${key_components[1]}" >> "${{ runner.temp }}/allowed_signers"
done
git config set --global gpg.ssh.allowedSignersFile "${{ runner.temp }}/allowed_signers"
echo "Verifying the tag: ${{ steps.vars.outputs.version_tag }}"
# Verify the tag is signed
if ! git verify-tag -v "${{ steps.vars.outputs.version_tag }}" 2>&1; then
echo "❌ Tag verification failed!"
echo "passed=false" >> $GITHUB_OUTPUT
git push --delete origin "${{ steps.vars.outputs.version_tag }}"
exit 1
fi
# Run it again to capture the output
git verify-tag -v "${{ steps.vars.outputs.version_tag }}" 2>&1 | tee /tmp/verify-output.txt;
# SSH verification output typically includes the key fingerprint
# Use GNU grep with Perl regex for cleaner extraction (Linux environment)
KEY_SHA256=$(grep -oP "SHA256:[\"']?\K[A-Za-z0-9+/=]+(?=[\"']?)" /tmp/verify-output.txt | head -1 || echo "")
if [ -z "$KEY_SHA256" ]; then
# Try alternative pattern with "key" prefix
KEY_SHA256=$(grep -oP "key SHA256:[\"']?\K[A-Za-z0-9+/=]+(?=[\"']?)" /tmp/verify-output.txt | head -1 || echo "")
fi
if [ -z "$KEY_SHA256" ]; then
# Fallback: extract any base64-like string (40+ chars)
KEY_SHA256=$(grep -oP '[A-Za-z0-9+/]{40,}=?' /tmp/verify-output.txt | head -1 || echo "")
fi
if [ -z "$KEY_SHA256" ]; then
echo "Somehow could not extract SSH key fingerprint from git verify-tag output"
echo "Cancelling flow and deleting tag"
echo "passed=false" >> $GITHUB_OUTPUT
git push --delete origin "${{ steps.vars.outputs.version_tag }}"
exit 1
fi
echo "✅ Tag verification succeeded!"
echo "passed=true" >> $GITHUB_OUTPUT
echo "key_id=$KEY_SHA256" >> $GITHUB_OUTPUT
- name: Find related release proposal
id: find_proposal
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: |
const version = '${{ steps.vars.outputs.version_tag }}';
// Search for PRs with release-proposal label that match this version
const prs = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open', // Changed to 'all' to find both open and closed PRs
sort: 'updated',
direction: 'desc'
});
// Find the most recent PR for this version
const proposal = prs.data.find(pr =>
pr.title.includes(version) &&
pr.labels.some(label => label.name === 'release-proposal')
);
if (!proposal) {
console.log(`⚠️ No release proposal PR found for ${version}`);
console.log('This might be a hotfix or emergency release');
return { number: null, approved: true, approvals: 0, proposedCommit: null };
}
console.log(`Found proposal PR #${proposal.number} for version ${version}`);
// Extract commit hash from PR body
const commitMatch = proposal.body.match(/\*\*Target Commit:\*\*\s*`([a-f0-9]+)`/);
const proposedCommit = commitMatch ? commitMatch[1] : null;
if (proposedCommit) {
console.log(`Proposal was for commit: ${proposedCommit}`);
} else {
console.log('⚠️ No target commit hash found in PR body');
}
// Get PR reviews to extract approvers
let approvers = 'Validated by automation';
let approvalCount = 2; // Minimum required
try {
const reviews = await github.rest.pulls.listReviews({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: proposal.number
});
// Get latest review per user and filter for approvals
const latestReviewsByUser = {};
reviews.data.forEach(review => {
const username = review.user.login;
if (!latestReviewsByUser[username] || new Date(review.submitted_at) > new Date(latestReviewsByUser[username].submitted_at)) {
latestReviewsByUser[username] = review;
}
});
const approvalReviews = Object.values(latestReviewsByUser).filter(review =>
review.state === 'APPROVED'
);
if (approvalReviews.length > 0) {
approvers = approvalReviews.map(r => '@' + r.user.login).join(', ');
approvalCount = approvalReviews.length;
console.log(`Found ${approvalCount} approvals from: ${approvers}`);
}
} catch (error) {
console.log(`Could not fetch reviews: ${error.message}`);
}
return {
number: proposal.number,
approved: true,
approvals: approvalCount,
approvers: approvers,
proposedCommit: proposedCommit
};
result-encoding: json
- name: Verify proposal commit
run: |
APPROVALS='${{ steps.find_proposal.outputs.result }}'
# Parse JSON
PROPOSED_COMMIT=$(echo "$APPROVALS" | jq -r '.proposedCommit')
CURRENT_COMMIT="${{ steps.info.outputs.sha }}"
echo "Proposed commit: $PROPOSED_COMMIT"
echo "Current commit: $CURRENT_COMMIT"
# Check if commits match (if proposal had a target commit)
if [ "$PROPOSED_COMMIT" != "null" ] && [ -n "$PROPOSED_COMMIT" ]; then
# Normalize both commits to full SHA for comparison
PROPOSED_FULL=$(git rev-parse "$PROPOSED_COMMIT" 2>/dev/null || echo "")
CURRENT_FULL=$(git rev-parse "$CURRENT_COMMIT" 2>/dev/null || echo "")
if [ -z "$PROPOSED_FULL" ]; then
echo "⚠️ Could not resolve proposed commit: $PROPOSED_COMMIT"
elif [ "$PROPOSED_FULL" != "$CURRENT_FULL" ]; then
echo "❌ Commit mismatch!"
echo "The tag points to commit $CURRENT_FULL but the proposal was for $PROPOSED_FULL"
echo "This indicates an error in tag creation."
# Delete the tag remotely
git push --delete origin "${{ steps.vars.outputs.version_tag }}"
echo "Tag ${{steps.vars.outputs.version_tag}} has been deleted"
exit 1
else
echo "✅ Commit hash matches proposal"
fi
else
echo "⚠️ No target commit found in proposal (might be legacy release)"
fi
echo "✅ Tag verification completed"
- name: Update release proposal PR
if: fromJson(steps.find_proposal.outputs.result).number != null
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: |
const result = ${{ steps.find_proposal.outputs.result }};
if (result.number) {
// Add in-progress label
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: result.number,
labels: ['release-in-progress']
});
// Remove approved label if present
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: result.number,
name: 'approved'
});
} catch (e) {
console.log('Approved label not found:', e.message);
}
const commentBody = [
'## 🚀 Release Workflow Started',
'',
'- **Tag:** ${{ steps.info.outputs.version }}',
'- **Signed by key:** ${{ steps.verify.outputs.key_id }}',
'- **Commit:** ${{ steps.info.outputs.sha }}',
'- **Approved by:** ' + result.approvers,
'',
'Release workflow is now running. This PR will be updated when the release is published.'
].join('\n');
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: result.number,
body: commentBody
});
}
- name: Summary
run: |
APPROVALS='${{ steps.find_proposal.outputs.result }}'
PROPOSED_COMMIT=$(echo "$APPROVALS" | jq -r '.proposedCommit // "N/A"')
APPROVERS=$(echo "$APPROVALS" | jq -r '.approvers // "N/A"')
echo "## Tag Verification Summary 🔐" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- **Tag:** ${{ steps.info.outputs.version }}" >> $GITHUB_STEP_SUMMARY
echo "- **Commit:** ${{ steps.info.outputs.sha }}" >> $GITHUB_STEP_SUMMARY
echo "- **Proposed Commit:** $PROPOSED_COMMIT" >> $GITHUB_STEP_SUMMARY
echo "- **Signature:** ✅ Verified" >> $GITHUB_STEP_SUMMARY
echo "- **Signed by:** ${{ steps.verify.outputs.key_id }}" >> $GITHUB_STEP_SUMMARY
echo "- **Approvals:** ✅ Sufficient" >> $GITHUB_STEP_SUMMARY
echo "- **Approved by:** $APPROVERS" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Proceeding with release build..." >> $GITHUB_STEP_SUMMARY
release:
name: Release
needs: verify-tag
if: ${{ needs.verify-tag.outputs.verification_passed == 'true' }}
strategy:
matrix:
os:
- ubuntu-latest
go:
- '1.26'
include:
# Set the minimum Go patch version for the given Go minor
# Usable via ${{ matrix.GO_SEMVER }}
- go: '1.26'
GO_SEMVER: '~1.26.0'
runs-on: ${{ matrix.os }}
# https://github.com/sigstore/cosign/issues/1258#issuecomment-1002251233
# https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#adding-permissions-settings
permissions:
id-token: write
# https://docs.github.com/en/rest/overview/permissions-required-for-github-apps#permission-on-contents
# "Releases" is part of `contents`, so it needs the `write`
contents: write
issues: write
pull-requests: write
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0
with:
egress-policy: audit
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- name: Install Go
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version: ${{ matrix.GO_SEMVER }}
check-latest: true
# Force fetch upstream tags -- because 65 minutes
# tl;dr: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.2.2 runs this line:
# git -c protocol.version=2 fetch --no-tags --prune --progress --no-recurse-submodules --depth=1 origin +ebc278ec98bb24f2852b61fde2a9bf2e3d83818b:refs/tags/
# which makes its own local lightweight tag, losing all the annotations in the process. Our earlier script ran:
# git fetch --prune --unshallow
# which doesn't overwrite that tag because that would be destructive.
# Credit to @francislavoie for the investigation.
# https://github.com/actions/checkout/issues/290#issuecomment-680260080
- name: Force fetch upstream tags
run: git fetch --tags --force
# https://github.community/t5/GitHub-Actions/How-to-get-just-the-tag-name/m-p/32167/highlight/true#M1027
- name: Print Go version and environment
id: vars
run: |
printf "Using go at: $(which go)\n"
printf "Go version: $(go version)\n"
printf "\n\nGo environment:\n\n"
go env
printf "\n\nSystem environment:\n\n"
env
echo "version_tag=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_OUTPUT
echo "short_sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
# Add "pip install" CLI tools to PATH
echo ~/.local/bin >> $GITHUB_PATH
# Parse semver
TAG=${GITHUB_REF/refs\/tags\//}
SEMVER_RE='[^0-9]*\([0-9]*\)[.]\([0-9]*\)[.]\([0-9]*\)\([0-9A-Za-z\.-]*\)'
TAG_MAJOR=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\1#"`
TAG_MINOR=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\2#"`
TAG_PATCH=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\3#"`
TAG_SPECIAL=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\4#"`
echo "tag_major=${TAG_MAJOR}" >> $GITHUB_OUTPUT
echo "tag_minor=${TAG_MINOR}" >> $GITHUB_OUTPUT
echo "tag_patch=${TAG_PATCH}" >> $GITHUB_OUTPUT
echo "tag_special=${TAG_SPECIAL}" >> $GITHUB_OUTPUT
# Cloudsmith CLI tooling for pushing releases
# See https://help.cloudsmith.io/docs/cli
- name: Install Cloudsmith CLI
run: pip install --upgrade cloudsmith-cli
- name: Install Cosign
uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 # main
- name: Cosign version
run: cosign version
- name: Install Syft
uses: anchore/sbom-action/download-syft@17ae1740179002c89186b61233e0f892c3118b11 # main
- name: Syft version
run: syft version
- name: Install xcaddy
run: |
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
xcaddy version
# GoReleaser will take care of publishing those artifacts into the release
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@ec59f474b9834571250b370d4735c50f8e2d1e29 # v7.0.0
with:
version: latest
args: release --clean --timeout 60m
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ steps.vars.outputs.version_tag }}
COSIGN_EXPERIMENTAL: 1
# Only publish on non-special tags (e.g. non-beta)
# We will continue to push to Gemfury for the foreseeable future, although
# Cloudsmith is probably better, to not break things for existing users of Gemfury.
# See https://gemfury.com/caddy/deb:caddy
- name: Publish .deb to Gemfury
if: ${{ steps.vars.outputs.tag_special == '' }}
env:
GEMFURY_PUSH_TOKEN: ${{ secrets.GEMFURY_PUSH_TOKEN }}
run: |
for filename in dist/*.deb; do
# armv6 and armv7 are both "armhf" so we can skip the duplicate
if [[ "$filename" == *"armv6"* ]]; then
echo "Skipping $filename"
continue
fi
curl -F package=@"$filename" https://${GEMFURY_PUSH_TOKEN}:@push.fury.io/caddy/
done
# Publish only special tags (unstable/beta/rc) to the "testing" repo
# See https://cloudsmith.io/~caddy/repos/testing/
- name: Publish .deb to Cloudsmith (special tags)
if: ${{ steps.vars.outputs.tag_special != '' }}
env:
CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }}
run: |
for filename in dist/*.deb; do
# armv6 and armv7 are both "armhf" so we can skip the duplicate
if [[ "$filename" == *"armv6"* ]]; then
echo "Skipping $filename"
continue
fi
echo "Pushing $filename to 'testing'"
cloudsmith push deb caddy/testing/any-distro/any-version $filename
done
# Publish stable tags to Cloudsmith to both repos, "stable" and "testing"
# See https://cloudsmith.io/~caddy/repos/stable/
- name: Publish .deb to Cloudsmith (stable tags)
if: ${{ steps.vars.outputs.tag_special == '' }}
env:
CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }}
run: |
for filename in dist/*.deb; do
# armv6 and armv7 are both "armhf" so we can skip the duplicate
if [[ "$filename" == *"armv6"* ]]; then
echo "Skipping $filename"
continue
fi
echo "Pushing $filename to 'stable'"
cloudsmith push deb caddy/stable/any-distro/any-version $filename
echo "Pushing $filename to 'testing'"
cloudsmith push deb caddy/testing/any-distro/any-version $filename
done
- name: Update release proposal PR
if: needs.verify-tag.outputs.proposal_issue_number != ''
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: |
const prNumber = parseInt('${{ needs.verify-tag.outputs.proposal_issue_number }}');
if (prNumber) {
// Get PR details to find the branch
const pr = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber
});
const branchName = pr.data.head.ref;
// Remove in-progress label
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
name: 'release-in-progress'
});
} catch (e) {
console.log('Label not found:', e.message);
}
// Add released label
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
labels: ['released']
});
// Add final comment
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: '## ✅ Release Published\n\nThe release has been successfully published and is now available.'
});
// Close the PR if it's still open
if (pr.data.state === 'open') {
await github.rest.pulls.update({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber,
state: 'closed'
});
console.log(`Closed PR #${prNumber}`);
}
// Delete the branch
try {
await github.rest.git.deleteRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: `heads/${branchName}`
});
console.log(`Deleted branch: ${branchName}`);
} catch (e) {
console.log(`Could not delete branch ${branchName}: ${e.message}`);
}
}
================================================
FILE: .github/workflows/release_published.yml
================================================
name: Release Published
# Event payload: https://developer.github.com/webhooks/event-payloads/#release
on:
release:
types: [published]
permissions:
contents: read
jobs:
release:
name: Release Published
strategy:
matrix:
os:
- ubuntu-latest
runs-on: ${{ matrix.os }}
permissions:
contents: read
pull-requests: read
actions: write
steps:
# See https://github.com/peter-evans/repository-dispatch
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0
with:
egress-policy: audit
- name: Trigger event on caddyserver/dist
uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # v4.0.1
with:
token: ${{ secrets.REPO_DISPATCH_TOKEN }}
repository: caddyserver/dist
event-type: release-tagged
client-payload: '{"tag": "${{ github.event.release.tag_name }}"}'
- name: Trigger event on caddyserver/caddy-docker
uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # v4.0.1
with:
token: ${{ secrets.REPO_DISPATCH_TOKEN }}
repository: caddyserver/caddy-docker
event-type: release-tagged
client-payload: '{"tag": "${{ github.event.release.tag_name }}"}'
================================================
FILE: .github/workflows/scorecard.yml
================================================
# This workflow uses actions that are not certified by GitHub. They are provided
# by a third-party and are governed by separate terms of service, privacy
# policy, and support documentation.
name: OpenSSF Scorecard supply-chain security
on:
# For Branch-Protection check. Only the default branch is supported. See
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
branch_protection_rule:
# To guarantee Maintained check is occasionally updated. See
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
schedule:
- cron: '20 2 * * 5'
push:
branches: [ "master", "2.*" ]
pull_request:
branches: [ "master", "2.*" ]
# Declare default permissions as read only.
permissions: read-all
jobs:
analysis:
name: Scorecard analysis
runs-on: ubuntu-latest
# `publish_results: true` only works when run from the default branch. conditional can be removed if disabled.
if: github.event.repository.default_branch == github.ref_name || github.event_name == 'pull_request'
permissions:
# Needed to upload the results to code-scanning dashboard.
security-events: write
# Needed to publish results and get a badge (see publish_results below).
id-token: write
# Uncomment the permissions below if installing in a private repository.
# contents: read
# actions: read
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b # v2.15.0
with:
egress-policy: audit
- name: "Checkout code"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
with:
results_file: results.sarif
results_format: sarif
# (Optional) "write" PAT token. Uncomment the `repo_token` line below if:
# - you want to enable the Branch-Protection check on a *public* repository, or
# - you are installing Scorecard on a *private* repository
# To create the PAT, follow the steps in https://github.com/ossf/scorecard-action?tab=readme-ov-file#authentication-with-fine-grained-pat-optional.
# repo_token: ${{ secrets.SCORECARD_TOKEN }}
# Public repositories:
# - Publish results to OpenSSF REST API for easy access by consumers
# - Allows the repository to include the Scorecard badge.
# - See https://github.com/ossf/scorecard-action#publishing-results.
# For private repositories:
# - `publish_results` will always be set to `false`, regardless
# of the value entered here.
publish_results: true
# (Optional) Uncomment file_mode if you have a .gitattributes with files marked export-ignore
# file_mode: git
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab.
- name: "Upload artifact"
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: SARIF file
path: results.sarif
retention-days: 5
# Upload the results to GitHub's code scanning dashboard (optional).
# Commenting out will disable upload of results to your repo's Code Scanning dashboard
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@89a39a4e59826350b863aa6b6252a07ad50cf83e # v3.29.5
with:
sarif_file: results.sarif
================================================
FILE: .gitignore
================================================
_gitignore/
*.log
Caddyfile
Caddyfile.*
!caddyfile/
!caddyfile.go
# artifacts from pprof tooling
*.prof
*.test
# build artifacts and helpers
cmd/caddy/caddy
cmd/caddy/caddy.exe
cmd/caddy/tmp/*.exe
cmd/caddy/.env
# mac specific
.DS_Store
# go modules
vendor
# goreleaser artifacts
dist
caddy-build
caddy-dist
# IDE files
.idea/
.vscode/
================================================
FILE: .golangci.yml
================================================
version: "2"
run:
issues-exit-code: 1
tests: false
build-tags:
- nobadger
- nomysql
- nopgx
output:
formats:
text:
path: stdout
print-linter-name: true
print-issued-lines: true
linters:
default: none
enable:
- asasalint
- asciicheck
- bidichk
- bodyclose
- decorder
- dogsled
- dupl
- dupword
- durationcheck
- errcheck
- errname
- exhaustive
- gosec
- govet
- importas
- ineffassign
- misspell
- modernize
- prealloc
- promlinter
- sloglint
- sqlclosecheck
- staticcheck
- testableexamples
- testifylint
- tparallel
- unconvert
- unused
- wastedassign
- whitespace
- zerologlint
settings:
staticcheck:
checks: ["all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022", "-QF1006", "-QF1008"] # default, and exclude 1 more undesired check
errcheck:
exclude-functions:
- fmt.*
- (go.uber.org/zap/zapcore.ObjectEncoder).AddObject
- (go.uber.org/zap/zapcore.ObjectEncoder).AddArray
exhaustive:
ignore-enum-types: reflect.Kind|svc.Cmd
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
rules:
- linters:
- gosec
text: G115 # TODO: Either we should fix the issues or nuke the linter if it's bad
- linters:
- gosec
text: G107 # we aren't calling unknown URL
- linters:
- gosec
text: G203 # as a web server that's expected to handle any template, this is totally in the hands of the user.
- linters:
- gosec
text: G204 # we're shelling out to known commands, not relying on user-defined input.
- linters:
- gosec
# the choice of weakrand is deliberate, hence the named import "weakrand"
path: modules/caddyhttp/reverseproxy/selectionpolicies.go
text: G404
- linters:
- gosec
path: modules/caddyhttp/reverseproxy/streaming.go
text: G404
- linters:
- dupl
path: modules/logging/filters.go
- linters:
- dupl
path: modules/caddyhttp/matchers.go
- linters:
- dupl
path: modules/caddyhttp/vars.go
- linters:
- errcheck
path: _test\.go
paths:
- third_party$
- builtin$
- examples$
formatters:
enable:
- gci
- gofmt
- gofumpt
- goimports
settings:
gci:
sections:
- standard # Standard section: captures all standard packages.
- default # Default section: contains all imports that could not be matched to another section type.
- prefix(github.com/caddyserver/caddy/v2/cmd) # ensure that this is always at the top and always has a line break.
- prefix(github.com/caddyserver/caddy) # Custom section: groups all imports with the specified Prefix.
custom-order: true
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
================================================
FILE: .goreleaser.yml
================================================
version: 2
before:
hooks:
# The build is done in this particular way to build Caddy in a designated directory named in .gitignore.
# This is so we can run goreleaser on tag without Git complaining of being dirty. The main.go in cmd/caddy directory
# cannot be built within that directory due to changes necessary for the build causing Git to be dirty, which
# subsequently causes gorleaser to refuse running.
- rm -rf caddy-build caddy-dist vendor
# vendor Caddy deps
- go mod vendor
- mkdir -p caddy-build
- cp cmd/caddy/main.go caddy-build/main.go
- /bin/sh -c 'cd ./caddy-build && go mod init caddy'
# prepare syso files for windows embedding
- /bin/sh -c 'for a in amd64 arm64; do XCADDY_SKIP_BUILD=1 GOOS=windows GOARCH=$a xcaddy build {{.Env.TAG}}; done'
- /bin/sh -c 'mv /tmp/buildenv_*/*.syso caddy-build'
# GoReleaser doesn't seem to offer {{.Tag}} at this stage, so we have to embed it into the env
# so we run: TAG=$(git describe --abbrev=0) goreleaser release --rm-dist --skip-publish --skip-validate
- go mod edit -require=github.com/caddyserver/caddy/v2@{{.Env.TAG}} ./caddy-build/go.mod
# as of Go 1.16, `go` commands no longer automatically change go.{mod,sum}. We now have to explicitly
# run `go mod tidy`. The `/bin/sh -c '...'` is because goreleaser can't find cd in PATH without shell invocation.
- /bin/sh -c 'cd ./caddy-build && go mod tidy'
# vendor the deps of the prepared to-build module
- /bin/sh -c 'cd ./caddy-build && go mod vendor'
- git clone --depth 1 https://github.com/caddyserver/dist caddy-dist
- mkdir -p caddy-dist/man
- go mod download
- go run cmd/caddy/main.go manpage --directory ./caddy-dist/man
- gzip -r ./caddy-dist/man/
- /bin/sh -c 'go run cmd/caddy/main.go completion bash > ./caddy-dist/scripts/bash-completion'
builds:
- env:
- CGO_ENABLED=0
- GO111MODULE=on
dir: ./caddy-build
binary: caddy
goos:
- darwin
- linux
- windows
- freebsd
goarch:
- amd64
- arm
- arm64
- s390x
- ppc64le
- riscv64
goarm:
- "5"
- "6"
- "7"
ignore:
- goos: darwin
goarch: arm
- goos: darwin
goarch: ppc64le
- goos: darwin
goarch: s390x
- goos: darwin
goarch: riscv64
- goos: windows
goarch: ppc64le
- goos: windows
goarch: s390x
- goos: windows
goarch: riscv64
- goos: windows
goarch: arm
- goos: freebsd
goarch: ppc64le
- goos: freebsd
goarch: s390x
- goos: freebsd
goarch: riscv64
- goos: freebsd
goarch: arm
goarm: "5"
flags:
- -trimpath
- -mod=readonly
ldflags:
- -s -w
tags:
- nobadger
- nomysql
- nopgx
signs:
- cmd: cosign
signature: "${artifact}.sig"
certificate: '{{ trimsuffix (trimsuffix .Env.artifact ".zip") ".tar.gz" }}.pem'
args: ["sign-blob", "--yes", "--output-signature=${signature}", "--output-certificate", "${certificate}", "${artifact}"]
artifacts: all
sboms:
- artifacts: binary
documents:
- >-
{{ .ProjectName }}_
{{- .Version }}_
{{- if eq .Os "darwin" }}mac{{ else }}{{ .Os }}{{ end }}_
{{- .Arch }}
{{- with .Arm }}v{{ . }}{{ end }}
{{- with .Mips }}_{{ . }}{{ end }}
{{- if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}.sbom
cmd: syft
args: ["$artifact", "--file", "${document}", "--output", "cyclonedx-json"]
archives:
- id: default
format_overrides:
- goos: windows
formats: zip
name_template: >-
{{ .ProjectName }}_
{{- .Version }}_
{{- if eq .Os "darwin" }}mac{{ else }}{{ .Os }}{{ end }}_
{{- .Arch }}
{{- with .Arm }}v{{ . }}{{ end }}
{{- with .Mips }}_{{ . }}{{ end }}
{{- if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}
# package the 'caddy-build' directory into a tarball,
# allowing users to build the exact same set of files as ours.
- id: source
meta: true
name_template: "{{ .ProjectName }}_{{ .Version }}_buildable-artifact"
files:
- src: LICENSE
dst: ./LICENSE
- src: README.md
dst: ./README.md
- src: AUTHORS
dst: ./AUTHORS
- src: ./caddy-build
dst: ./
source:
enabled: true
name_template: '{{ .ProjectName }}_{{ .Version }}_src'
format: 'tar.gz'
# Additional files/template/globs you want to add to the source archive.
#
# Default: empty.
files:
- vendor
checksum:
algorithm: sha512
nfpms:
- id: default
package_name: caddy
vendor: Dyanim
homepage: https://caddyserver.com
maintainer: Matthew Holt <mholt@users.noreply.github.com>
description: |
Caddy - Powerful, enterprise-ready, open source web server with automatic HTTPS written in Go
license: Apache 2.0
formats:
- deb
# - rpm
bindir: /usr/bin
contents:
- src: ./caddy-dist/init/caddy.service
dst: /lib/systemd/system/caddy.service
- src: ./caddy-dist/init/caddy-api.service
dst: /lib/systemd/system/caddy-api.service
- src: ./caddy-dist/welcome/index.html
dst: /usr/share/caddy/index.html
- src: ./caddy-dist/scripts/bash-completion
dst: /etc/bash_completion.d/caddy
- src: ./caddy-dist/config/Caddyfile
dst: /etc/caddy/Caddyfile
type: config
- src: ./caddy-dist/man/*
dst: /usr/share/man/man8/
scripts:
postinstall: ./caddy-dist/scripts/postinstall.sh
preremove: ./caddy-dist/scripts/preremove.sh
postremove: ./caddy-dist/scripts/postremove.sh
provides:
- httpd
release:
github:
owner: caddyserver
name: caddy
draft: true
prerelease: auto
changelog:
sort: asc
filters:
exclude:
- '^chore:'
- '^ci:'
- '^docs?:'
- '^readme:'
- '^tests?:'
- '^\w+\s+' # a hack to remove commit messages without colons thus don't correspond to a package
================================================
FILE: .pre-commit-config.yaml
================================================
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.16.3
hooks:
- id: gitleaks
- repo: https://github.com/golangci/golangci-lint
rev: v1.52.2
hooks:
- id: golangci-lint-config-verify
- id: golangci-lint
- id: golangci-lint-fmt
- repo: https://github.com/jumanjihouse/pre-commit-hooks
rev: 3.0.0
hooks:
- id: shellcheck
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: end-of-file-fixer
- id: trailing-whitespace
================================================
FILE: AUTHORS
================================================
# This is the official list of Caddy Authors for copyright purposes.
# Authors may be either individual people or legal entities.
#
# Not all individual contributors are authors. For the full list of
# contributors, refer to the project's page on GitHub or the repo's
# commit history.
Matthew Holt <Matthew.Holt@gmail.com>
Light Code Labs <sales@lightcodelabs.com>
Ardan Labs <info@ardanlabs.com>
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.
================================================
FILE: README.md
================================================
<p align="center">
<a href="https://caddyserver.com">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/1128849/210187358-e2c39003-9a5e-4dd5-a783-6deb6483ee72.svg">
<source media="(prefers-color-scheme: light)" srcset="https://user-images.githubusercontent.com/1128849/210187356-dfb7f1c5-ac2e-43aa-bb23-fc014280ae1f.svg">
<img src="https://user-images.githubusercontent.com/1128849/210187356-dfb7f1c5-ac2e-43aa-bb23-fc014280ae1f.svg" alt="Caddy" width="550">
</picture>
</a>
<br>
<h3 align="center">a <a href="https://zerossl.com"><img src="https://user-images.githubusercontent.com/55066419/208327323-2770dc16-ec09-43a0-9035-c5b872c2ad7f.svg" height="28" style="vertical-align: -7.7px" valign="middle"></a> project</h3>
</p>
<hr>
<h3 align="center">Every site on HTTPS</h3>
<p align="center">Caddy is an extensible server platform that uses TLS by default.</p>
<p align="center">
<a href="https://github.com/caddyserver/caddy/releases">Releases</a> ·
<a href="https://caddyserver.com/docs/">Documentation</a> ·
<a href="https://caddy.community">Get Help</a>
</p>
<p align="center">
<a href="https://github.com/caddyserver/caddy/actions/workflows/ci.yml"><img src="https://github.com/caddyserver/caddy/actions/workflows/ci.yml/badge.svg"></a>
<a href="https://www.bestpractices.dev/projects/7141"><img src="https://www.bestpractices.dev/projects/7141/badge"></a>
<a href="https://pkg.go.dev/github.com/caddyserver/caddy/v2"><img src="https://img.shields.io/badge/godoc-reference-%23007d9c.svg"></a>
<a href="https://x.com/caddyserver" title="@caddyserver on Twitter"><img src="https://img.shields.io/twitter/follow/caddyserver" alt="@caddyserver on Twitter"></a>
<a href="https://caddy.community" title="Caddy Forum"><img src="https://img.shields.io/badge/community-forum-ff69b4.svg" alt="Caddy Forum"></a>
<br>
<a href="https://sourcegraph.com/github.com/caddyserver/caddy?badge" title="Caddy on Sourcegraph"><img src="https://sourcegraph.com/github.com/caddyserver/caddy/-/badge.svg" alt="Caddy on Sourcegraph"></a>
<a href="https://cloudsmith.io/~caddy/repos/"><img src="https://img.shields.io/badge/OSS%20hosting%20by-cloudsmith-blue?logo=cloudsmith" alt="Cloudsmith"></a>
</p>
<p align="center">
<b>Powered by</b>
<br>
<a href="https://github.com/caddyserver/certmagic">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/55066419/206946718-740b6371-3df3-4d72-a822-47e4c48af999.png">
<source media="(prefers-color-scheme: light)" srcset="https://user-images.githubusercontent.com/1128849/49704830-49d37200-fbd5-11e8-8385-767e0cd033c3.png">
<img src="https://user-images.githubusercontent.com/1128849/49704830-49d37200-fbd5-11e8-8385-767e0cd033c3.png" alt="CertMagic" width="250">
</picture>
</a>
</p>
<!-- Warp sponsorship requests this section -->
<div align="center" markdown="1">
<hr>
<sup>Special thanks to:</sup>
<br>
<a href="https://go.warp.dev/caddy">
<img alt="Warp sponsorship" width="400" src="https://github.com/user-attachments/assets/c8efffde-18c7-4af4-83ed-b1aba2dda394">
</a>
### [Warp, built for coding with multiple AI agents](https://go.warp.dev/caddy)
[Available for MacOS, Linux, & Windows](https://go.warp.dev/caddy)<br>
</div>
<hr>
### Menu
- [Features](#features)
- [Install](#install)
- [Build from source](#build-from-source)
- [For development](#for-development)
- [With version information and/or plugins](#with-version-information-andor-plugins)
- [Quick start](#quick-start)
- [Overview](#overview)
- [Full documentation](#full-documentation)
- [Getting help](#getting-help)
- [About](#about)
## [Features](https://caddyserver.com/features)
- **Easy configuration** with the [Caddyfile](https://caddyserver.com/docs/caddyfile)
- **Powerful configuration** with its [native JSON config](https://caddyserver.com/docs/json/)
- **Dynamic configuration** with the [JSON API](https://caddyserver.com/docs/api)
- [**Config adapters**](https://caddyserver.com/docs/config-adapters) if you don't like JSON
- **Automatic HTTPS** by default
- [ZeroSSL](https://zerossl.com) and [Let's Encrypt](https://letsencrypt.org) for public names
- Fully-managed local CA for internal names & IPs
- Can coordinate with other Caddy instances in a cluster
- Multi-issuer fallback
- Encrypted ClientHello (ECH) support
- **Stays up when other servers go down** due to TLS/OCSP/certificate-related issues
- **Production-ready** after serving trillions of requests and managing millions of TLS certificates
- **Scales to hundreds of thousands of sites** as proven in production
- **HTTP/1.1, HTTP/2, and HTTP/3** all supported by default
- **Highly extensible** [modular architecture](https://caddyserver.com/docs/architecture) lets Caddy do anything without bloat
- **Runs anywhere** with **no external dependencies** (not even libc)
- Written in Go, a language with higher **memory safety guarantees** than other servers
- Actually **fun to use**
- So much more to [discover](https://caddyserver.com/features)
## Install
The simplest, cross-platform way to get started is to download Caddy from [GitHub Releases](https://github.com/caddyserver/caddy/releases) and place the executable file in your PATH.
See [our online documentation](https://caddyserver.com/docs/install) for other install instructions.
## Build from source
Requirements:
- [Go 1.25.0 or newer](https://golang.org/dl/)
### For development
_**Note:** These steps [will not embed proper version information](https://github.com/golang/go/issues/29228). For that, please follow the instructions in the next section._
```bash
$ git clone "https://github.com/caddyserver/caddy.git"
$ cd caddy/cmd/caddy/
$ go build
```
When you run Caddy, it may try to bind to low ports unless otherwise specified in your config. If your OS requires elevated privileges for this, you will need to give your new binary permission to do so. On Linux, this can be done easily with: `sudo setcap cap_net_bind_service=+ep ./caddy`
If you prefer to use `go run` which only creates temporary binaries, you can still do this with the included `setcap.sh` like so:
```bash
$ go run -exec ./setcap.sh main.go
```
If you don't want to type your password for `setcap`, use `sudo visudo` to edit your sudoers file and allow your user account to run that command without a password, for example:
```
username ALL=(ALL:ALL) NOPASSWD: /usr/sbin/setcap
```
replacing `username` with your actual username. Please be careful and only do this if you know what you are doing! We are only qualified to document how to use Caddy, not Go tooling or your computer, and we are providing these instructions for convenience only; please learn how to use your own computer at your own risk and make any needful adjustments.
Then you can run the tests in all modules or a specific one:
```bash
$ go test ./...
$ go test ./modules/caddyhttp/tracing/
```
### With version information and/or plugins
Using [our builder tool, `xcaddy`](https://github.com/caddyserver/xcaddy)...
```bash
$ xcaddy build
```
...the following steps are automated:
1. Create a new folder: `mkdir caddy`
2. Change into it: `cd caddy`
3. Copy [Caddy's main.go](https://github.com/caddyserver/caddy/blob/master/cmd/caddy/main.go) into the empty folder. Add imports for any custom plugins you want to add.
4. Initialize a Go module: `go mod init caddy`
5. (Optional) Pin Caddy version: `go get github.com/caddyserver/caddy/v2@version` replacing `version` with a git tag, commit, or branch name.
6. (Optional) Add plugins by adding their import: `_ "import/path/here"`
7. Compile: `go build -tags=nobadger,nomysql,nopgx`
## Quick start
The [Caddy website](https://caddyserver.com/docs/) has documentation that includes tutorials, quick-start guides, reference, and more.
**We recommend that all users -- regardless of experience level -- do our [Getting Started](https://caddyserver.com/docs/getting-started) guide to become familiar with using Caddy.**
If you've only got a minute, [the website has several quick-start tutorials](https://caddyserver.com/docs/quick-starts) to choose from! However, after finishing a quick-start tutorial, please read more documentation to understand how the software works. 🙂
## Overview
Caddy is most often used as an HTTPS server, but it is suitable for any long-running Go program. First and foremost, it is a platform to run Go applications. Caddy "apps" are just Go programs that are implemented as Caddy modules. Two apps -- `tls` and `http` -- ship standard with Caddy.
Caddy apps instantly benefit from [automated documentation](https://caddyserver.com/docs/json/), graceful on-line [config changes via API](https://caddyserver.com/docs/api), and unification with other Caddy apps.
Although [JSON](https://caddyserver.com/docs/json/) is Caddy's native config language, Caddy can accept input from [config adapters](https://caddyserver.com/docs/config-adapters) which can essentially convert any config format of your choice into JSON: Caddyfile, JSON 5, YAML, TOML, NGINX config, and more.
The primary way to configure Caddy is through [its API](https://caddyserver.com/docs/api), but if you prefer config files, the [command-line interface](https://caddyserver.com/docs/command-line) supports those too.
Caddy exposes an unprecedented level of control compared to any web server in existence. In Caddy, you are usually setting the actual values of the initialized types in memory that power everything from your HTTP handlers and TLS handshakes to your storage medium. Caddy is also ridiculously extensible, with a powerful plugin system that makes vast improvements over other web servers.
To wield the power of this design, you need to know how the config document is structured. Please see [our documentation site](https://caddyserver.com/docs/) for details about [Caddy's config structure](https://caddyserver.com/docs/json/).
Nearly all of Caddy's configuration is contained in a single config document, rather than being scattered across CLI flags and env variables and a configuration file as with other web servers. This makes managing your server config more straightforward and reduces hidden variables/factors.
## Full documentation
Our website has complete documentation:
**https://caddyserver.com/docs/**
The docs are also open source. You can contribute to them here: https://github.com/caddyserver/website
## Getting help
- We advise companies using Caddy to secure a support contract through [Ardan Labs](https://www.ardanlabs.com) before help is needed.
- A [sponsorship](https://github.com/sponsors/mholt) goes a long way! We can offer private help to sponsors. If Caddy is benefitting your company, please consider a sponsorship. This not only helps fund full-time work to ensure the longevity of the project, it provides your company the resources, support, and discounts you need; along with being a great look for your company to your customers and potential customers!
- Individuals can exchange help for free on our community forum at https://caddy.community. Remember that people give help out of their spare time and good will. The best way to get help is to give it first!
Please use our [issue tracker](https://github.com/caddyserver/caddy/issues) only for bug reports and feature requests, i.e. actionable development items (support questions will usually be referred to the forums).
## About
Matthew Holt began developing Caddy in 2014 while studying computer science at Brigham Young University. (The name "Caddy" was chosen because this software helps with the tedious, mundane tasks of serving the Web, and is also a single place for multiple things to be organized together.) It soon became the first web server to use HTTPS automatically and by default, and now has hundreds of contributors and has served trillions of HTTPS requests.
**The name "Caddy" is trademarked.** The name of the software is "Caddy", not "Caddy Server" or "CaddyServer". Please call it "Caddy" or, if you wish to clarify, "the Caddy web server". Caddy is a registered trademark of Stack Holdings GmbH.
- _Project on X: [@caddyserver](https://x.com/caddyserver)_
- _Author on X: [@mholt6](https://x.com/mholt6)_
Caddy is a project of [ZeroSSL](https://zerossl.com), an HID Global company.
Debian package repository hosting is graciously provided by [Cloudsmith](https://cloudsmith.com). Cloudsmith is the only fully hosted, cloud-native, universal package management solution, that enables your organization to create, store and share packages in any format, to any place, with total confidence.
================================================
FILE: admin.go
================================================
// 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 caddy
import (
"bytes"
"context"
"crypto"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"encoding/json"
"errors"
"expvar"
"fmt"
"hash"
"io"
"net"
"net/http"
"net/http/pprof"
"net/url"
"os"
"path"
"regexp"
"slices"
"strconv"
"strings"
"sync"
"time"
"github.com/caddyserver/certmagic"
"github.com/cespare/xxhash/v2"
"github.com/prometheus/client_golang/prometheus"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
// testCertMagicStorageOverride is a package-level test hook. Tests may set
// this variable to provide a temporary certmagic.Storage so that cert
// management in tests does not hit the real default storage on disk.
// This must NOT be set in production code.
var testCertMagicStorageOverride certmagic.Storage
func init() {
// The hard-coded default `DefaultAdminListen` can be overridden
// by setting the `CADDY_ADMIN` environment variable.
// The environment variable may be used by packagers to change
// the default admin address to something more appropriate for
// that platform. See #5317 for discussion.
if env, exists := os.LookupEnv("CADDY_ADMIN"); exists {
DefaultAdminListen = env
}
}
// AdminConfig configures Caddy's API endpoint, which is used
// to manage Caddy while it is running.
type AdminConfig struct {
// If true, the admin endpoint will be completely disabled.
// Note that this makes any runtime changes to the config
// impossible, since the interface to do so is through the
// admin endpoint.
Disabled bool `json:"disabled,omitempty"`
// The address to which the admin endpoint's listener should
// bind itself. Can be any single network address that can be
// parsed by Caddy. Accepts placeholders.
// Default: the value of the `CADDY_ADMIN` environment variable,
// or `localhost:2019` otherwise.
//
// Remember: When changing this value through a config reload,
// be sure to use the `--address` CLI flag to specify the current
// admin address if the currently-running admin endpoint is not
// the default address.
Listen string `json:"listen,omitempty"`
// If true, CORS headers will be emitted, and requests to the
// API will be rejected if their `Host` and `Origin` headers
// do not match the expected value(s). Use `origins` to
// customize which origins/hosts are allowed. If `origins` is
// not set, the listen address is the only value allowed by
// default. Enforced only on local (plaintext) endpoint.
EnforceOrigin bool `json:"enforce_origin,omitempty"`
// The list of allowed origins/hosts for API requests. Only needed
// if accessing the admin endpoint from a host different from the
// socket's network interface or if `enforce_origin` is true. If not
// set, the listener address will be the default value. If set but
// empty, no origins will be allowed. Enforced only on local
// (plaintext) endpoint.
Origins []string `json:"origins,omitempty"`
// Options pertaining to configuration management.
Config *ConfigSettings `json:"config,omitempty"`
// Options that establish this server's identity. Identity refers to
// credentials which can be used to uniquely identify and authenticate
// this server instance. This is required if remote administration is
// enabled (but does not require remote administration to be enabled).
// Default: no identity management.
Identity *IdentityConfig `json:"identity,omitempty"`
// Options pertaining to remote administration. By default, remote
// administration is disabled. If enabled, identity management must
// also be configured, as that is how the endpoint is secured.
// See the neighboring "identity" object.
//
// EXPERIMENTAL: This feature is subject to change.
Remote *RemoteAdmin `json:"remote,omitempty"`
// Holds onto the routers so that we can later provision them
// if they require provisioning.
routers []AdminRouter
}
// ConfigSettings configures the management of configuration.
type ConfigSettings struct {
// Whether to keep a copy of the active config on disk. Default is true.
// Note that "pulled" dynamic configs (using the neighboring "load" module)
// are not persisted; only configs that are pushed to Caddy get persisted.
Persist *bool `json:"persist,omitempty"`
// Loads a new configuration. This is helpful if your configs are
// managed elsewhere and you want Caddy to pull its config dynamically
// when it starts. The pulled config completely replaces the current
// one, just like any other config load. It is an error if a pulled
// config is configured to pull another config without a load_delay,
// as this creates a tight loop.
//
// EXPERIMENTAL: Subject to change.
LoadRaw json.RawMessage `json:"load,omitempty" caddy:"namespace=caddy.config_loaders inline_key=module"`
// The duration after which to load config. If set, config will be pulled
// from the config loader after this duration. A delay is required if a
// dynamically-loaded config is configured to load yet another config. To
// load configs on a regular interval, ensure this value is set the same
// on all loaded configs; it can also be variable if needed, and to stop
// the loop, simply remove dynamic config loading from the next-loaded
// config.
//
// EXPERIMENTAL: Subject to change.
LoadDelay Duration `json:"load_delay,omitempty"`
}
// IdentityConfig configures management of this server's identity. An identity
// consists of credentials that uniquely verify this instance; for example,
// TLS certificates (public + private key pairs).
type IdentityConfig struct {
// List of names or IP addresses which refer to this server.
// Certificates will be obtained for these identifiers so
// secure TLS connections can be made using them.
Identifiers []string `json:"identifiers,omitempty"`
// Issuers that can provide this admin endpoint its identity
// certificate(s). Default: ACME issuers configured for
// ZeroSSL and Let's Encrypt. Be sure to change this if you
// require credentials for private identifiers.
IssuersRaw []json.RawMessage `json:"issuers,omitempty" caddy:"namespace=tls.issuance inline_key=module"`
issuers []certmagic.Issuer
}
// RemoteAdmin enables and configures remote administration. If enabled,
// a secure listener enforcing mutual TLS authentication will be started
// on a different port from the standard plaintext admin server.
//
// This endpoint is secured using identity management, which must be
// configured separately (because identity management does not depend
// on remote administration). See the admin/identity config struct.
//
// EXPERIMENTAL: Subject to change.
type RemoteAdmin struct {
// The address on which to start the secure listener. Accepts placeholders.
// Default: :2021
Listen string `json:"listen,omitempty"`
// List of access controls for this secure admin endpoint.
// This configures TLS mutual authentication (i.e. authorized
// client certificates), but also application-layer permissions
// like which paths and methods each identity is authorized for.
AccessControl []*AdminAccess `json:"access_control,omitempty"`
}
// AdminAccess specifies what permissions an identity or group
// of identities are granted.
type AdminAccess struct {
// Base64-encoded DER certificates containing public keys to accept.
// (The contents of PEM certificate blocks are base64-encoded DER.)
// Any of these public keys can appear in any part of a verified chain.
PublicKeys []string `json:"public_keys,omitempty"`
// Limits what the associated identities are allowed to do.
// If unspecified, all permissions are granted.
Permissions []AdminPermissions `json:"permissions,omitempty"`
publicKeys []crypto.PublicKey
}
// AdminPermissions specifies what kinds of requests are allowed
// to be made to the admin endpoint.
type AdminPermissions struct {
// The API paths allowed. Paths are simple prefix matches.
// Any subpath of the specified paths will be allowed.
Paths []string `json:"paths,omitempty"`
// The HTTP methods allowed for the given paths.
Methods []string `json:"methods,omitempty"`
}
// newAdminHandler reads admin's config and returns an http.Handler suitable
// for use in an admin endpoint server, which will be listening on listenAddr.
func (admin *AdminConfig) newAdminHandler(addr NetworkAddress, remote bool, _ Context) adminHandler {
muxWrap := adminHandler{mux: http.NewServeMux()}
// secure the local or remote endpoint respectively
if remote {
muxWrap.remoteControl = admin.Remote
} else {
// see comment in allowedOrigins() as to why we disable the host check for unix/fd networks
muxWrap.enforceHost = !addr.isWildcardInterface() && !addr.IsUnixNetwork() && !addr.IsFdNetwork()
muxWrap.allowedOrigins = admin.allowedOrigins(addr)
muxWrap.enforceOrigin = admin.EnforceOrigin
}
addRouteWithMetrics := func(pattern string, handlerLabel string, h http.Handler) {
labels := prometheus.Labels{"path": pattern, "handler": handlerLabel}
h = instrumentHandlerCounter(
adminMetrics.requestCount.MustCurryWith(labels),
h,
)
muxWrap.mux.Handle(pattern, h)
}
// addRoute just calls muxWrap.mux.Handle after
// wrapping the handler with error handling
addRoute := func(pattern string, handlerLabel string, h AdminHandler) {
wrapper := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err := h.ServeHTTP(w, r)
if err != nil {
labels := prometheus.Labels{
"path": pattern,
"handler": handlerLabel,
"method": strings.ToUpper(r.Method),
}
adminMetrics.requestErrors.With(labels).Inc()
}
muxWrap.handleError(w, r, err)
})
addRouteWithMetrics(pattern, handlerLabel, wrapper)
}
const handlerLabel = "admin"
// register standard config control endpoints
addRoute("/"+rawConfigKey+"/", handlerLabel, AdminHandlerFunc(handleConfig))
addRoute("/id/", handlerLabel, AdminHandlerFunc(handleConfigID))
addRoute("/stop", handlerLabel, AdminHandlerFunc(handleStop))
// register debugging endpoints
addRouteWithMetrics("/debug/pprof/", handlerLabel, http.HandlerFunc(pprof.Index))
addRouteWithMetrics("/debug/pprof/cmdline", handlerLabel, http.HandlerFunc(pprof.Cmdline))
addRouteWithMetrics("/debug/pprof/profile", handlerLabel, http.HandlerFunc(pprof.Profile))
addRouteWithMetrics("/debug/pprof/symbol", handlerLabel, http.HandlerFunc(pprof.Symbol))
addRouteWithMetrics("/debug/pprof/trace", handlerLabel, http.HandlerFunc(pprof.Trace))
addRouteWithMetrics("/debug/vars", handlerLabel, expvar.Handler())
// register third-party module endpoints
for _, m := range GetModules("admin.api") {
router := m.New().(AdminRouter)
for _, route := range router.Routes() {
addRoute(route.Pattern, handlerLabel, route.Handler)
}
admin.routers = append(admin.routers, router)
}
return muxWrap
}
// provisionAdminRouters provisions all the router modules
// in the admin.api namespace that need provisioning.
func (admin *AdminConfig) provisionAdminRouters(ctx Context) error {
for _, router := range admin.routers {
provisioner, ok := router.(Provisioner)
if !ok {
continue
}
err := provisioner.Provision(ctx)
if err != nil {
return err
}
}
// We no longer need the routers once provisioned, allow for GC
admin.routers = nil
return nil
}
// allowedOrigins returns a list of origins that are allowed.
// If admin.Origins is nil (null), the provided listen address
// will be used as the default origin. If admin.Origins is
// empty, no origins will be allowed, effectively bricking the
// endpoint for non-unix-socket endpoints, but whatever.
func (admin AdminConfig) allowedOrigins(addr NetworkAddress) []*url.URL {
uniqueOrigins := make(map[string]struct{})
for _, o := range admin.Origins {
uniqueOrigins[o] = struct{}{}
}
// RFC 2616, Section 14.26:
// "A client MUST include a Host header field in all HTTP/1.1 request
// messages. If the requested URI does not include an Internet host
// name for the service being requested, then the Host header field MUST
// be given with an empty value."
//
// UPDATE July 2023: Go broke this by patching a minor security bug in 1.20.6.
// Understandable, but frustrating. See:
// https://github.com/golang/go/issues/60374
// See also the discussion here:
// https://github.com/golang/go/issues/61431
//
// We can no longer conform to RFC 2616 Section 14.26 from either Go or curl
// in purity. (Curl allowed no host between 7.40 and 7.50, but now requires a
// bogus host; see https://superuser.com/a/925610.) If we disable Host/Origin
// security checks, the infosec community assures me that it is secure to do
// so, because:
//
// 1) Browsers do not allow access to unix sockets
// 2) DNS is irrelevant to unix sockets
//
// If either of those two statements ever fail to hold true, it is not the
// fault of Caddy.
//
// Thus, we do not fill out allowed origins and do not enforce Host
// requirements for unix sockets. Enforcing it leads to confusion and
// frustration, when UDS have their own permissions from the OS.
// Enforcing host requirements here is effectively security theater,
// and a false sense of security.
//
// See also the discussion in #6832.
if admin.Origins == nil && !addr.IsUnixNetwork() && !addr.IsFdNetwork() {
if addr.isLoopback() {
uniqueOrigins[net.JoinHostPort("localhost", addr.port())] = struct{}{}
uniqueOrigins[net.JoinHostPort("::1", addr.port())] = struct{}{}
uniqueOrigins[net.JoinHostPort("127.0.0.1", addr.port())] = struct{}{}
} else {
uniqueOrigins[addr.JoinHostPort(0)] = struct{}{}
}
}
allowed := make([]*url.URL, 0, len(uniqueOrigins))
for originStr := range uniqueOrigins {
var origin *url.URL
if strings.Contains(originStr, "://") {
var err error
origin, err = url.Parse(originStr)
if err != nil {
continue
}
origin.Path = ""
origin.RawPath = ""
origin.Fragment = ""
origin.RawFragment = ""
origin.RawQuery = ""
} else {
origin = &url.URL{Host: originStr}
}
allowed = append(allowed, origin)
}
return allowed
}
// replaceLocalAdminServer replaces the running local admin server
// according to the relevant configuration in cfg. If no configuration
// for the admin endpoint exists in cfg, a default one is used, so
// that there is always an admin server (unless it is explicitly
// configured to be disabled).
// Critically note that some elements and functionality of the context
// may not be ready, e.g. storage. Tread carefully.
func replaceLocalAdminServer(cfg *Config, ctx Context) error {
// always* be sure to close down the old admin endpoint
// as gracefully as possible, even if the new one is
// disabled -- careful to use reference to the current
// (old) admin endpoint since it will be different
// when the function returns
// (* except if the new one fails to start)
oldAdminServer := localAdminServer
var err error
defer func() {
// do the shutdown asynchronously so that any
// current API request gets a response; this
// goroutine may last a few seconds
if oldAdminServer != nil && err == nil {
go func(oldAdminServer *http.Server) {
err := stopAdminServer(oldAdminServer)
if err != nil {
Log().Named("admin").Error("stopping current admin endpoint", zap.Error(err))
}
}(oldAdminServer)
}
}()
// set a default if admin wasn't otherwise configured
if cfg.Admin == nil {
cfg.Admin = &AdminConfig{
Listen: DefaultAdminListen,
}
}
// if new admin endpoint is to be disabled, we're done
if cfg.Admin.Disabled {
Log().Named("admin").Warn("admin endpoint disabled")
return nil
}
// extract a singular listener address
addr, err := parseAdminListenAddr(cfg.Admin.Listen, DefaultAdminListen)
if err != nil {
return err
}
handler := cfg.Admin.newAdminHandler(addr, false, ctx)
// run the provisioners for loaded modules to make sure local
// state is properly re-initialized in the new admin server
err = cfg.Admin.provisionAdminRouters(ctx)
if err != nil {
return err
}
ln, err := addr.Listen(context.TODO(), 0, net.ListenConfig{})
if err != nil {
return err
}
serverMu.Lock()
localAdminServer = &http.Server{
Addr: addr.String(), // for logging purposes only
Handler: handler,
ReadTimeout: 10 * time.Second,
ReadHeaderTimeout: 5 * time.Second,
IdleTimeout: 60 * time.Second,
MaxHeaderBytes: 1024 * 64,
}
serverMu.Unlock()
adminLogger := Log().Named("admin")
go func() {
serverMu.Lock()
server := localAdminServer
serverMu.Unlock()
if err := server.Serve(ln.(net.Listener)); !errors.Is(err, http.ErrServerClosed) {
adminLogger.Error("admin server shutdown for unknown reason", zap.Error(err))
}
}()
adminLogger.Info("admin endpoint started",
zap.String("address", addr.String()),
zap.Bool("enforce_origin", cfg.Admin.EnforceOrigin),
zap.Array("origins", loggableURLArray(handler.allowedOrigins)))
if !handler.enforceHost {
adminLogger.Warn("admin endpoint on open interface; host checking disabled",
zap.String("address", addr.String()))
}
return nil
}
// manageIdentity sets up automated identity management for this server.
func manageIdentity(ctx Context, cfg *Config) error {
if cfg == nil || cfg.Admin == nil || cfg.Admin.Identity == nil {
return nil
}
// set default issuers; this is pretty hacky because we can't
// import the caddytls package -- but it works
if cfg.Admin.Identity.IssuersRaw == nil {
cfg.Admin.Identity.IssuersRaw = []json.RawMessage{
json.RawMessage(`{"module": "acme"}`),
}
}
// load and provision issuer modules
if cfg.Admin.Identity.IssuersRaw != nil {
val, err := ctx.LoadModule(cfg.Admin.Identity, "IssuersRaw")
if err != nil {
return fmt.Errorf("loading identity issuer modules: %s", err)
}
for _, issVal := range val.([]any) {
cfg.Admin.Identity.issuers = append(cfg.Admin.Identity.issuers, issVal.(certmagic.Issuer))
}
}
// we'll make a new cache when we make the CertMagic config, so stop any previous cache
if identityCertCache != nil {
identityCertCache.Stop()
}
logger := Log().Named("admin.identity")
cmCfg := cfg.Admin.Identity.certmagicConfig(logger, true)
// issuers have circular dependencies with the configs because,
// as explained in the caddytls package, they need access to the
// correct storage and cache to solve ACME challenges
for _, issuer := range cfg.Admin.Identity.issuers {
// avoid import cycle with caddytls package, so manually duplicate the interface here, yuck
if annoying, ok := issuer.(interface{ SetConfig(cfg *certmagic.Config) }); ok {
annoying.SetConfig(cmCfg)
}
}
// obtain and renew server identity certificate(s)
return cmCfg.ManageAsync(ctx, cfg.Admin.Identity.Identifiers)
}
// replaceRemoteAdminServer replaces the running remote admin server
// according to the relevant configuration in cfg. It stops any previous
// remote admin server and only starts a new one if configured.
func replaceRemoteAdminServer(ctx Context, cfg *Config) error {
if cfg == nil {
return nil
}
remoteLogger := Log().Named("admin.remote")
oldAdminServer := remoteAdminServer
defer func() {
if oldAdminServer != nil {
go func(oldAdminServer *http.Server) {
err := stopAdminServer(oldAdminServer)
if err != nil {
Log().Named("admin").Error("stopping current secure admin endpoint", zap.Error(err))
}
}(oldAdminServer)
}
}()
if cfg.Admin == nil || cfg.Admin.Remote == nil {
return nil
}
addr, err := parseAdminListenAddr(cfg.Admin.Remote.Listen, DefaultRemoteAdminListen)
if err != nil {
return err
}
// make the HTTP handler but disable Host/Origin enforcement
// because we are using TLS authentication instead
handler := cfg.Admin.newAdminHandler(addr, true, ctx)
// run the provisioners for loaded modules to make sure local
// state is properly re-initialized in the new admin server
err = cfg.Admin.provisionAdminRouters(ctx)
if err != nil {
return err
}
// create client certificate pool for TLS mutual auth, and extract public keys
// so that we can enforce access controls at the application layer
clientCertPool := x509.NewCertPool()
for i, accessControl := range cfg.Admin.Remote.AccessControl {
for j, certBase64 := range accessControl.PublicKeys {
cert, err := decodeBase64DERCert(certBase64)
if err != nil {
return fmt.Errorf("access control %d public key %d: parsing base64 certificate DER: %v", i, j, err)
}
accessControl.publicKeys = append(accessControl.publicKeys, cert.PublicKey)
clientCertPool.AddCert(cert)
}
}
// create TLS config that will enforce mutual authentication
if identityCertCache == nil {
return fmt.Errorf("cannot enable remote admin without a certificate cache; configure identity management to initialize a certificate cache")
}
cmCfg := cfg.Admin.Identity.certmagicConfig(remoteLogger, false)
tlsConfig := cmCfg.TLSConfig()
tlsConfig.NextProtos = nil // this server does not solve ACME challenges
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
tlsConfig.ClientCAs = clientCertPool
// convert logger to stdlib so it can be used by HTTP server
serverLogger, err := zap.NewStdLogAt(remoteLogger, zap.DebugLevel)
if err != nil {
return err
}
serverMu.Lock()
// create secure HTTP server
remoteAdminServer = &http.Server{
Addr: addr.String(), // for logging purposes only
Handler: handler,
TLSConfig: tlsConfig,
ReadTimeout: 10 * time.Second,
ReadHeaderTimeout: 5 * time.Second,
IdleTimeout: 60 * time.Second,
MaxHeaderBytes: 1024 * 64,
ErrorLog: serverLogger,
}
serverMu.Unlock()
// start listener
lnAny, err := addr.Listen(ctx, 0, net.ListenConfig{})
if err != nil {
return err
}
ln := lnAny.(net.Listener)
ln = tls.NewListener(ln, tlsConfig)
go func() {
serverMu.Lock()
server := remoteAdminServer
serverMu.Unlock()
if err := server.Serve(ln); !errors.Is(err, http.ErrServerClosed) {
remoteLogger.Error("admin remote server shutdown for unknown reason", zap.Error(err))
}
}()
remoteLogger.Info("secure admin remote control endpoint started",
zap.String("address", addr.String()))
return nil
}
func (ident *IdentityConfig) certmagicConfig(logger *zap.Logger, makeCache bool) *certmagic.Config {
var cmCfg *certmagic.Config
if ident == nil {
// user might not have configured identity; that's OK, we can still make a
// certmagic config, although it'll be mostly useless for remote management
ident = new(IdentityConfig)
}
// Choose storage: prefer the package-level test override when present,
// otherwise use the configured DefaultStorage. Tests may set an override
// to divert storage into a temporary location. Otherwise, in production
// we use the DefaultStorage since we don't want to act as part of a
// cluster; this storage is for the server's local identity only.
var storage certmagic.Storage
if testCertMagicStorageOverride != nil {
storage = testCertMagicStorageOverride
} else {
storage = DefaultStorage
}
template := certmagic.Config{
Storage: storage,
Logger: logger,
Issuers: ident.issuers,
}
if makeCache {
identityCertCache = certmagic.NewCache(certmagic.CacheOptions{
GetConfigForCert: func(certmagic.Certificate) (*certmagic.Config, error) {
return cmCfg, nil
},
Logger: logger.Named("cache"),
})
}
cmCfg = certmagic.New(identityCertCache, template)
return cmCfg
}
// IdentityCredentials returns this instance's configured, managed identity credentials
// that can be used in TLS client authentication.
func (ctx Context) IdentityCredentials(logger *zap.Logger) ([]tls.Certificate, error) {
if ctx.cfg == nil || ctx.cfg.Admin == nil || ctx.cfg.Admin.Identity == nil {
return nil, fmt.Errorf("no server identity configured")
}
ident := ctx.cfg.Admin.Identity
if len(ident.Identifiers) == 0 {
return nil, fmt.Errorf("no identifiers configured")
}
if logger == nil {
logger = Log()
}
magic := ident.certmagicConfig(logger, false)
return magic.ClientCredentials(ctx, ident.Identifiers)
}
// enforceAccessControls enforces application-layer access controls for r based on remote.
// It expects that the TLS server has already established at least one verified chain of
// trust, and then looks for a matching, authorized public key that is allowed to access
// the defined path(s) using the defined method(s).
func (remote RemoteAdmin) enforceAccessControls(r *http.Request) error {
for _, chain := range r.TLS.VerifiedChains {
for _, peerCert := range chain {
for _, adminAccess := range remote.AccessControl {
for _, allowedKey := range adminAccess.publicKeys {
// see if we found a matching public key; the TLS server already verified the chain
// so we know the client possesses the associated private key; this handy interface
// doesn't appear to be defined anywhere in the std lib, but was implemented here:
// https://github.com/golang/go/commit/b5f2c0f50297fa5cd14af668ddd7fd923626cf8c
comparer, ok := peerCert.PublicKey.(interface{ Equal(crypto.PublicKey) bool })
if !ok || !comparer.Equal(allowedKey) {
continue
}
// key recognized; make sure its HTTP request is permitted
for _, accessPerm := range adminAccess.Permissions {
// verify method
methodFound := accessPerm.Methods == nil || slices.Contains(accessPerm.Methods, r.Method)
if !methodFound {
return APIError{
HTTPStatus: http.StatusForbidden,
Message: "not authorized to use this method",
}
}
// verify path
pathFound := accessPerm.Paths == nil
for _, allowedPath := range accessPerm.Paths {
if strings.HasPrefix(r.URL.Path, allowedPath) {
pathFound = true
break
}
}
if !pathFound {
return APIError{
HTTPStatus: http.StatusForbidden,
Message: "not authorized to access this path",
}
}
}
// public key authorized, method and path allowed
return nil
}
}
}
}
// in theory, this should never happen; with an unverified chain, the TLS server
// should not accept the connection in the first place, and the acceptable cert
// pool is configured using the same list of public keys we verify against
return APIError{
HTTPStatus: http.StatusUnauthorized,
Message: "client identity not authorized",
}
}
func stopAdminServer(srv *http.Server) error {
if srv == nil {
return fmt.Errorf("no admin server")
}
timeout := 10 * time.Second
ctx, cancel := context.WithTimeoutCause(context.Background(), timeout, fmt.Errorf("stopping admin server: %ds timeout", int(timeout.Seconds())))
defer cancel()
err := srv.Shutdown(ctx)
if err != nil {
if cause := context.Cause(ctx); cause != nil && errors.Is(err, context.DeadlineExceeded) {
err = cause
}
return fmt.Errorf("shutting down admin server: %v", err)
}
Log().Named("admin").Info("stopped previous server", zap.String("address", srv.Addr))
return nil
}
// AdminRouter is a type which can return routes for the admin API.
type AdminRouter interface {
Routes() []AdminRoute
}
// AdminRoute represents a route for the admin endpoint.
type AdminRoute struct {
Pattern string
Handler AdminHandler
}
type adminHandler struct {
mux *http.ServeMux
// security for local/plaintext endpoint
enforceOrigin bool
enforceHost bool
allowedOrigins []*url.URL
// security for remote/encrypted endpoint
remoteControl *RemoteAdmin
}
// ServeHTTP is the external entry point for API requests.
// It will only be called once per request.
func (h adminHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ip, port, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
ip = r.RemoteAddr
port = ""
}
log := Log().Named("admin.api").With(
zap.String("method", r.Method),
zap.String("host", r.Host),
zap.String("uri", r.RequestURI),
zap.String("remote_ip", ip),
zap.String("remote_port", port),
zap.Reflect("headers", r.Header),
)
if r.TLS != nil {
log = log.With(
zap.Bool("secure", true),
zap.Int("verified_chains", len(r.TLS.VerifiedChains)),
)
}
if r.RequestURI == "/metrics" {
log.Debug("received request")
} else {
log.Info("received request")
}
h.serveHTTP(w, r)
}
// serveHTTP is the internal entry point for API requests. It may
// be called more than once per request, for example if a request
// is rewritten (i.e. internal redirect).
func (h adminHandler) serveHTTP(w http.ResponseWriter, r *http.Request) {
if h.remoteControl != nil {
// enforce access controls on secure endpoint
if err := h.remoteControl.enforceAccessControls(r); err != nil {
h.handleError(w, r, err)
return
}
}
// common mitigations in browser contexts
if strings.Contains(r.Header.Get("Upgrade"), "websocket") {
// I've never been able demonstrate a vulnerability myself, but apparently
// WebSocket connections originating from browsers aren't subject to CORS
// restrictions, so we'll just be on the safe side
h.handleError(w, r, APIError{
HTTPStatus: http.StatusBadRequest,
Err: errors.New("websocket connections aren't allowed"),
Message: "WebSocket connections aren't allowed.",
})
return
}
if strings.Contains(r.Header.Get("Sec-Fetch-Mode"), "no-cors") {
// turns out web pages can just disable the same-origin policy (!???!?)
// but at least browsers let us know that's the case, holy heck
h.handleError(w, r, APIError{
HTTPStatus: http.StatusBadRequest,
Err: errors.New("client attempted to make request by disabling same-origin policy using no-cors mode"),
Message: "Disabling same-origin restrictions is not allowed.",
})
return
}
if r.Header.Get("Origin") == "null" {
// bug in Firefox in certain cross-origin situations (yikes?)
// (not strictly a security vuln on its own, but it's red flaggy,
// since it seems to manifest in cross-origin contexts)
h.handleError(w, r, APIError{
HTTPStatus: http.StatusBadRequest,
Err: errors.New("invalid origin 'null'"),
Message: "Buggy browser is sending null Origin header.",
})
}
if h.enforceHost {
// DNS rebinding mitigation
err := h.checkHost(r)
if err != nil {
h.handleError(w, r, err)
return
}
}
_, hasOriginHeader := r.Header["Origin"]
_, hasSecHeader := r.Header["Sec-Fetch-Mode"]
if h.enforceOrigin || hasOriginHeader || hasSecHeader {
// cross-site mitigation
origin, err := h.checkOrigin(r)
if err != nil {
h.handleError(w, r, err)
return
}
if r.Method == http.MethodOptions {
w.Header().Set("Access-Control-Allow-Methods", "OPTIONS, GET, POST, PUT, PATCH, DELETE")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Cache-Control")
w.Header().Set("Access-Control-Allow-Credentials", "true")
}
w.Header().Set("Access-Control-Allow-Origin", origin)
}
h.mux.ServeHTTP(w, r)
}
func (h adminHandler) handleError(w http.ResponseWriter, r *http.Request, err error) {
if err == nil {
return
}
if err == errInternalRedir {
h.serveHTTP(w, r)
return
}
apiErr, ok := err.(APIError)
if !ok {
apiErr = APIError{
HTTPStatus: http.StatusInternalServerError,
Err: err,
}
}
if apiErr.HTTPStatus == 0 {
apiErr.HTTPStatus = http.StatusInternalServerError
}
if apiErr.Message == "" && apiErr.Err != nil {
apiErr.Message = apiErr.Err.Error()
}
Log().Named("admin.api").Error("request error",
zap.Error(err),
zap.Int("status_code", apiErr.HTTPStatus),
)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(apiErr.HTTPStatus)
encErr := json.NewEncoder(w).Encode(apiErr)
if encErr != nil {
Log().Named("admin.api").Error("failed to encode error response", zap.Error(encErr))
}
}
// checkHost returns a handler that wraps next such that
// it will only be called if the request's Host header matches
// a trustworthy/expected value. This helps to mitigate DNS
// rebinding attacks.
func (h adminHandler) checkHost(r *http.Request) error {
allowed := slices.ContainsFunc(h.allowedOrigins, func(u *url.URL) bool {
return r.Host == u.Host
})
if !allowed {
return APIError{
HTTPStatus: http.StatusForbidden,
Err: fmt.Errorf("host not allowed: %s", r.Host),
}
}
return nil
}
// checkOrigin ensures that the Origin header, if
// set, matches the intended target; prevents arbitrary
// sites from issuing requests to our listener. It
// returns the origin that was obtained from r.
func (h adminHandler) checkOrigin(r *http.Request) (string, error) {
originStr, origin := h.getOrigin(r)
if origin == nil {
return "", APIError{
HTTPStatus: http.StatusForbidden,
Err: fmt.Errorf("required Origin header is missing or invalid"),
}
}
if !h.originAllowed(origin) {
return "", APIError{
HTTPStatus: http.StatusForbidden,
Err: fmt.Errorf("client is not allowed to access from origin '%s'", originStr),
}
}
return origin.String(), nil
}
func (h adminHandler) getOrigin(r *http.Request) (string, *url.URL) {
origin := r.Header.Get("Origin")
if origin == "" {
origin = r.Header.Get("Referer")
}
originURL, err := url.Parse(origin)
if err != nil {
return origin, nil
}
originURL.Path = ""
originURL.RawPath = ""
originURL.Fragment = ""
originURL.RawFragment = ""
originURL.RawQuery = ""
return origin, originURL
}
func (h adminHandler) originAllowed(origin *url.URL) bool {
for _, allowedOrigin := range h.allowedOrigins {
if allowedOrigin.Scheme != "" && origin.Scheme != allowedOrigin.Scheme {
continue
}
if origin.Host == allowedOrigin.Host {
return true
}
}
return false
}
// etagHasher returns the hasher we used on the config to both
// produce and verify ETags.
func etagHasher() hash.Hash { return xxhash.New() }
// makeEtag returns an Etag header value (including quotes) for
// the given config path and hash of contents at that path.
func makeEtag(path string, hash hash.Hash) string {
return fmt.Sprintf(`"%s %x"`, path, hash.Sum(nil))
}
// This buffer pool is used to keep buffers for
// reading the config file during eTag header generation
var bufferPool = sync.Pool{
New: func() any {
return new(bytes.Buffer)
},
}
func handleConfig(w http.ResponseWriter, r *http.Request) error {
switch r.Method {
case http.MethodGet:
w.Header().Set("Content-Type", "application/json")
hash := etagHasher()
// Read the config into a buffer instead of writing directly to
// the response writer, as we want to set the ETag as the header,
// not the trailer.
buf := bufferPool.Get().(*bytes.Buffer)
buf.Reset()
defer bufferPool.Put(buf)
configWriter := io.MultiWriter(buf, hash)
err := readConfig(r.URL.Path, configWriter)
if err != nil {
return APIError{HTTPStatus: http.StatusBadRequest, Err: err}
}
// we could consider setting up a sync.Pool for the summed
// hashes to reduce GC pressure.
w.Header().Set("Etag", makeEtag(r.URL.Path, hash))
_, err = w.Write(buf.Bytes())
if err != nil {
return APIError{HTTPStatus: http.StatusInternalServerError, Err: err}
}
return nil
case http.MethodPost,
http.MethodPut,
http.MethodPatch,
http.MethodDelete:
// DELETE does not use a body, but the others do
var body []byte
if r.Method != http.MethodDelete {
if ct := r.Header.Get("Content-Type"); !strings.Contains(ct, "/json") {
return APIError{
HTTPStatus: http.StatusBadRequest,
Err: fmt.Errorf("unacceptable content-type: %v; 'application/json' required", ct),
}
}
buf := bufPool.Get().(*bytes.Buffer)
buf.Reset()
defer bufPool.Put(buf)
_, err := io.Copy(buf, r.Body)
if err != nil {
return APIError{
HTTPStatus: http.StatusBadRequest,
Err: fmt.Errorf("reading request body: %v", err),
}
}
body = buf.Bytes()
}
forceReload := r.Header.Get("Cache-Control") == "must-revalidate"
err := changeConfig(r.Method, r.URL.Path, body, r.Header.Get("If-Match"), forceReload)
if err != nil && !errors.Is(err, errSameConfig) {
return err
}
// If this request changed the config, clear the last
// config info we have stored, if it is different from
// the original source.
ClearLastConfigIfDifferent(
r.Header.Get("Caddy-Config-Source-File"),
r.Header.Get("Caddy-Config-Source-Adapter"))
default:
return APIError{
HTTPStatus: http.StatusMethodNotAllowed,
Err: fmt.Errorf("method %s not allowed", r.Method),
}
}
return nil
}
func handleConfigID(w http.ResponseWriter, r *http.Request) error {
idPath := r.URL.Path
parts := strings.Split(idPath, "/")
if len(parts) < 3 || parts[2] == "" {
return APIError{
HTTPStatus: http.StatusBadRequest,
Err: fmt.Errorf("request path is missing object ID"),
}
}
if parts[0] != "" || parts[1] != "id" {
return APIError{
HTTPStatus: http.StatusBadRequest,
Err: fmt.Errorf("malformed object path"),
}
}
id := parts[2]
// map the ID to the expanded path
rawCfgMu.RLock()
expanded, ok := rawCfgIndex[id]
rawCfgMu.RUnlock()
if !ok {
return APIError{
HTTPStatus: http.StatusNotFound,
Err: fmt.Errorf("unknown object ID '%s'", id),
}
}
// piece the full URL path back together
parts = append([]string{expanded}, parts[3:]...)
r.URL.Path = path.Join(parts...)
return errInternalRedir
}
func handleStop(w http.ResponseWriter, r *http.Request) error {
if r.Method != http.MethodPost {
return APIError{
HTTPStatus: http.StatusMethodNotAllowed,
Err: fmt.Errorf("method not allowed"),
}
}
exitProcess(context.Background(), Log().Named("admin.api"))
return nil
}
// unsyncedConfigAccess traverses into the current config and performs
// the operation at path according to method, using body and out as
// needed. This is a low-level, unsynchronized function; most callers
// will want to use changeConfig or readConfig instead. This requires a
// read or write lock on currentCtxMu, depending on method (GET needs
// only a read lock; all others need a write lock).
func unsyncedConfigAccess(method, path string, body []byte, out io.Writer) error {
var err error
var val any
// if there is a request body, decode it into the
// variable that will be set in the config according
// to method and path
if len(body) > 0 {
err = json.Unmarshal(body, &val)
if err != nil {
if jsonErr, ok := err.(*json.SyntaxError); ok {
return fmt.Errorf("decoding request body: %w, at offset %d", jsonErr, jsonErr.Offset)
}
return fmt.Errorf("decoding request body: %w", err)
}
}
enc := json.NewEncoder(out)
cleanPath := strings.Trim(path, "/")
if cleanPath == "" {
return fmt.Errorf("no traversable path")
}
parts := strings.Split(cleanPath, "/")
if len(parts) == 0 {
return fmt.Errorf("path missing")
}
// A path that ends with "..." implies:
// 1) the part before it is an array
// 2) the payload is an array
// and means that the user wants to expand the elements
// in the payload array and append each one into the
// destination array, like so:
// array = append(array, elems...)
// This special case is handled below.
ellipses := parts[len(parts)-1] == "..."
if ellipses {
parts = parts[:len(parts)-1]
}
var ptr any = rawCfg
traverseLoop:
for i, part := range parts {
switch v := ptr.(type) {
case map[string]any:
// if the next part enters a slice, and the slice is our destination,
// handle it specially (because appending to the slice copies the slice
// header, which does not replace the original one like we want)
if arr, ok := v[part].([]any); ok && i == len(parts)-2 {
var idx int
if method != http.MethodPost {
idxStr := parts[len(parts)-1]
idx, err = strconv.Atoi(idxStr)
if err != nil {
return fmt.Errorf("[%s] invalid array index '%s': %v",
path, idxStr, err)
}
if idx < 0 || (method != http.MethodPut && idx >= len(arr)) || idx > len(arr) {
return fmt.Errorf("[%s] array index out of bounds: %s", path, idxStr)
}
}
switch method {
case http.MethodGet:
err = enc.Encode(arr[idx])
if err != nil {
return fmt.Errorf("encoding config: %v", err)
}
case http.MethodPost:
if ellipses {
valArray, ok := val.([]any)
if !ok {
return fmt.Errorf("final element is not an array")
}
v[part] = append(arr, valArray...)
} else {
v[part] = append(arr, val)
}
case http.MethodPut:
// avoid creation of new slice and a second copy (see
// https://github.com/golang/go/wiki/SliceTricks#insert)
arr = append(arr, nil)
copy(arr[idx+1:], arr[idx:])
arr[idx] = val
v[part] = arr
case http.MethodPatch:
arr[idx] = val
case http.MethodDelete:
v[part] = append(arr[:idx], arr[idx+1:]...)
default:
return fmt.Errorf("unrecognized method %s", method)
}
break traverseLoop
}
if i == len(parts)-1 {
switch method {
case http.MethodGet:
err = enc.Encode(v[part])
if err != nil {
return fmt.Errorf("encoding config: %v", err)
}
case http.MethodPost:
// if the part is an existing list, POST appends to
// it, otherwise it just sets or creates the value
if arr, ok := v[part].([]any); ok {
if ellipses {
valArray, ok := val.([]any)
if !ok {
return fmt.Errorf("final element is not an array")
}
v[part] = append(arr, valArray...)
} else {
v[part] = append(arr, val)
}
} else {
v[part] = val
}
case http.MethodPut:
if _, ok := v[part]; ok {
return APIError{
HTTPStatus: http.StatusConflict,
Err: fmt.Errorf("[%s] key already exists: %s", path, part),
}
}
v[part] = val
case http.MethodPatch:
if _, ok := v[part]; !ok {
return APIError{
HTTPStatus: http.StatusNotFound,
Err: fmt.Errorf("[%s] key does not exist: %s", path, part),
}
}
v[part] = val
case http.MethodDelete:
if _, ok := v[part]; !ok {
return APIError{
HTTPStatus: http.StatusNotFound,
Err: fmt.Errorf("[%s] key does not exist: %s", path, part),
}
}
delete(v, part)
default:
return fmt.Errorf("unrecognized method %s", method)
}
} else {
// if we are "PUTting" a new resource, the key(s) in its path
// might not exist yet; that's OK but we need to make them as
// we go, while we still have a pointer from the level above
if v[part] == nil && method == http.MethodPut {
v[part] = make(map[string]any)
}
ptr = v[part]
}
case []any:
partInt, err := strconv.Atoi(part)
if err != nil {
return fmt.Errorf("[/%s] invalid array index '%s': %v",
strings.Join(parts[:i+1], "/"), part, err)
}
if partInt < 0 || partInt >= len(v) {
return fmt.Errorf("[/%s] array index out of bounds: %s",
strings.Join(parts[:i+1], "/"), part)
}
ptr = v[partInt]
default:
return fmt.Errorf("invalid traversal path at: %s", strings.Join(parts[:i+1], "/"))
}
}
return nil
}
// RemoveMetaFields removes meta fields like "@id" from a JSON message
// by using a simple regular expression. (An alternate way to do this
// would be to delete them from the raw, map[string]any
// representation as they are indexed, then iterate the index we made
// and add them back after encoding as JSON, but this is simpler.)
func RemoveMetaFields(rawJSON []byte) []byte {
return idRegexp.ReplaceAllFunc(rawJSON, func(in []byte) []byte {
// matches with a comma on both sides (when "@id" property is
// not the first or last in the object) need to keep exactly
// one comma for correct JSON syntax
comma := []byte{','}
if bytes.HasPrefix(in, comma) && bytes.HasSuffix(in, comma) {
return comma
}
return []byte{}
})
}
// AdminHandler is like http.Handler except ServeHTTP may return an error.
//
// If any handler encounters an error, it should be returned for proper
// handling.
type AdminHandler interface {
ServeHTTP(http.ResponseWriter, *http.Request) error
}
// AdminHandlerFunc is a convenience type like http.HandlerFunc.
type AdminHandlerFunc func(http.ResponseWriter, *http.Request) error
// ServeHTTP implements the Handler interface.
func (f AdminHandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) error {
return f(w, r)
}
// APIError is a structured error that every API
// handler should return for consistency in logging
// and client responses. If Message is unset, then
// Err.Error() will be serialized in its place.
type APIError struct {
HTTPStatus int `json:"-"`
Err error `json:"-"`
Message string `json:"error"`
}
func (e APIError) Error() string {
if e.Err != nil {
return e.Err.Error()
}
return e.Message
}
// parseAdminListenAddr extracts a singular listen address from either addr
// or defaultAddr, returning the network and the address of the listener.
func parseAdminListenAddr(addr string, defaultAddr string) (NetworkAddress, error) {
input, err := NewReplacer().ReplaceOrErr(addr, true, true)
if err != nil {
return NetworkAddress{}, fmt.Errorf("replacing listen address: %v", err)
}
if input == "" {
input = defaultAddr
}
listenAddr, err := ParseNetworkAddress(input)
if err != nil {
return NetworkAddress{}, fmt.Errorf("parsing listener address: %v", err)
}
if listenAddr.PortRangeSize() != 1 {
return NetworkAddress{}, fmt.Errorf("must be exactly one listener address; cannot listen on: %s", listenAddr)
}
return listenAddr, nil
}
// decodeBase64DERCert base64-decodes, then DER-decodes, certStr.
func decodeBase64DERCert(certStr string) (*x509.Certificate, error) {
derBytes, err := base64.StdEncoding.DecodeString(certStr)
if err != nil {
return nil, err
}
return x509.ParseCertificate(derBytes)
}
type loggableURLArray []*url.URL
func (ua loggableURLArray) MarshalLogArray(enc zapcore.ArrayEncoder) error {
if ua == nil {
return nil
}
for _, u := range ua {
enc.AppendString(u.String())
}
return nil
}
var (
// DefaultAdminListen is the address for the local admin
// listener, if none is specified at startup.
DefaultAdminListen = "localhost:2019"
// DefaultRemoteAdminListen is the address for the remote
// (TLS-authenticated) admin listener, if enabled and not
// specified otherwise.
DefaultRemoteAdminListen = ":2021"
)
// PIDFile writes a pidfile to the file at filename. It
// will get deleted before the process gracefully exits.
func PIDFile(filename string) error {
pid := []byte(strconv.Itoa(os.Getpid()) + "\n")
err := os.WriteFile(filename, pid, 0o600)
if err != nil {
return err
}
pidfile = filename
return nil
}
// idRegexp is used to match ID fields and their associated values
// in the config. It also matches adjacent commas so that syntax
// can be preserved no matter where in the object the field appears.
// It supports string and most numeric values.
var idRegexp = regexp.MustCompile(`(?m),?\s*"` + idKey + `"\s*:\s*(-?[0-9]+(\.[0-9]+)?|(?U)".*")\s*,?`)
// pidfile is the name of the pidfile, if any.
var pidfile string
// errInternalRedir indicates an internal redirect
// and is useful when admin API handlers rewrite
// the request; in that case, authentication and
// authorization needs to happen again for the
// rewritten request.
var errInternalRedir = fmt.Errorf("internal redirect; re-authorization required")
const (
rawConfigKey = "config"
idKey = "@id"
)
var bufPool = sync.Pool{
New: func() any {
return new(bytes.Buffer)
},
}
// keep a reference to admin endpoint singletons while they're active
var (
serverMu sync.Mutex
localAdminServer, remoteAdminServer *http.Server
identityCertCache *certmagic.Cache
)
================================================
FILE: admin_test.go
================================================
// 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 caddy
import (
"context"
"crypto/x509"
"encoding/json"
"fmt"
"maps"
"net/http"
"net/http/httptest"
"os"
"reflect"
"sync"
"testing"
"time"
"github.com/caddyserver/certmagic"
"github.com/prometheus/client_golang/prometheus"
dto "github.com/prometheus/client_model/go"
)
var testCfg = []byte(`{
"apps": {
"http": {
"servers": {
"myserver": {
"listen": ["tcp/localhost:8080-8084"],
"read_timeout": "30s"
},
"yourserver": {
"listen": ["127.0.0.1:5000"],
"read_header_timeout": "15s"
}
}
}
}
}
`)
func TestUnsyncedConfigAccess(t *testing.T) {
// each test is performed in sequence, so
// each change builds on the previous ones;
// the config is not reset between tests
for i, tc := range []struct {
method string
path string // rawConfigKey will be prepended
payload string
expect string // JSON representation of what the whole config is expected to be after the request
shouldErr bool
}{
{
method: "POST",
path: "",
payload: `{"foo": "bar", "list": ["a", "b", "c"]}`, // starting value
expect: `{"foo": "bar", "list": ["a", "b", "c"]}`,
},
{
method: "POST",
path: "/foo",
payload: `"jet"`,
expect: `{"foo": "jet", "list": ["a", "b", "c"]}`,
},
{
method: "POST",
path: "/bar",
payload: `{"aa": "bb", "qq": "zz"}`,
expect: `{"foo": "jet", "bar": {"aa": "bb", "qq": "zz"}, "list": ["a", "b", "c"]}`,
},
{
method: "DELETE",
path: "/bar/qq",
expect: `{"foo": "jet", "bar": {"aa": "bb"}, "list": ["a", "b", "c"]}`,
},
{
method: "DELETE",
path: "/bar/qq",
expect: `{"foo": "jet", "bar": {"aa": "bb"}, "list": ["a", "b", "c"]}`,
shouldErr: true,
},
{
method: "POST",
path: "/list",
payload: `"e"`,
expect: `{"foo": "jet", "bar": {"aa": "bb"}, "list": ["a", "b", "c", "e"]}`,
},
{
method: "PUT",
path: "/list/3",
payload: `"d"`,
expect: `{"foo": "jet", "bar": {"aa": "bb"}, "list": ["a", "b", "c", "d", "e"]}`,
},
{
method: "DELETE",
path: "/list/3",
expect: `{"foo": "jet", "bar": {"aa": "bb"}, "list": ["a", "b", "c", "e"]}`,
},
{
method: "PATCH",
path: "/list/3",
payload: `"d"`,
expect: `{"foo": "jet", "bar": {"aa": "bb"}, "list": ["a", "b", "c", "d"]}`,
},
{
method: "POST",
path: "/list/...",
payload: `["e", "f", "g"]`,
expect: `{"foo": "jet", "bar": {"aa": "bb"}, "list": ["a", "b", "c", "d", "e", "f", "g"]}`,
},
} {
err := unsyncedConfigAccess(tc.method, rawConfigKey+tc.path, []byte(tc.payload), nil)
if tc.shouldErr && err == nil {
t.Fatalf("Test %d: Expected error return value, but got: %v", i, err)
}
if !tc.shouldErr && err != nil {
t.Fatalf("Test %d: Should not have had error return value, but got: %v", i, err)
}
// decode the expected config so we can do a convenient DeepEqual
var expectedDecoded any
err = json.Unmarshal([]byte(tc.expect), &expectedDecoded)
if err != nil {
t.Fatalf("Test %d: Unmarshaling expected config: %v", i, err)
}
// make sure the resulting config is as we expect it
if !reflect.DeepEqual(rawCfg[rawConfigKey], expectedDecoded) {
t.Fatalf("Test %d:\nExpected:\n\t%#v\nActual:\n\t%#v",
i, expectedDecoded, rawCfg[rawConfigKey])
}
}
}
// TestLoadConcurrent exercises Load under concurrent conditions
// and is most useful under test with `-race` enabled.
func TestLoadConcurrent(t *testing.T) {
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Go(func() {
_ = Load(testCfg, true)
})
}
wg.Wait()
}
type fooModule struct {
IntField int
StrField string
}
func (fooModule) CaddyModule() ModuleInfo {
return ModuleInfo{
ID: "foo",
New: func() Module { return new(fooModule) },
}
}
func (fooModule) Start() error { return nil }
func (fooModule) Stop() error { return nil }
func TestETags(t *testing.T) {
RegisterModule(fooModule{})
if err := Load([]byte(`{"admin": {"listen": "localhost:2999"}, "apps": {"foo": {"strField": "abc", "intField": 0}}}`), true); err != nil {
t.Fatalf("loading: %s", err)
}
const key = "/" + rawConfigKey + "/apps/foo"
// try update the config with the wrong etag
err := changeConfig(http.MethodPost, key, []byte(`{"strField": "abc", "intField": 1}}`), fmt.Sprintf(`"/%s not_an_etag"`, rawConfigKey), false)
if apiErr, ok := err.(APIError); !ok || apiErr.HTTPStatus != http.StatusPreconditionFailed {
t.Fatalf("expected precondition failed; got %v", err)
}
// get the etag
hash := etagHasher()
if err := readConfig(key, hash); err != nil {
t.Fatalf("reading: %s", err)
}
// do the same update with the correct key
err = changeConfig(http.MethodPost, key, []byte(`{"strField": "abc", "intField": 1}`), makeEtag(key, hash), false)
if err != nil {
t.Fatalf("expected update to work; got %v", err)
}
// now try another update. The hash should no longer match and we should get precondition failed
err = changeConfig(http.MethodPost, key, []byte(`{"strField": "abc", "intField": 2}`), makeEtag(key, hash), false)
if apiErr, ok := err.(APIError); !ok || apiErr.HTTPStatus != http.StatusPreconditionFailed {
t.Fatalf("expected precondition failed; got %v", err)
}
}
func BenchmarkLoad(b *testing.B) {
for b.Loop() {
Load(testCfg, true)
}
}
func TestAdminHandlerErrorHandling(t *testing.T) {
initAdminMetrics()
handler := adminHandler{
mux: http.NewServeMux(),
}
handler.mux.Handle("/error", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err := fmt.Errorf("test error")
handler.handleError(w, r, err)
}))
req := httptest.NewRequest(http.MethodGet, "/error", nil)
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
if rr.Code == http.StatusOK {
t.Error("expected error response, got success")
}
var apiErr APIError
if err := json.NewDecoder(rr.Body).Decode(&apiErr); err != nil {
t.Fatalf("decoding response: %v", err)
}
if apiErr.Message != "test error" {
t.Errorf("expected error message 'test error', got '%s'", apiErr.Message)
}
}
func initAdminMetrics() {
if adminMetrics.requestErrors != nil {
prometheus.Unregister(adminMetrics.requestErrors)
}
if adminMetrics.requestCount != nil {
prometheus.Unregister(adminMetrics.requestCount)
}
adminMetrics.requestErrors = prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: "caddy",
Subsystem: "admin_http",
Name: "request_errors_total",
Help: "Number of errors that occurred handling admin endpoint requests",
}, []string{"handler", "path", "method"})
adminMetrics.requestCount = prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: "caddy",
Subsystem: "admin_http",
Name: "requests_total",
Help: "Count of requests to the admin endpoint",
}, []string{"handler", "path", "code", "method"}) // Added code and method labels
prometheus.MustRegister(adminMetrics.requestErrors)
prometheus.MustRegister(adminMetrics.requestCount)
}
func TestAdminHandlerBuiltinRouteErrors(t *testing.T) {
initAdminMetrics()
cfg := &Config{
Admin: &AdminConfig{
Listen: "localhost:2019",
},
}
// Build the admin handler directly (no listener active)
addr, err := ParseNetworkAddress("localhost:2019")
if err != nil {
t.Fatalf("Failed to parse address: %v", err)
}
handler := cfg.Admin.newAdminHandler(addr, false, Context{})
tests := []struct {
name string
path string
method string
expectedStatus int
}{
{
name: "stop endpoint wrong method",
path: "/stop",
method: http.MethodGet,
expectedStatus: http.StatusMethodNotAllowed,
},
{
name: "config endpoint wrong content-type",
path: "/config/",
method: http.MethodPost,
expectedStatus: http.StatusBadRequest,
},
{
name: "config ID missing ID",
path: "/id/",
method: http.MethodGet,
expectedStatus: http.StatusBadRequest,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
req := httptest.NewRequest(test.method, fmt.Sprintf("http://localhost:2019%s", test.path), nil)
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
if rr.Code != test.expectedStatus {
t.Errorf("expected status %d but got %d", test.expectedStatus, rr.Code)
}
metricValue := testGetMetricValue(map[string]string{
"path": test.path,
"handler": "admin",
"method": test.method,
})
if metricValue != 1 {
t.Errorf("expected error metric to be incremented once, got %v", metricValue)
}
})
}
}
func testGetMetricValue(labels map[string]string) float64 {
promLabels := prometheus.Labels{}
maps.Copy(promLabels, labels)
metric, err := adminMetrics.requestErrors.GetMetricWith(promLabels)
if err != nil {
return 0
}
pb := &dto.Metric{}
metric.Write(pb)
return pb.GetCounter().GetValue()
}
type mockRouter struct {
routes []AdminRoute
}
func (m mockRouter) Routes() []AdminRoute {
return m.routes
}
type mockModule struct {
mockRouter
}
func (m *mockModule) CaddyModule() ModuleInfo {
return ModuleInfo{
ID: "admin.api.mock",
New: func() Module {
mm := &mockModule{
mockRouter: mockRouter{
routes: m.routes,
},
}
return mm
},
}
}
func TestNewAdminHandlerRouterRegistration(t *testing.T) {
originalModules := make(map[string]ModuleInfo)
maps.Copy(originalModules, modules)
defer func() {
modules = originalModules
}()
mockRoute := AdminRoute{
Pattern: "/mock",
Handler: AdminHandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
w.WriteHeader(http.StatusOK)
return nil
}),
}
mock := &mockModule{
mockRouter: mockRouter{
routes: []AdminRoute{mockRoute},
},
}
RegisterModule(mock)
addr, err := ParseNetworkAddress("localhost:2019")
if err != nil {
t.Fatalf("Failed to parse address: %v", err)
}
admin := &AdminConfig{
EnforceOrigin: false,
}
handler := admin.newAdminHandler(addr, false, Context{})
req := httptest.NewRequest("GET", "/mock", nil)
req.Host = "localhost:2019"
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
if rr.Code != http.StatusOK {
t.Errorf("Expected status code %d but got %d", http.StatusOK, rr.Code)
t.Logf("Response body: %s", rr.Body.String())
}
if len(admin.routers) != 1 {
t.Errorf("Expected 1 router to be stored, got %d", len(admin.routers))
}
}
type mockProvisionableRouter struct {
mockRouter
provisionErr error
provisioned bool
}
func (m *mockProvisionableRouter) Provision(Context) error {
m.provisioned = true
return m.provisionErr
}
type mockProvisionableModule struct {
*mockProvisionableRouter
}
func (m *mockProvisionableModule) CaddyModule() ModuleInfo {
return ModuleInfo{
ID: "admin.api.mock_provision",
New: func() Module {
mm := &mockProvisionableModule{
mockProvisionableRouter: &mockProvisionableRouter{
mockRouter: m.mockRouter,
provisionErr: m.provisionErr,
},
}
return mm
},
}
}
func TestAdminRouterProvisioning(t *testing.T) {
tests := []struct {
name string
provisionErr error
wantErr bool
routersAfter int // expected number of routers after provisioning
}{
{
name: "successful provisioning",
provisionErr: nil,
wantErr: false,
routersAfter: 0,
},
{
name: "provisioning error",
provisionErr: fmt.Errorf("provision failed"),
wantErr: true,
routersAfter: 1,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
originalModules := make(map[string]ModuleInfo)
maps.Copy(originalModules, modules)
defer func() {
modules = originalModules
}()
mockRoute := AdminRoute{
Pattern: "/mock",
Handler: AdminHandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
return nil
}),
}
// Create provisionable module
mock := &mockProvisionableModule{
mockProvisionableRouter: &mockProvisionableRouter{
mockRouter: mockRouter{
routes: []AdminRoute{mockRoute},
},
provisionErr: test.provisionErr,
},
}
RegisterModule(mock)
admin := &AdminConfig{}
addr, err := ParseNetworkAddress("localhost:2019")
if err != nil {
t.Fatalf("Failed to parse address: %v", err)
}
_ = admin.newAdminHandler(addr, false, Context{})
err = admin.provisionAdminRouters(Context{})
if test.wantErr {
if err == nil {
t.Error("Expected error but got nil")
}
} else {
if err != nil {
t.Errorf("Expected no error but got: %v", err)
}
}
if len(admin.routers) != test.routersAfter {
t.Errorf("Expected %d routers after provisioning, got %d", test.routersAfter, len(admin.routers))
}
})
}
}
func TestAllowedOriginsUnixSocket(t *testing.T) {
// see comment in allowedOrigins() as to why we do not fill out allowed origins for UDS
tests := []struct {
name string
addr NetworkAddress
origins []string
expectOrigins []string
}{
{
name: "unix socket with default origins",
addr: NetworkAddress{
Network: "unix",
Host: "/tmp/caddy.sock",
},
origins: nil, // default origins
expectOrigins: []string{},
},
{
name: "unix socket with custom origins",
addr: NetworkAddress{
Network: "unix",
Host: "/tmp/caddy.sock",
},
origins: []string{"example.com"},
expectOrigins: []string{
"example.com",
},
},
{
name: "tcp socket on localhost gets all loopback addresses",
addr: NetworkAddress{
Network: "tcp",
Host: "localhost",
StartPort: 2019,
EndPort: 2019,
},
origins: nil,
expectOrigins: []string{
"localhost:2019",
"[::1]:2019",
"127.0.0.1:2019",
},
},
}
for i, test := range tests {
t.Run(test.name, func(t *testing.T) {
admin := AdminConfig{
Origins: test.origins,
}
got := admin.allowedOrigins(test.addr)
var gotOrigins []string
for _, u := range got {
gotOrigins = append(gotOrigins, u.Host)
}
if len(gotOrigins) != len(test.expectOrigins) {
t.Errorf("%d: Expected %d origins but got %d", i, len(test.expectOrigins), len(gotOrigins))
return
}
expectMap := make(map[string]struct{})
for _, origin := range test.expectOrigins {
expectMap[origin] = struct{}{}
}
gotMap := make(map[string]struct{})
for _, origin := range gotOrigins {
gotMap[origin] = struct{}{}
}
if !reflect.DeepEqual(expectMap, gotMap) {
t.Errorf("%d: Origins mismatch.\nExpected: %v\nGot: %v", i, test.expectOrigins, gotOrigins)
}
})
}
}
func TestReplaceRemoteAdminServer(t *testing.T) {
const testCert = `MIIDCTCCAfGgAwIBAgIUXsqJ1mY8pKlHQtI3HJ23x2eZPqwwDQYJKoZIhvcNAQEL
BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTIzMDEwMTAwMDAwMFoXDTI0MDEw
MTAwMDAwMFowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEA4O4S6BSoYcoxvRqI+h7yPOjF6KjntjzVVm9M+uHK4lzX
F1L3pSxJ2nDD4wZEV3FJ5yFOHVFqkG2vXG3BIczOlYG7UeNmKbQnKc5kZj3HGUrS
VGEktA4OJbeZhhWP15gcXN5eDM2eH3g9BFXVX6AURxLiUXzhNBUEZuj/OEyH9yEF
/qPCE+EjzVvWxvBXwgz/io4r4yok/Vq/bxJ6FlV6R7DX5oJSXyO0VEHZPi9DIyNU
kK3F/r4U1sWiJGWOs8i3YQWZ2ejh1C0aLFZpPcCGGgMNpoF31gyYP6ZuPDUyCXsE
g36UUw1JHNtIXYcLhnXuqj4A8TybTDpgXLqvwA9DBQIDAQABo1MwUTAdBgNVHQ4E
FgQUc13z30pFC63rr/HGKOE7E82vjXwwHwYDVR0jBBgwFoAUc13z30pFC63rr/HG
KOE7E82vjXwwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAHO3j
oeiUXXJ7xD4P8Wj5t9d+E8lE1Xv1Dk3Z+EdG5+dan+RcToE42JJp9zB7FIh5Qz8g
W77LAjqh5oyqz3A2VJcyVgfE3uJP1R1mJM7JfGHf84QH4TZF2Q1RZY4SZs0VQ6+q
5wSlIZ4NXDy4Q4XkIJBGS61wT8IzYFXYBpx4PCP1Qj0PIE4sevEGwjsBIgxK307o
BxF8AWe6N6e4YZmQLGjQ+SeH0iwZb6vpkHyAY8Kj2hvK+cq2P7vU3VGi0t3r1F8L
IvrXHCvO2BMNJ/1UK1M4YNX8LYJqQhg9hEsIROe1OE/m3VhxIYMJI+qZXk9yHfgJ
vq+SH04xKhtFudVBAQ==`
tests := []struct {
name string
cfg *Config
wantErr bool
}{
{
name: "nil config",
cfg: nil,
wantErr: false,
},
{
name: "nil admin config",
cfg: &Config{
Admin: nil,
},
wantErr: false,
},
{
name: "nil remote config",
cfg: &Config{
Admin: &AdminConfig{},
},
wantErr: false,
},
{
name: "invalid listen address",
cfg: &Config{
Admin: &AdminConfig{
Remote: &RemoteAdmin{
Listen: "invalid:address",
},
},
},
wantErr: true,
},
{
name: "valid config",
cfg: &Config{
Admin: &AdminConfig{
Identity: &IdentityConfig{},
Remote: &RemoteAdmin{
Listen: "localhost:2021",
AccessControl: []*AdminAccess{
{
PublicKeys: []string{testCert},
Permissions: []AdminPermissions{{Methods: []string{"GET"}, Paths: []string{"/test"}}},
},
},
},
},
},
wantErr: false,
},
{
name: "invalid certificate",
cfg: &Config{
Admin: &AdminConfig{
Identity: &IdentityConfig{},
Remote: &RemoteAdmin{
Listen: "localhost:2021",
AccessControl: []*AdminAccess{
{
PublicKeys: []string{"invalid-cert-data"},
Permissions: []AdminPermissions{{Methods: []string{"GET"}, Paths: []string{"/test"}}},
},
},
},
},
},
wantErr: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ctx := Context{
Context: context.Background(),
cfg: test.cfg,
}
if test.cfg != nil {
test.cfg.storage = &certmagic.FileStorage{Path: t.TempDir()}
}
if test.cfg != nil && test.cfg.Admin != nil && test.cfg.Admin.Identity != nil {
identityCertCache = certmagic.NewCache(certmagic.CacheOptions{
GetConfigForCert: func(certmagic.Certificate) (*certmagic.Config, error) {
return &certmagic.Config{}, nil
},
})
}
err := replaceRemoteAdminServer(ctx, test.cfg)
if test.wantErr {
if err == nil {
t.Error("Expected error but got nil")
}
} else {
if err != nil {
t.Errorf("Expected no error but got: %v", err)
}
}
// Clean up
if remoteAdminServer != nil {
_ = stopAdminServer(remoteAdminServer)
}
})
}
}
type mockIssuer struct {
configSet *certmagic.Config
}
func (m *mockIssuer) Issue(ctx context.Context, csr *x509.CertificateRequest) (*certmagic.IssuedCertificate, error) {
return &certmagic.IssuedCertificate{
Certificate: []byte(csr.Raw),
}, nil
}
func (m *mockIssuer) SetConfig(cfg *certmagic.Config) {
m.configSet = cfg
}
func (m *mockIssuer) IssuerKey() string {
return "mock"
}
type mockIssuerModule struct {
*mockIssuer
}
func (m *mockIssuerModule) CaddyModule() ModuleInfo {
return ModuleInfo{
ID: "tls.issuance.acme",
New: func() Module {
return &mockIssuerModule{mockIssuer: new(mockIssuer)}
},
}
}
func TestManageIdentity(t *testing.T) {
originalModules := make(map[string]ModuleInfo)
maps.Copy(originalModules, modules)
defer func() {
modules = originalModules
}()
RegisterModule(&mockIssuerModule{})
certPEM := []byte(`-----BEGIN CERTIFICATE-----
MIIDujCCAqKgAwIBAgIIE31FZVaPXTUwDQYJKoZIhvcNAQEFBQAwSTELMAkGA1UE
BhMCVVMxEzARBgNVBAoTCkdvb2dsZSBJbmMxJTAjBgNVBAMTHEdvb2dsZSBJbnRl
cm5ldCBBdXRob3JpdHkgRzIwHhcNMTQwMTI5MTMyNzQzWhcNMTQwNTI5MDAwMDAw
WjBpMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwN
TW91bnRhaW4gVmlldzETMBEGA1UECgwKR29vZ2xlIEluYzEYMBYGA1UEAwwPbWFp
bC5nb29nbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE3lcub2pUwkjC
5GJQA2ZZfJJi6d1QHhEmkX9VxKYGp6gagZuRqJWy9TXP6++1ZzQQxqZLD0TkuxZ9
8i9Nz00000CCBjCCAQQwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMGgG
CCsGAQUFBwEBBFwwWjArBggrBgEFBQcwAoYfaHR0cDovL3BraS5nb29nbGUuY29t
L0dJQUcyLmNydDArBggrBgEFBQcwAYYfaHR0cDovL2NsaWVudHMxLmdvb2dsZS5j
b20vb2NzcDAdBgNVHQ4EFgQUiJxtimAuTfwb+aUtBn5UYKreKvMwDAYDVR0TAQH/
BAIwADAfBgNVHSMEGDAWgBRK3QYWG7z2aLV29YG2u2IaulqBLzAXBgNVHREEEDAO
ggxtYWlsLmdvb2dsZTANBgkqhkiG9w0BAQUFAAOCAQEAMP6IWgNGZE8wP9TjFjSZ
3mmW3A1eIr0CuPwNZ2LJ5ZD1i70ojzcj4I9IdP5yPg9CAEV4hNASbM1LzfC7GmJE
tPzW5tRmpKVWZGRgTgZI8Hp/xZXMwLh9ZmXV4kESFAGj5G5FNvJyUV7R5Eh+7OZX
7G4jJ4ZGJh+5jzN9HdJJHQHGYNIYOzC7+HH9UMwCjX9vhQ4RjwFZJThS2Yb+y7pb
9yxTJZoXC6J0H5JpnZb7kZEJ+Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
-----END CERTIFICATE-----`)
keyPEM := []byte(`-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDRS0LmTwUT0iwP
...
-----END PRIVATE KEY-----`)
tmpDir, err := os.MkdirTemp("", "TestManageIdentity-")
if err != nil {
t.Fatal(err)
}
testStorage := certmagic.FileStorage{Path: tmpDir}
// Clean up the temp dir after the test finishes. Ensure any background
// certificate maintenance is stopped first to avoid RemoveAll races.
t.Cleanup(func() {
if identityCertCache != nil {
identityCertCache.Stop()
identityCertCache = nil
}
// Give goroutines a moment to exit and release file handles.
time.Sleep(50 * time.Millisecond)
_ = os.RemoveAll(tmpDir)
})
err = testStorage.Store(context.Background(), "localhost/localhost.crt", certPEM)
if err != nil {
t.Fatal(err)
}
err = testStorage.Store(context.Background(), "localhost/localhost.key", keyPEM)
if err != nil {
t.Fatal(err)
}
tests := []struct {
name string
cfg *Config
wantErr bool
checkState func(*testing.T, *Config)
}{
{
name: "nil config",
cfg: nil,
},
{
name: "nil admin config",
cfg: &Config{
Admin: nil,
},
},
{
name: "nil identity config",
cfg: &Config{
Admin: &AdminConfig{},
},
},
{
name: "default issuer when none specified",
cfg: &Config{
Admin: &AdminConfig{
Identity: &IdentityConfig{
Identifiers: []string{"localhost"},
},
},
storage: &testStorage,
},
checkState: func(t *testing.T, cfg *Config) {
if len(cfg.Admin.Identity.issuers) == 0 {
t.Error("Expected at least 1 issuer to be configured")
return
}
if _, ok := cfg.Admin.Identity.issuers[0].(*mockIssuerModule); !ok {
t.Error("Expected mock issuer to be configured")
}
},
},
{
name: "custom issuer",
cfg: &Config{
Admin: &AdminConfig{
Identity: &IdentityConfig{
Identifiers: []string{"localhost"},
IssuersRaw: []json.RawMessage{
json.RawMessage(`{"module": "acme"}`),
},
},
},
storage: &testStorage,
},
checkState: func(t *testing.T, cfg *Config) {
if len(cfg.Admin.Identity.issuers) != 1 {
t.Fatalf("Expected 1 issuer, got %d", len(cfg.Admin.Identity.issuers))
}
mockIss, ok := cfg.Admin.Identity.issuers[0].(*mockIssuerModule)
if !ok {
t.Fatal("Expected mock issuer")
}
if mockIss.configSet == nil {
t.Error("Issuer config was not set")
}
},
},
{
name: "invalid issuer module",
cfg: &Config{
Admin: &AdminConfig{
Identity: &IdentityConfig{
Identifiers: []string{"localhost"},
IssuersRaw: []json.RawMessage{
json.RawMessage(`{"module": "doesnt_exist"}`),
},
},
},
},
wantErr: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if identityCertCache != nil {
// Reset the cert cache before each test
identityCertCache.Stop()
identityCertCache = nil
}
// Ensure any cache started by manageIdentity is stopped at the end
defer func() {
if identityCertCache != nil {
identityCertCache.Stop()
identityCertCache = nil
gitextract_8o2w0qyu/ ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── CONTRIBUTING.md │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── ISSUE.yml │ │ └── config.yml │ ├── SECURITY.md │ ├── dependabot.yml │ ├── pull_request_template.md │ └── workflows/ │ ├── ai.yml │ ├── auto-release-pr.yml │ ├── ci.yml │ ├── cross-build.yml │ ├── lint.yml │ ├── release-proposal.yml │ ├── release.yml │ ├── release_published.yml │ └── scorecard.yml ├── .gitignore ├── .golangci.yml ├── .goreleaser.yml ├── .pre-commit-config.yaml ├── AUTHORS ├── LICENSE ├── README.md ├── admin.go ├── admin_test.go ├── caddy.go ├── caddy_test.go ├── caddyconfig/ │ ├── caddyfile/ │ │ ├── adapter.go │ │ ├── dispenser.go │ │ ├── dispenser_test.go │ │ ├── formatter.go │ │ ├── formatter_fuzz.go │ │ ├── formatter_test.go │ │ ├── importargs.go │ │ ├── importgraph.go │ │ ├── lexer.go │ │ ├── lexer_fuzz.go │ │ ├── lexer_test.go │ │ ├── parse.go │ │ ├── parse_test.go │ │ └── testdata/ │ │ ├── empty.txt │ │ ├── glob/ │ │ │ ├── .dotfile.txt │ │ │ └── import_test1.txt │ │ ├── import_args0.txt │ │ ├── import_args1.txt │ │ ├── import_glob0.txt │ │ ├── import_glob1.txt │ │ ├── import_glob2.txt │ │ ├── import_recursive0.txt │ │ ├── import_recursive1.txt │ │ ├── import_recursive2.txt │ │ ├── import_recursive3.txt │ │ ├── import_test1.txt │ │ ├── import_test2.txt │ │ └── only_white_space.txt │ ├── configadapters.go │ ├── httpcaddyfile/ │ │ ├── addresses.go │ │ ├── addresses_fuzz.go │ │ ├── addresses_test.go │ │ ├── builtins.go │ │ ├── builtins_test.go │ │ ├── directives.go │ │ ├── directives_test.go │ │ ├── httptype.go │ │ ├── httptype_test.go │ │ ├── options.go │ │ ├── options_test.go │ │ ├── pkiapp.go │ │ ├── pkiapp_test.go │ │ ├── serveroptions.go │ │ ├── shorthands.go │ │ ├── testdata/ │ │ │ ├── import_variadic.txt │ │ │ ├── import_variadic_snippet.txt │ │ │ └── import_variadic_with_import.txt │ │ ├── tlsapp.go │ │ └── tlsapp_test.go │ ├── httploader.go │ └── load.go ├── caddytest/ │ ├── a.caddy.localhost.crt │ ├── a.caddy.localhost.key │ ├── caddy.ca.cer │ ├── caddy.localhost.crt │ ├── caddy.localhost.key │ ├── caddytest.go │ ├── caddytest_test.go │ ├── integration/ │ │ ├── acme_test.go │ │ ├── acmeserver_test.go │ │ ├── autohttps_test.go │ │ ├── caddyfile_adapt/ │ │ │ ├── acme_dns_configured.caddyfiletest │ │ │ ├── acme_dns_naked_use_dns_defaults.caddyfiletest │ │ │ ├── acme_dns_naked_without_dns.caddyfiletest │ │ │ ├── acme_server_custom_challenges.caddyfiletest │ │ │ ├── acme_server_default_challenges.caddyfiletest │ │ │ ├── acme_server_lifetime.caddyfiletest │ │ │ ├── acme_server_multi_custom_challenges.caddyfiletest │ │ │ ├── acme_server_policy-allow.caddyfiletest │ │ │ ├── acme_server_policy-both.caddyfiletest │ │ │ ├── acme_server_policy-deny.caddyfiletest │ │ │ ├── acme_server_sign_with_root.caddyfiletest │ │ │ ├── ambiguous_site_definition.caddyfiletest │ │ │ ├── ambiguous_site_definition_duplicate_key.caddyfiletest │ │ │ ├── auto_https_disable_redirects.caddyfiletest │ │ │ ├── auto_https_ignore_loaded_certs.caddyfiletest │ │ │ ├── auto_https_off.caddyfiletest │ │ │ ├── bind_fd_fdgram_h123.caddyfiletest │ │ │ ├── bind_ipv6.caddyfiletest │ │ │ ├── directive_as_site_address.caddyfiletest │ │ │ ├── duplicate_listener_address_global.caddyfiletest │ │ │ ├── enable_tls_for_catch_all_site.caddyfiletest │ │ │ ├── encode_options.caddyfiletest │ │ │ ├── error_example.caddyfiletest │ │ │ ├── error_multi_site_blocks.caddyfiletest │ │ │ ├── error_range_codes.caddyfiletest │ │ │ ├── error_range_simple_codes.caddyfiletest │ │ │ ├── error_simple_codes.caddyfiletest │ │ │ ├── error_sort.caddyfiletest │ │ │ ├── error_subhandlers.caddyfiletest │ │ │ ├── expression_quotes.caddyfiletest │ │ │ ├── file_server_disable_canonical_uris.caddyfiletest │ │ │ ├── file_server_etag_file_extensions.caddyfiletest │ │ │ ├── file_server_file_limit.caddyfiletest │ │ │ ├── file_server_pass_thru.caddyfiletest │ │ │ ├── file_server_precompressed.caddyfiletest │ │ │ ├── file_server_sort.caddyfiletest │ │ │ ├── file_server_status.caddyfiletest │ │ │ ├── forward_auth_authelia.caddyfiletest │ │ │ ├── forward_auth_copy_headers_strip.caddyfiletest │ │ │ ├── forward_auth_rename_headers.caddyfiletest │ │ │ ├── global_options.caddyfiletest │ │ │ ├── global_options_acme.caddyfiletest │ │ │ ├── global_options_admin.caddyfiletest │ │ │ ├── global_options_admin_with_persist_config_off.caddyfiletest │ │ │ ├── global_options_debug_with_access_log.caddyfiletest │ │ │ ├── global_options_default_bind.caddyfiletest │ │ │ ├── global_options_log_and_site.caddyfiletest │ │ │ ├── global_options_log_basic.caddyfiletest │ │ │ ├── global_options_log_custom.caddyfiletest │ │ │ ├── global_options_log_multi.caddyfiletest │ │ │ ├── global_options_log_sampling.caddyfiletest │ │ │ ├── global_options_persist_config.caddyfiletest │ │ │ ├── global_options_preferred_chains.caddyfiletest │ │ │ ├── global_options_resolvers.caddyfiletest │ │ │ ├── global_options_resolvers_http_challenge.caddyfiletest │ │ │ ├── global_options_resolvers_local_dns_inherit.caddyfiletest │ │ │ ├── global_options_resolvers_local_override.caddyfiletest │ │ │ ├── global_options_resolvers_mixed.caddyfiletest │ │ │ ├── global_options_skip_install_trust.caddyfiletest │ │ │ ├── global_server_options_multi.caddyfiletest │ │ │ ├── global_server_options_single.caddyfiletest │ │ │ ├── handle_nested_in_route.caddyfiletest │ │ │ ├── handle_path.caddyfiletest │ │ │ ├── handle_path_sorting.caddyfiletest │ │ │ ├── header.caddyfiletest │ │ │ ├── header_placeholder_search.caddyfiletest │ │ │ ├── heredoc.caddyfiletest │ │ │ ├── heredoc_extra_indentation.caddyfiletest │ │ │ ├── heredoc_incomplete.caddyfiletest │ │ │ ├── heredoc_invalid_marker.caddyfiletest │ │ │ ├── heredoc_mismatched_whitespace.caddyfiletest │ │ │ ├── heredoc_missing_marker.caddyfiletest │ │ │ ├── heredoc_too_many_angle_brackets.caddyfiletest │ │ │ ├── http_only_hostnames.caddyfiletest │ │ │ ├── http_only_on_any_address.caddyfiletest │ │ │ ├── http_only_on_domain.caddyfiletest │ │ │ ├── http_only_on_hostless_block.caddyfiletest │ │ │ ├── http_only_on_localhost.caddyfiletest │ │ │ ├── http_only_on_non_standard_port.caddyfiletest │ │ │ ├── http_valid_directive_like_site_address.caddyfiletest │ │ │ ├── https_on_domain.caddyfiletest │ │ │ ├── import_args_file.caddyfiletest │ │ │ ├── import_args_snippet.caddyfiletest │ │ │ ├── import_args_snippet_env_placeholder.caddyfiletest │ │ │ ├── import_block_anonymous.caddyfiletest │ │ │ ├── import_block_snippet.caddyfiletest │ │ │ ├── import_block_snippet_args.caddyfiletest │ │ │ ├── import_block_snippet_non_replaced_block.caddyfiletest │ │ │ ├── import_block_snippet_non_replaced_block_from_separate_file.caddyfiletest │ │ │ ├── import_block_snippet_non_replaced_key_block.caddyfiletest │ │ │ ├── import_block_with_site_block.caddyfiletest │ │ │ ├── import_blocks_snippet.caddyfiletest │ │ │ ├── import_blocks_snippet_nested.caddyfiletest │ │ │ ├── import_cycle.caddyfiletest │ │ │ ├── intercept_response.caddyfiletest │ │ │ ├── invoke_named_routes.caddyfiletest │ │ │ ├── invoke_undefined_named_route.caddyfiletest │ │ │ ├── log_add.caddyfiletest │ │ │ ├── log_append_encoder.caddyfiletest │ │ │ ├── log_except_catchall_blocks.caddyfiletest │ │ │ ├── log_filter_no_wrap.caddyfiletest │ │ │ ├── log_filter_with_header.txt │ │ │ ├── log_filters.caddyfiletest │ │ │ ├── log_multi_logger_name.caddyfiletest │ │ │ ├── log_multiple_regexp_filters.caddyfiletest │ │ │ ├── log_override_hostname.caddyfiletest │ │ │ ├── log_override_name_multiaccess.caddyfiletest │ │ │ ├── log_override_name_multiaccess_debug.caddyfiletest │ │ │ ├── log_roll_days.caddyfiletest │ │ │ ├── log_sampling.caddyfiletest │ │ │ ├── log_skip_hosts.caddyfiletest │ │ │ ├── map_and_vars_with_raw_types.caddyfiletest │ │ │ ├── matcher_outside_site_block.caddyfiletest │ │ │ ├── matcher_syntax.caddyfiletest │ │ │ ├── matchers_in_route.caddyfiletest │ │ │ ├── method_directive.caddyfiletest │ │ │ ├── metrics_disable_om.caddyfiletest │ │ │ ├── metrics_merge_options.caddyfiletest │ │ │ ├── metrics_perhost.caddyfiletest │ │ │ ├── metrics_syntax.caddyfiletest │ │ │ ├── not_block_merging.caddyfiletest │ │ │ ├── php_fastcgi_expanded_form.caddyfiletest │ │ │ ├── php_fastcgi_handle_response.caddyfiletest │ │ │ ├── php_fastcgi_index_off.caddyfiletest │ │ │ ├── php_fastcgi_matcher.caddyfiletest │ │ │ ├── php_fastcgi_subdirectives.caddyfiletest │ │ │ ├── php_fastcgi_try_files_override.caddyfiletest │ │ │ ├── php_fastcgi_try_files_override_no_dir_index.caddyfiletest │ │ │ ├── portless_upstream.caddyfiletest │ │ │ ├── push.caddyfiletest │ │ │ ├── renewal_window_ratio_global.caddyfiletest │ │ │ ├── renewal_window_ratio_tls_directive.caddyfiletest │ │ │ ├── replaceable_upstream.caddyfiletest │ │ │ ├── replaceable_upstream_partial_port.caddyfiletest │ │ │ ├── replaceable_upstream_port.caddyfiletest │ │ │ ├── request_body.caddyfiletest │ │ │ ├── request_header.caddyfiletest │ │ │ ├── reverse_proxy_buffers.caddyfiletest │ │ │ ├── reverse_proxy_dynamic_upstreams.caddyfiletest │ │ │ ├── reverse_proxy_dynamic_upstreams_grace_period.caddyfiletest │ │ │ ├── reverse_proxy_empty_non_http_transport.caddyfiletest │ │ │ ├── reverse_proxy_h2c_shorthand.caddyfiletest │ │ │ ├── reverse_proxy_handle_response.caddyfiletest │ │ │ ├── reverse_proxy_health_headers.caddyfiletest │ │ │ ├── reverse_proxy_health_method.caddyfiletest │ │ │ ├── reverse_proxy_health_path_query.caddyfiletest │ │ │ ├── reverse_proxy_health_reqbody.caddyfiletest │ │ │ ├── reverse_proxy_http_transport_forward_proxy_url.txt │ │ │ ├── reverse_proxy_http_transport_none_proxy.txt │ │ │ ├── reverse_proxy_http_transport_tls_file_cert.txt │ │ │ ├── reverse_proxy_http_transport_tls_inline_cert.txt │ │ │ ├── reverse_proxy_http_transport_url_proxy.txt │ │ │ ├── reverse_proxy_load_balance.caddyfiletest │ │ │ ├── reverse_proxy_load_balance_wrr.caddyfiletest │ │ │ ├── reverse_proxy_localaddr.caddyfiletest │ │ │ ├── reverse_proxy_options.caddyfiletest │ │ │ ├── reverse_proxy_port_range.caddyfiletest │ │ │ ├── reverse_proxy_trusted_proxies.caddyfiletest │ │ │ ├── reverse_proxy_trusted_proxies_unix.caddyfiletest │ │ │ ├── reverse_proxy_upstream_placeholder.caddyfiletest │ │ │ ├── rewrite_directive_permutations.caddyfiletest │ │ │ ├── root_directive_permutations.caddyfiletest │ │ │ ├── server_names.caddyfiletest │ │ │ ├── shorthand_parameterized_placeholders.caddyfiletest │ │ │ ├── site_address_invalid_port.caddyfiletest │ │ │ ├── site_address_negative_port.caddyfiletest │ │ │ ├── site_address_unsupported_scheme.caddyfiletest │ │ │ ├── site_address_wss_invalid_port.caddyfiletest │ │ │ ├── site_address_wss_scheme.caddyfiletest │ │ │ ├── site_block_sorting.caddyfiletest │ │ │ ├── sort_directives_with_any_matcher_first.caddyfiletest │ │ │ ├── sort_directives_within_handle.caddyfiletest │ │ │ ├── sort_vars_in_reverse.caddyfiletest │ │ │ ├── tls_acme_dns_override_global_dns.caddyfiletest │ │ │ ├── tls_acme_preferred_chains.caddyfiletest │ │ │ ├── tls_automation_policies_1.caddyfiletest │ │ │ ├── tls_automation_policies_10.caddyfiletest │ │ │ ├── tls_automation_policies_11.caddyfiletest │ │ │ ├── tls_automation_policies_2.caddyfiletest │ │ │ ├── tls_automation_policies_3.caddyfiletest │ │ │ ├── tls_automation_policies_4.caddyfiletest │ │ │ ├── tls_automation_policies_5.caddyfiletest │ │ │ ├── tls_automation_policies_6.caddyfiletest │ │ │ ├── tls_automation_policies_7.caddyfiletest │ │ │ ├── tls_automation_policies_8.caddyfiletest │ │ │ ├── tls_automation_policies_9.caddyfiletest │ │ │ ├── tls_automation_policies_global_email_localhost.caddyfiletest │ │ │ ├── tls_automation_wildcard_force_automate.caddyfiletest │ │ │ ├── tls_automation_wildcard_shadowing.caddyfiletest │ │ │ ├── tls_client_auth_cert_file-legacy-with-verifier.caddyfiletest │ │ │ ├── tls_client_auth_cert_file-legacy.caddyfiletest │ │ │ ├── tls_client_auth_cert_file.caddyfiletest │ │ │ ├── tls_client_auth_inline_cert-legacy.caddyfiletest │ │ │ ├── tls_client_auth_inline_cert.caddyfiletest │ │ │ ├── tls_client_auth_inline_cert_with_leaf_trust.caddyfiletest │ │ │ ├── tls_client_auth_leaf_verifier_file_loader_block.caddyfiletest │ │ │ ├── tls_client_auth_leaf_verifier_file_loader_inline.caddyfiletest │ │ │ ├── tls_client_auth_leaf_verifier_file_loader_multi-in-block.caddyfiletest │ │ │ ├── tls_client_auth_leaf_verifier_folder_loader_block.caddyfiletest │ │ │ ├── tls_client_auth_leaf_verifier_folder_loader_inline.caddyfiletest │ │ │ ├── tls_client_auth_leaf_verifier_folder_loader_multi-in-block.caddyfiletest │ │ │ ├── tls_conn_policy_consolidate.caddyfiletest │ │ │ ├── tls_dns_multiple_options_without_provider.caddyfiletest │ │ │ ├── tls_dns_override_acme_dns.caddyfiletest │ │ │ ├── tls_dns_override_global_dns.caddyfiletest │ │ │ ├── tls_dns_propagation_timeout_without_provider.caddyfiletest │ │ │ ├── tls_dns_propagation_without_provider.caddyfiletest │ │ │ ├── tls_dns_resolvers_with_global_provider.caddyfiletest │ │ │ ├── tls_dns_ttl.caddyfiletest │ │ │ ├── tls_explicit_issuer_dns_ttl.caddyfiletest │ │ │ ├── tls_explicit_issuer_propagation_options.caddyfiletest │ │ │ ├── tls_internal_options.caddyfiletest │ │ │ ├── tls_propagation_options.caddyfiletest │ │ │ ├── tracing.caddyfiletest │ │ │ ├── uri_query_operations.caddyfiletest │ │ │ ├── uri_replace_brace_escape.caddyfiletest │ │ │ └── wildcard_pattern.caddyfiletest │ │ ├── caddyfile_adapt_test.go │ │ ├── caddyfile_test.go │ │ ├── forwardauth_test.go │ │ ├── h2listener_test.go │ │ ├── handler_test.go │ │ ├── intercept_test.go │ │ ├── leafcertloaders_test.go │ │ ├── listener_test.go │ │ ├── map_test.go │ │ ├── mockdns_test.go │ │ ├── pki_test.go │ │ ├── proxyprotocol_test.go │ │ ├── reverseproxy_test.go │ │ ├── sni_test.go │ │ ├── stream_test.go │ │ └── testdata/ │ │ ├── cookie.html │ │ ├── foo.txt │ │ ├── foo_with_multiple_trailing_newlines.txt │ │ ├── foo_with_trailing_newline.txt │ │ ├── import_respond.txt │ │ ├── index.localhost.html │ │ └── issue_7518_unused_block_panic_snippets.conf │ └── leafcert.pem ├── cmd/ │ ├── caddy/ │ │ ├── main.go │ │ └── setcap.sh │ ├── cobra.go │ ├── commandfactory.go │ ├── commandfuncs.go │ ├── commands.go │ ├── commands_test.go │ ├── main.go │ ├── main_test.go │ ├── packagesfuncs.go │ ├── removebinary.go │ ├── removebinary_windows.go │ ├── storagefuncs.go │ └── x509rootsfallback.go ├── context.go ├── context_test.go ├── duration_fuzz.go ├── filepath.go ├── filepath_windows.go ├── filesystem.go ├── go.mod ├── go.sum ├── internal/ │ ├── filesystems/ │ │ ├── map.go │ │ └── os.go │ ├── logbuffer.go │ ├── logs.go │ ├── metrics/ │ │ ├── metrics.go │ │ └── metrics_test.go │ ├── ranges.go │ ├── sockets.go │ └── testmocks/ │ └── dummyverifier.go ├── listen.go ├── listen_unix.go ├── listen_unix_setopt.go ├── listen_unix_setopt_freebsd.go ├── listeners.go ├── listeners_fuzz.go ├── listeners_test.go ├── logging.go ├── logging_test.go ├── metrics.go ├── modules/ │ ├── caddyevents/ │ │ ├── app.go │ │ └── eventsconfig/ │ │ └── caddyfile.go │ ├── caddyfs/ │ │ └── filesystem.go │ ├── caddyhttp/ │ │ ├── app.go │ │ ├── autohttps.go │ │ ├── caddyauth/ │ │ │ ├── argon2id.go │ │ │ ├── basicauth.go │ │ │ ├── bcrypt.go │ │ │ ├── caddyauth.go │ │ │ ├── caddyfile.go │ │ │ └── command.go │ │ ├── caddyhttp.go │ │ ├── caddyhttp_test.go │ │ ├── celmatcher.go │ │ ├── celmatcher_test.go │ │ ├── encode/ │ │ │ ├── brotli/ │ │ │ │ └── brotli_precompressed.go │ │ │ ├── caddyfile.go │ │ │ ├── encode.go │ │ │ ├── encode_test.go │ │ │ ├── gzip/ │ │ │ │ ├── gzip.go │ │ │ │ └── gzip_precompressed.go │ │ │ └── zstd/ │ │ │ ├── zstd.go │ │ │ └── zstd_precompressed.go │ │ ├── errors.go │ │ ├── fileserver/ │ │ │ ├── browse.go │ │ │ ├── browse.html │ │ │ ├── browsetplcontext.go │ │ │ ├── browsetplcontext_test.go │ │ │ ├── caddyfile.go │ │ │ ├── command.go │ │ │ ├── matcher.go │ │ │ ├── matcher_test.go │ │ │ ├── staticfiles.go │ │ │ ├── staticfiles_test.go │ │ │ └── testdata/ │ │ │ ├── %D9%85%D9%84%D9%81.txt │ │ │ ├── foo.php.php/ │ │ │ │ └── index.php │ │ │ ├── foo.txt │ │ │ ├── foodir/ │ │ │ │ ├── bar.txt │ │ │ │ └── foo.txt │ │ │ ├── index.php │ │ │ ├── large.txt │ │ │ ├── notphp.php.txt │ │ │ ├── remote.php │ │ │ └── ملف.txt │ │ ├── headers/ │ │ │ ├── caddyfile.go │ │ │ ├── headers.go │ │ │ └── headers_test.go │ │ ├── http2listener.go │ │ ├── httpredirectlistener.go │ │ ├── intercept/ │ │ │ └── intercept.go │ │ ├── invoke.go │ │ ├── ip_matchers.go │ │ ├── ip_range.go │ │ ├── logging/ │ │ │ ├── caddyfile.go │ │ │ └── logappend.go │ │ ├── logging.go │ │ ├── map/ │ │ │ ├── caddyfile.go │ │ │ ├── map.go │ │ │ └── map_test.go │ │ ├── marshalers.go │ │ ├── matchers.go │ │ ├── matchers_test.go │ │ ├── metrics.go │ │ ├── metrics_test.go │ │ ├── proxyprotocol/ │ │ │ ├── listenerwrapper.go │ │ │ ├── module.go │ │ │ └── policy.go │ │ ├── push/ │ │ │ ├── caddyfile.go │ │ │ ├── handler.go │ │ │ ├── link.go │ │ │ └── link_test.go │ │ ├── replacer.go │ │ ├── replacer_test.go │ │ ├── requestbody/ │ │ │ ├── caddyfile.go │ │ │ └── requestbody.go │ │ ├── responsematchers.go │ │ ├── responsematchers_test.go │ │ ├── responsewriter.go │ │ ├── responsewriter_test.go │ │ ├── reverseproxy/ │ │ │ ├── addresses.go │ │ │ ├── addresses_test.go │ │ │ ├── admin.go │ │ │ ├── admin_test.go │ │ │ ├── ascii.go │ │ │ ├── ascii_test.go │ │ │ ├── buffering_test.go │ │ │ ├── caddyfile.go │ │ │ ├── command.go │ │ │ ├── copyresponse.go │ │ │ ├── dynamic_upstreams_test.go │ │ │ ├── fastcgi/ │ │ │ │ ├── caddyfile.go │ │ │ │ ├── client.go │ │ │ │ ├── client_test.go │ │ │ │ ├── fastcgi.go │ │ │ │ ├── fastcgi_test.go │ │ │ │ ├── header.go │ │ │ │ ├── pool.go │ │ │ │ ├── reader.go │ │ │ │ ├── record.go │ │ │ │ └── writer.go │ │ │ ├── forwardauth/ │ │ │ │ └── caddyfile.go │ │ │ ├── headers_test.go │ │ │ ├── healthchecks.go │ │ │ ├── hosts.go │ │ │ ├── httptransport.go │ │ │ ├── httptransport_test.go │ │ │ ├── metrics.go │ │ │ ├── passive_health_test.go │ │ │ ├── retries_test.go │ │ │ ├── reverseproxy.go │ │ │ ├── selectionpolicies.go │ │ │ ├── selectionpolicies_test.go │ │ │ ├── streaming.go │ │ │ ├── streaming_test.go │ │ │ ├── upstreams.go │ │ │ └── upstreams_test.go │ │ ├── rewrite/ │ │ │ ├── caddyfile.go │ │ │ ├── rewrite.go │ │ │ └── rewrite_test.go │ │ ├── routes.go │ │ ├── server.go │ │ ├── server_test.go │ │ ├── standard/ │ │ │ └── imports.go │ │ ├── staticerror.go │ │ ├── staticresp.go │ │ ├── staticresp_test.go │ │ ├── subroute.go │ │ ├── templates/ │ │ │ ├── caddyfile.go │ │ │ ├── frontmatter.go │ │ │ ├── frontmatter_fuzz.go │ │ │ ├── templates.go │ │ │ ├── tplcontext.go │ │ │ └── tplcontext_test.go │ │ ├── tracing/ │ │ │ ├── module.go │ │ │ ├── module_test.go │ │ │ ├── tracer.go │ │ │ ├── tracer_test.go │ │ │ ├── tracerprovider.go │ │ │ └── tracerprovider_test.go │ │ └── vars.go │ ├── caddypki/ │ │ ├── acmeserver/ │ │ │ ├── acmeserver.go │ │ │ ├── acmeserver_test.go │ │ │ ├── caddyfile.go │ │ │ ├── challenges.go │ │ │ ├── policy.go │ │ │ └── policy_test.go │ │ ├── adminapi.go │ │ ├── ca.go │ │ ├── certificates.go │ │ ├── command.go │ │ ├── crypto.go │ │ ├── crypto_test.go │ │ ├── maintain.go │ │ ├── maintain_test.go │ │ └── pki.go │ ├── caddytls/ │ │ ├── acmeissuer.go │ │ ├── automation.go │ │ ├── capools.go │ │ ├── capools_test.go │ │ ├── certmanagers.go │ │ ├── certselection.go │ │ ├── connpolicy.go │ │ ├── connpolicy_test.go │ │ ├── distributedstek/ │ │ │ └── distributedstek.go │ │ ├── ech.go │ │ ├── fileloader.go │ │ ├── folderloader.go │ │ ├── internalissuer.go │ │ ├── internalissuer_test.go │ │ ├── leaffileloader.go │ │ ├── leaffileloader_test.go │ │ ├── leaffolderloader.go │ │ ├── leaffolderloader_test.go │ │ ├── leafpemloader.go │ │ ├── leafpemloader_test.go │ │ ├── leafstorageloader.go │ │ ├── matchers.go │ │ ├── matchers_test.go │ │ ├── ondemand.go │ │ ├── pemloader.go │ │ ├── sessiontickets.go │ │ ├── standardstek/ │ │ │ └── stek.go │ │ ├── storageloader.go │ │ ├── tls.go │ │ ├── values.go │ │ └── zerosslissuer.go │ ├── filestorage/ │ │ └── filestorage.go │ ├── internal/ │ │ └── network/ │ │ └── networkproxy.go │ ├── logging/ │ │ ├── appendencoder.go │ │ ├── cores.go │ │ ├── encoders.go │ │ ├── filewriter.go │ │ ├── filewriter_test.go │ │ ├── filewriter_test_windows.go │ │ ├── filterencoder.go │ │ ├── filters.go │ │ ├── filters_test.go │ │ ├── netwriter.go │ │ └── nopencoder.go │ ├── metrics/ │ │ ├── adminmetrics.go │ │ ├── metrics.go │ │ └── metrics_test.go │ └── standard/ │ └── imports.go ├── modules.go ├── modules_test.go ├── notify/ │ ├── notify_linux.go │ ├── notify_other.go │ └── notify_windows.go ├── replacer.go ├── replacer_fuzz.go ├── replacer_test.go ├── service_windows.go ├── sigtrap.go ├── sigtrap_nonposix.go ├── sigtrap_posix.go ├── storage.go └── usagepool.go
Showing preview only (280K chars total). Download the full file or copy to clipboard to get everything.
SYMBOL INDEX (3053 symbols across 297 files)
FILE: admin.go
function init (line 56) | func init() {
type AdminConfig (line 69) | type AdminConfig struct
method newAdminHandler (line 223) | func (admin *AdminConfig) newAdminHandler(addr NetworkAddress, remote ...
method provisionAdminRouters (line 291) | func (admin *AdminConfig) provisionAdminRouters(ctx Context) error {
method allowedOrigins (line 315) | func (admin AdminConfig) allowedOrigins(addr NetworkAddress) []*url.URL {
type ConfigSettings (line 128) | type ConfigSettings struct
type IdentityConfig (line 159) | type IdentityConfig struct
method certmagicConfig (line 635) | func (ident *IdentityConfig) certmagicConfig(logger *zap.Logger, makeC...
type RemoteAdmin (line 183) | type RemoteAdmin struct
method enforceAccessControls (line 691) | func (remote RemoteAdmin) enforceAccessControls(r *http.Request) error {
type AdminAccess (line 197) | type AdminAccess struct
type AdminPermissions (line 212) | type AdminPermissions struct
function replaceLocalAdminServer (line 389) | func replaceLocalAdminServer(cfg *Config, ctx Context) error {
function manageIdentity (line 480) | func manageIdentity(ctx Context, cfg *Config) error {
function replaceRemoteAdminServer (line 529) | func replaceRemoteAdminServer(ctx Context, cfg *Config) error {
method IdentityCredentials (line 672) | func (ctx Context) IdentityCredentials(logger *zap.Logger) ([]tls.Certif...
function stopAdminServer (line 748) | func stopAdminServer(srv *http.Server) error {
type AdminRouter (line 767) | type AdminRouter interface
type AdminRoute (line 772) | type AdminRoute struct
type adminHandler (line 777) | type adminHandler struct
method ServeHTTP (line 791) | func (h adminHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
method serveHTTP (line 822) | func (h adminHandler) serveHTTP(w http.ResponseWriter, r *http.Request) {
method handleError (line 894) | func (h adminHandler) handleError(w http.ResponseWriter, r *http.Reque...
method checkHost (line 934) | func (h adminHandler) checkHost(r *http.Request) error {
method checkOrigin (line 951) | func (h adminHandler) checkOrigin(r *http.Request) (string, error) {
method getOrigin (line 968) | func (h adminHandler) getOrigin(r *http.Request) (string, *url.URL) {
method originAllowed (line 985) | func (h adminHandler) originAllowed(origin *url.URL) bool {
function etagHasher (line 999) | func etagHasher() hash.Hash { return xxhash.New() }
function makeEtag (line 1003) | func makeEtag(path string, hash hash.Hash) string {
function handleConfig (line 1015) | func handleConfig(w http.ResponseWriter, r *http.Request) error {
function handleConfigID (line 1097) | func handleConfigID(w http.ResponseWriter, r *http.Request) error {
function handleStop (line 1133) | func handleStop(w http.ResponseWriter, r *http.Request) error {
function unsyncedConfigAccess (line 1151) | func unsyncedConfigAccess(method, path string, body []byte, out io.Write...
function RemoveMetaFields (line 1334) | func RemoveMetaFields(rawJSON []byte) []byte {
type AdminHandler (line 1351) | type AdminHandler interface
type AdminHandlerFunc (line 1356) | type AdminHandlerFunc
method ServeHTTP (line 1359) | func (f AdminHandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Req...
type APIError (line 1367) | type APIError struct
method Error (line 1373) | func (e APIError) Error() string {
function parseAdminListenAddr (line 1382) | func parseAdminListenAddr(addr string, defaultAddr string) (NetworkAddre...
function decodeBase64DERCert (line 1401) | func decodeBase64DERCert(certStr string) (*x509.Certificate, error) {
type loggableURLArray (line 1409) | type loggableURLArray
method MarshalLogArray (line 1411) | func (ua loggableURLArray) MarshalLogArray(enc zapcore.ArrayEncoder) e...
function PIDFile (line 1434) | func PIDFile(filename string) error {
constant rawConfigKey (line 1461) | rawConfigKey = "config"
constant idKey (line 1462) | idKey = "@id"
FILE: admin_test.go
function TestUnsyncedConfigAccess (line 54) | func TestUnsyncedConfigAccess(t *testing.T) {
function TestLoadConcurrent (line 150) | func TestLoadConcurrent(t *testing.T) {
type fooModule (line 161) | type fooModule struct
method CaddyModule (line 166) | func (fooModule) CaddyModule() ModuleInfo {
method Start (line 172) | func (fooModule) Start() error { return nil }
method Stop (line 173) | func (fooModule) Stop() error { return nil }
function TestETags (line 175) | func TestETags(t *testing.T) {
function BenchmarkLoad (line 209) | func BenchmarkLoad(b *testing.B) {
function TestAdminHandlerErrorHandling (line 215) | func TestAdminHandlerErrorHandling(t *testing.T) {
function initAdminMetrics (line 245) | func initAdminMetrics() {
function TestAdminHandlerBuiltinRouteErrors (line 271) | func TestAdminHandlerBuiltinRouteErrors(t *testing.T) {
function testGetMetricValue (line 336) | func testGetMetricValue(labels map[string]string) float64 {
type mockRouter (line 350) | type mockRouter struct
method Routes (line 354) | func (m mockRouter) Routes() []AdminRoute {
type mockModule (line 358) | type mockModule struct
method CaddyModule (line 362) | func (m *mockModule) CaddyModule() ModuleInfo {
function TestNewAdminHandlerRouterRegistration (line 376) | func TestNewAdminHandlerRouterRegistration(t *testing.T) {
type mockProvisionableRouter (line 424) | type mockProvisionableRouter struct
method Provision (line 430) | func (m *mockProvisionableRouter) Provision(Context) error {
type mockProvisionableModule (line 435) | type mockProvisionableModule struct
method CaddyModule (line 439) | func (m *mockProvisionableModule) CaddyModule() ModuleInfo {
function TestAdminRouterProvisioning (line 454) | func TestAdminRouterProvisioning(t *testing.T) {
function TestAllowedOriginsUnixSocket (line 527) | func TestAllowedOriginsUnixSocket(t *testing.T) {
function TestReplaceRemoteAdminServer (line 607) | func TestReplaceRemoteAdminServer(t *testing.T) {
type mockIssuer (line 738) | type mockIssuer struct
method Issue (line 742) | func (m *mockIssuer) Issue(ctx context.Context, csr *x509.CertificateR...
method SetConfig (line 748) | func (m *mockIssuer) SetConfig(cfg *certmagic.Config) {
method IssuerKey (line 752) | func (m *mockIssuer) IssuerKey() string {
type mockIssuerModule (line 756) | type mockIssuerModule struct
method CaddyModule (line 760) | func (m *mockIssuerModule) CaddyModule() ModuleInfo {
function TestManageIdentity (line 769) | func TestManageIdentity(t *testing.T) {
FILE: caddy.go
type Config (line 68) | type Config struct
type App (line 98) | type App interface
function Run (line 104) | func Run(cfg *Config) error {
function Load (line 115) | func Load(cfgJSON []byte, forceReload bool) error {
function changeConfig (line 159) | func changeConfig(method, path string, input []byte, ifMatchHeader strin...
function readConfig (line 281) | func readConfig(path string, out io.Writer) error {
function indexConfigObjects (line 291) | func indexConfigObjects(ptr any, configPath string, index map[string]str...
function unsyncedDecodeAndRun (line 338) | func unsyncedDecodeAndRun(cfgJSON []byte, allowPersist bool) error {
function run (line 420) | func run(newCfg *Config, start bool) (Context, error) {
function provisionContext (line 492) | func provisionContext(newCfg *Config, replaceAdminServer bool) (Context,...
function ProvisionContext (line 597) | func ProvisionContext(newCfg *Config) (Context, error) {
function finishSettingUp (line 602) | func finishSettingUp(ctx Context, cfg *Config) error {
type ConfigLoader (line 692) | type ConfigLoader interface
function Stop (line 702) | func Stop() error {
function unsyncedStop (line 732) | func unsyncedStop(ctx Context) {
function Validate (line 754) | func Validate(cfg *Config) error {
function exitProcess (line 768) | func exitProcess(ctx context.Context, logger *zap.Logger) {
function Exiting (line 853) | func Exiting() bool { return atomic.LoadInt32(exiting) == 1 }
function OnExit (line 861) | func OnExit(f func(context.Context)) {
type Duration (line 876) | type Duration
method UnmarshalJSON (line 879) | func (d *Duration) UnmarshalJSON(b []byte) error {
function ParseDuration (line 898) | func ParseDuration(s string) (time.Duration, error) {
function InstanceID (line 930) | func InstanceID() (uuid.UUID, error) {
function Version (line 1011) | func Version() (simple, full string) {
type Event (line 1115) | type Event struct
method ID (line 1158) | func (e Event) ID() uuid.UUID { return e.id }
method Timestamp (line 1159) | func (e Event) Timestamp() time.Time { return e.ts }
method Name (line 1160) | func (e Event) Name() string { return e.name }
method Origin (line 1161) | func (e Event) Origin() Module { return e.origin }
method CloudEvent (line 1166) | func (e Event) CloudEvent() CloudEvent {
function NewEvent (line 1143) | func NewEvent(ctx Context, name string, data map[string]any) (Event, err...
type CloudEvent (line 1189) | type CloudEvent struct
function ActiveContext (line 1205) | func ActiveContext() Context {
type CtxKey (line 1212) | type CtxKey
type reloadFromSourceFunc (line 1258) | type reloadFromSourceFunc
function SetLastConfig (line 1270) | func SetLastConfig(file, adapter string, fn reloadFromSourceFunc) {
function ClearLastConfigIfDifferent (line 1281) | func ClearLastConfigIfDifferent(srcFile, srcAdapter string) {
function getLastConfig (line 1289) | func getLastConfig() (file, adapter string, fn reloadFromSourceFunc) {
function lastConfigMatches (line 1302) | func lastConfigMatches(srcFile, srcAdapter string) bool {
constant ImportPath (line 1337) | ImportPath = "github.com/caddyserver/caddy/v2"
FILE: caddy_test.go
function TestParseDuration (line 23) | func TestParseDuration(t *testing.T) {
function TestEvent_CloudEvent_NilOrigin (line 77) | func TestEvent_CloudEvent_NilOrigin(t *testing.T) {
FILE: caddyconfig/caddyfile/adapter.go
type Adapter (line 27) | type Adapter struct
method Adapt (line 32) | func (a Adapter) Adapt(body []byte, options map[string]any) ([]byte, [...
function FormattingDifference (line 69) | func FormattingDifference(filename string, body []byte) (caddyconfig.War...
type Unmarshaler (line 106) | type Unmarshaler interface
type ServerType (line 111) | type ServerType interface
function UnmarshalModule (line 127) | func UnmarshalModule(d *Dispenser, moduleID string) (Unmarshaler, error) {
FILE: caddyconfig/caddyfile/dispenser.go
type Dispenser (line 29) | type Dispenser struct
method Next (line 60) | func (d *Dispenser) Next() bool {
method Prev (line 74) | func (d *Dispenser) Prev() bool {
method NextArg (line 89) | func (d *Dispenser) NextArg() bool {
method nextOnSameLine (line 103) | func (d *Dispenser) nextOnSameLine() bool {
method NextLine (line 124) | func (d *Dispenser) NextLine() bool {
method NextBlock (line 167) | func (d *Dispenser) NextBlock(initialNestingLevel int) bool {
method Nesting (line 196) | func (d *Dispenser) Nesting() int {
method Val (line 202) | func (d *Dispenser) Val() string {
method ValRaw (line 213) | func (d *Dispenser) ValRaw() string {
method ScalarVal (line 227) | func (d *Dispenser) ScalarVal() any {
method Line (line 251) | func (d *Dispenser) Line() int {
method File (line 259) | func (d *Dispenser) File() string {
method Args (line 272) | func (d *Dispenser) Args(targets ...*string) bool {
method AllArgs (line 286) | func (d *Dispenser) AllArgs(targets ...*string) bool {
method CountRemainingArgs (line 299) | func (d *Dispenser) CountRemainingArgs() int {
method RemainingArgs (line 314) | func (d *Dispenser) RemainingArgs() []string {
method RemainingArgsRaw (line 326) | func (d *Dispenser) RemainingArgsRaw() []string {
method RemainingArgsAsTokens (line 338) | func (d *Dispenser) RemainingArgsAsTokens() []Token {
method NewFromNextSegment (line 351) | func (d *Dispenser) NewFromNextSegment() *Dispenser {
method NextSegment (line 358) | func (d *Dispenser) NextSegment() Segment {
method Token (line 390) | func (d *Dispenser) Token() Token {
method Reset (line 399) | func (d *Dispenser) Reset() {
method ArgErr (line 408) | func (d *Dispenser) ArgErr() error {
method SyntaxErr (line 417) | func (d *Dispenser) SyntaxErr(expected string) error {
method EOFErr (line 424) | func (d *Dispenser) EOFErr() error {
method Err (line 429) | func (d *Dispenser) Err(msg string) error {
method Errf (line 434) | func (d *Dispenser) Errf(format string, args ...any) error {
method WrapErr (line 439) | func (d *Dispenser) WrapErr(err error) error {
method Delete (line 455) | func (d *Dispenser) Delete() []Token {
method DeleteN (line 465) | func (d *Dispenser) DeleteN(amount int) []Token {
method SetContext (line 474) | func (d *Dispenser) SetContext(key string, value any) {
method GetContext (line 482) | func (d *Dispenser) GetContext(key string) any {
method GetContextString (line 491) | func (d *Dispenser) GetContextString(key string) string {
method isNewLine (line 504) | func (d *Dispenser) isNewLine() bool {
method isNextOnNewLine (line 520) | func (d *Dispenser) isNextOnNewLine() bool {
function NewDispenser (line 40) | func NewDispenser(tokens []Token) *Dispenser {
function NewTestDispenser (line 49) | func NewTestDispenser(input string) *Dispenser {
constant MatcherNameCtxKey (line 533) | MatcherNameCtxKey = "matcher_name"
FILE: caddyconfig/caddyfile/dispenser_test.go
function TestDispenser_Val_Next (line 24) | func TestDispenser_Val_Next(t *testing.T) {
function TestDispenser_NextArg (line 63) | func TestDispenser_NextArg(t *testing.T) {
function TestDispenser_NextLine (line 110) | func TestDispenser_NextLine(t *testing.T) {
function TestDispenser_NextBlock (line 140) | func TestDispenser_NextBlock(t *testing.T) {
function TestDispenser_Args (line 171) | func TestDispenser_Args(t *testing.T) {
function TestDispenser_RemainingArgs (line 239) | func TestDispenser_RemainingArgs(t *testing.T) {
function TestDispenser_RemainingArgsAsTokens (line 277) | func TestDispenser_RemainingArgsAsTokens(t *testing.T) {
function TestDispenser_ArgErr_Err (line 337) | func TestDispenser_ArgErr_Err(t *testing.T) {
FILE: caddyconfig/caddyfile/formatter.go
function Format (line 30) | func Format(input []byte) []byte {
FILE: caddyconfig/caddyfile/formatter_fuzz.go
function FuzzFormat (line 21) | func FuzzFormat(input []byte) int {
FILE: caddyconfig/caddyfile/formatter_test.go
function TestFormatter (line 22) | func TestFormatter(t *testing.T) {
FILE: caddyconfig/caddyfile/importargs.go
function parseVariadic (line 31) | func parseVariadic(token Token, argCount int) (bool, int, int) {
function makeArgsReplacer (line 98) | func makeArgsReplacer(args []string) *caddy.Replacer {
FILE: caddyconfig/caddyfile/importgraph.go
type adjacency (line 22) | type adjacency
type importGraph (line 24) | type importGraph struct
method addNode (line 29) | func (i *importGraph) addNode(name string) {
method addNodes (line 39) | func (i *importGraph) addNodes(names []string) {
method removeNode (line 45) | func (i *importGraph) removeNode(name string) {
method removeNodes (line 49) | func (i *importGraph) removeNodes(names []string) {
method addEdge (line 55) | func (i *importGraph) addEdge(from, to string) error {
method addEdges (line 80) | func (i *importGraph) addEdges(from string, tos []string) error {
method areConnected (line 90) | func (i *importGraph) areConnected(from, to string) bool {
method willCycle (line 98) | func (i *importGraph) willCycle(from, to string) bool {
method exists (line 123) | func (i *importGraph) exists(key string) bool {
FILE: caddyconfig/caddyfile/lexer.go
type lexer (line 32) | type lexer struct
method load (line 78) | func (l *lexer) load(input io.Reader) error {
method next (line 107) | func (l *lexer) next() (bool, error) {
method finalizeHeredoc (line 300) | func (l *lexer) finalizeHeredoc(val []rune, marker string) ([]rune, er...
type Token (line 40) | type Token struct
method Quoted (line 346) | func (t Token) Quoted() bool {
method NumLineBreaks (line 351) | func (t Token) NumLineBreaks() int {
method Clone (line 363) | func (t Token) Clone() Token {
function Tokenize (line 56) | func Tokenize(input []byte, filename string) ([]Token, error) {
function isNextOnNewLine (line 378) | func isNextOnNewLine(t1, t2 Token) bool {
FILE: caddyconfig/caddyfile/lexer_fuzz.go
function FuzzTokenize (line 19) | func FuzzTokenize(input []byte) int {
FILE: caddyconfig/caddyfile/lexer_test.go
function TestLexer (line 21) | func TestLexer(t *testing.T) {
function lexerCompare (line 524) | func lexerCompare(t *testing.T, n int, expected, actual []Token) {
FILE: caddyconfig/caddyfile/parse.go
function Parse (line 39) | func Parse(filename string, input []byte) ([]ServerBlock, error) {
function allTokens (line 63) | func allTokens(filename string, input []byte) ([]Token, error) {
function replaceEnvVars (line 69) | func replaceEnvVars(input []byte) []byte {
type parser (line 113) | type parser struct
method parseAll (line 122) | func (p *parser) parseAll() ([]ServerBlock, error) {
method parseOne (line 141) | func (p *parser) parseOne() error {
method begin (line 146) | func (p *parser) begin() error {
method addresses (line 210) | func (p *parser) addresses() error {
method blockContents (line 293) | func (p *parser) blockContents() error {
method directives (line 320) | func (p *parser) directives() error {
method doImport (line 356) | func (p *parser) doImport(nesting int) error {
method doSingleImport (line 584) | func (p *parser) doSingleImport(importFile string) ([]Token, error) {
method directive (line 632) | func (p *parser) directive() error {
method openCurlyBrace (line 683) | func (p *parser) openCurlyBrace() error {
method closeCurlyBrace (line 694) | func (p *parser) closeCurlyBrace() error {
method isNamedRoute (line 701) | func (p *parser) isNamedRoute() (bool, string) {
method isSnippet (line 710) | func (p *parser) isSnippet() (bool, string) {
method blockTokens (line 720) | func (p *parser) blockTokens(retainCurlies bool) ([]Token, error) {
type ServerBlock (line 756) | type ServerBlock struct
method GetKeysText (line 763) | func (sb ServerBlock) GetKeysText() []string {
method DispenseDirective (line 773) | func (sb ServerBlock) DispenseDirective(dir string) *Dispenser {
type Segment (line 786) | type Segment
method Directive (line 790) | func (s Segment) Directive() string {
FILE: caddyconfig/caddyfile/parse_test.go
function TestParseVariadic (line 25) | func TestParseVariadic(t *testing.T) {
function TestAllTokens (line 111) | func TestAllTokens(t *testing.T) {
function TestParseOneAndImport (line 129) | func TestParseOneAndImport(t *testing.T) {
function TestRecursiveImport (line 374) | func TestRecursiveImport(t *testing.T) {
function TestDirectiveImport (line 470) | func TestDirectiveImport(t *testing.T) {
function TestParseAll (line 533) | func TestParseAll(t *testing.T) {
function TestEnvironmentReplacement (line 636) | func TestEnvironmentReplacement(t *testing.T) {
function TestImportReplacementInJSONWithBrace (line 732) | func TestImportReplacementInJSONWithBrace(t *testing.T) {
function TestSnippets (line 762) | func TestSnippets(t *testing.T) {
function writeStringToTempFileOrDie (line 793) | func writeStringToTempFileOrDie(t *testing.T, str string) (pathToFile st...
function TestImportedFilesIgnoreNonDirectiveImportTokens (line 807) | func TestImportedFilesIgnoreNonDirectiveImportTokens(t *testing.T) {
function TestSnippetAcrossMultipleFiles (line 831) | func TestSnippetAcrossMultipleFiles(t *testing.T) {
function TestRejectsGlobalMatcher (line 865) | func TestRejectsGlobalMatcher(t *testing.T) {
function TestRejectAnonymousImportBlock (line 888) | func TestRejectAnonymousImportBlock(t *testing.T) {
function TestAcceptSiteImportWithBraces (line 913) | func TestAcceptSiteImportWithBraces(t *testing.T) {
function testParser (line 933) | func testParser(input string) parser {
FILE: caddyconfig/configadapters.go
type Adapter (line 26) | type Adapter interface
type Warning (line 31) | type Warning struct
method String (line 38) | func (w Warning) String() string {
function JSON (line 51) | func JSON(val any, warnings *[]Warning) json.RawMessage {
function JSONModuleObject (line 69) | func JSONModuleObject(val any, fieldName, fieldVal string, warnings *[]W...
function RegisterAdapter (line 111) | func RegisterAdapter(name string, adapter Adapter) {
function GetAdapter (line 121) | func GetAdapter(name string) Adapter {
type adapterModule (line 130) | type adapterModule struct
method CaddyModule (line 135) | func (am adapterModule) CaddyModule() caddy.ModuleInfo {
FILE: caddyconfig/httpcaddyfile/addresses.go
method mapAddressToProtocolToServerBlocks (line 80) | func (st *ServerType) mapAddressToProtocolToServerBlocks(originalServerB...
method consolidateAddrMappings (line 195) | func (st *ServerType) consolidateAddrMappings(addrToProtocolToServerBloc...
method listenersForServerBlockAddress (line 270) | func (st *ServerType) listenersForServerBlockAddress(sblock serverBlock,...
type addressesWithProtocols (line 355) | type addressesWithProtocols struct
type Address (line 367) | type Address struct
method String (line 425) | func (a Address) String() string {
method Normalize (line 455) | func (a Address) Normalize() Address {
function ParseAddress (line 373) | func ParseAddress(str string) (Address, error) {
function lowerExceptPlaceholders (line 478) | func lowerExceptPlaceholders(s string) string {
FILE: caddyconfig/httpcaddyfile/addresses_fuzz.go
function FuzzParseAddress (line 19) | func FuzzParseAddress(data []byte) int {
FILE: caddyconfig/httpcaddyfile/addresses_test.go
function TestParseAddress (line 7) | func TestParseAddress(t *testing.T) {
function TestAddressString (line 84) | func TestAddressString(t *testing.T) {
function TestKeyNormalization (line 106) | func TestKeyNormalization(t *testing.T) {
FILE: caddyconfig/httpcaddyfile/builtins.go
function init (line 38) | func init() {
function parseBind (line 63) | func parseBind(h Helper) ([]ConfigValue, error) {
function parseTLS (line 118) | func parseTLS(h Helper) ([]ConfigValue, error) {
function parseRoot (line 654) | func parseRoot(h Helper) ([]ConfigValue, error) {
function parseFilesystem (line 692) | func parseFilesystem(h Helper) (caddyhttp.MiddlewareHandler, error) {
function parseVars (line 704) | func parseVars(h Helper) (caddyhttp.MiddlewareHandler, error) {
function parseRedir (line 719) | func parseRedir(h Helper) (caddyhttp.MiddlewareHandler, error) {
function parseRespond (line 793) | func parseRespond(h Helper) (caddyhttp.MiddlewareHandler, error) {
function parseAbort (line 800) | func parseAbort(h Helper) (caddyhttp.MiddlewareHandler, error) {
function parseError (line 809) | func parseError(h Helper) (caddyhttp.MiddlewareHandler, error) {
function parseRoute (line 816) | func parseRoute(h Helper) (caddyhttp.MiddlewareHandler, error) {
function parseHandle (line 833) | func parseHandle(h Helper) (caddyhttp.MiddlewareHandler, error) {
function parseHandleErrors (line 837) | func parseHandleErrors(h Helper) ([]ConfigValue, error) {
function parseInvoke (line 913) | func parseInvoke(h Helper) (caddyhttp.MiddlewareHandler, error) {
function parseLog (line 942) | func parseLog(h Helper) ([]ConfigValue, error) {
function parseLogHelper (line 950) | func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]Con...
function parseLogSkip (line 1203) | func parseLogSkip(h Helper) (caddyhttp.MiddlewareHandler, error) {
function parseLogName (line 1225) | func parseLogName(h Helper) (caddyhttp.MiddlewareHandler, error) {
FILE: caddyconfig/httpcaddyfile/builtins_test.go
function TestLogDirectiveSyntax (line 11) | func TestLogDirectiveSyntax(t *testing.T) {
function TestRedirDirectiveSyntax (line 98) | func TestRedirDirectiveSyntax(t *testing.T) {
function TestImportErrorLine (line 235) | func TestImportErrorLine(t *testing.T) {
function TestNestedImport (line 298) | func TestNestedImport(t *testing.T) {
FILE: caddyconfig/httpcaddyfile/directives.go
function RegisterDirective (line 109) | func RegisterDirective(dir string, setupFunc UnmarshalFunc) {
function RegisterHandlerDirective (line 120) | func RegisterHandlerDirective(dir string, setupFunc UnmarshalHandlerFunc) {
function RegisterDirectiveOrder (line 153) | func RegisterDirectiveOrder(dir string, position Positional, standardDir...
function RegisterGlobalOption (line 193) | func RegisterGlobalOption(opt string, setupFunc UnmarshalGlobalFunc) {
type Helper (line 202) | type Helper struct
method Option (line 214) | func (h Helper) Option(name string) any {
method Caddyfiles (line 220) | func (h Helper) Caddyfiles() []string {
method JSON (line 239) | func (h Helper) JSON(val any) json.RawMessage {
method MatcherToken (line 247) | func (h Helper) MatcherToken() (caddy.ModuleMap, bool, error) {
method ExtractMatcherSet (line 259) | func (h Helper) ExtractMatcherSet() (caddy.ModuleMap, error) {
method NewRoute (line 277) | func (h Helper) NewRoute(matcherSet caddy.ModuleMap,
method GroupRoutes (line 306) | func (h Helper) GroupRoutes(vals []ConfigValue) {
method WithDispenser (line 333) | func (h Helper) WithDispenser(d *caddyfile.Dispenser) Helper {
function ParseSegmentAsSubroute (line 341) | func ParseSegmentAsSubroute(h Helper) (caddyhttp.MiddlewareHandler, erro...
function parseSegmentAsConfig (line 353) | func parseSegmentAsConfig(h Helper) ([]ConfigValue, error) {
type ConfigValue (line 420) | type ConfigValue struct
function sortRoutes (line 437) | func sortRoutes(routes []ConfigValue) {
type serverBlock (line 536) | type serverBlock struct
method hostsFromKeys (line 553) | func (sb serverBlock) hostsFromKeys(loggerMode bool) []string {
method hostsFromKeysNotHTTP (line 585) | func (sb serverBlock) hostsFromKeysNotHTTP(httpPort string) []string {
method hasHostCatchAllKey (line 608) | func (sb serverBlock) hasHostCatchAllKey() bool {
method isAllHTTP (line 616) | func (sb serverBlock) isAllHTTP() bool {
type Positional (line 623) | type Positional
constant Before (line 626) | Before Positional = "before"
constant After (line 627) | After Positional = "after"
constant First (line 628) | First Positional = "first"
constant Last (line 629) | Last Positional = "last"
type UnmarshalFunc (line 636) | type UnmarshalFunc
type UnmarshalHandlerFunc (line 647) | type UnmarshalHandlerFunc
type UnmarshalGlobalFunc (line 653) | type UnmarshalGlobalFunc
FILE: caddyconfig/httpcaddyfile/directives_test.go
function TestHostsFromKeys (line 9) | func TestHostsFromKeys(t *testing.T) {
FILE: caddyconfig/httpcaddyfile/httptype.go
function init (line 38) | func init() {
type App (line 45) | type App struct
type ServerType (line 54) | type ServerType struct
method Setup (line 57) | func (st ServerType) Setup(
method evaluateGlobalOptionsBlock (line 369) | func (ServerType) evaluateGlobalOptionsBlock(serverBlocks []serverBloc...
method extractNamedRoutes (line 459) | func (ServerType) extractNamedRoutes(
method serversFromPairings (line 533) | func (st *ServerType) serversFromPairings(
method compileEncodedMatcherSets (line 1459) | func (st *ServerType) compileEncodedMatcherSets(sblock serverBlock) ([...
function detectConflictingSchemes (line 1002) | func detectConflictingSchemes(srv *caddyhttp.Server, serverBlocks []serv...
function consolidateConnPolicies (line 1062) | func consolidateConnPolicies(cps caddytls.ConnectionPolicies) (caddytls....
function appendSubrouteToRouteList (line 1225) | func appendSubrouteToRouteList(routeList caddyhttp.RouteList,
function buildSubroute (line 1288) | func buildSubroute(routes []ConfigValue, groupCounter counter, needsSort...
function normalizeDirectiveName (line 1400) | func normalizeDirectiveName(directive string) string {
function consolidateRoutes (line 1414) | func consolidateRoutes(routes caddyhttp.RouteList) caddyhttp.RouteList {
function matcherSetFromMatcherToken (line 1428) | func matcherSetFromMatcherToken(
function parseMatcherDefinitions (line 1535) | func parseMatcherDefinitions(d *caddyfile.Dispenser, matchers map[string...
function encodeMatcherSet (line 1623) | func encodeMatcherSet(matchers map[string]caddyhttp.RequestMatcherWithEr...
function WasReplacedPlaceholderShorthand (line 1640) | func WasReplacedPlaceholderShorthand(token string) string {
function tryInt (line 1659) | func tryInt(val any, warnings *[]caddyconfig.Warning) int {
function tryString (line 1667) | func tryString(val any, warnings *[]caddyconfig.Warning) string {
function tryDuration (line 1675) | func tryDuration(val any, warnings *[]caddyconfig.Warning) caddy.Duration {
function listenersUseAnyPortOtherThan (line 1686) | func listenersUseAnyPortOtherThan(addresses []string, otherPort string) ...
function mapContains (line 1703) | func mapContains[K comparable, V any](m map[K]V, keys []K) bool {
function specificity (line 1724) | func specificity(s string) int {
type counter (line 1741) | type counter struct
method nextGroup (line 1745) | func (c counter) nextGroup() string {
type namedCustomLog (line 1751) | type namedCustomLog struct
type addressWithProtocols (line 1760) | type addressWithProtocols struct
type sbAddrAssociation (line 1768) | type sbAddrAssociation struct
constant matcherPrefix (line 1774) | matcherPrefix = "@"
constant namedRouteKey (line 1775) | namedRouteKey = "named_route"
FILE: caddyconfig/httpcaddyfile/httptype_test.go
function TestMatcherSyntax (line 11) | func TestMatcherSyntax(t *testing.T) {
function TestSpecificity (line 79) | func TestSpecificity(t *testing.T) {
function TestGlobalOptions (line 109) | func TestGlobalOptions(t *testing.T) {
function TestDefaultSNIWithoutHTTPS (line 215) | func TestDefaultSNIWithoutHTTPS(t *testing.T) {
FILE: caddyconfig/httpcaddyfile/options.go
function init (line 32) | func init() {
function parseOptTrue (line 72) | func parseOptTrue(d *caddyfile.Dispenser, _ any) (any, error) { return t...
function parseOptHTTPPort (line 74) | func parseOptHTTPPort(d *caddyfile.Dispenser, _ any) (any, error) {
function parseOptHTTPSPort (line 89) | func parseOptHTTPSPort(d *caddyfile.Dispenser, _ any) (any, error) {
function parseOptOrder (line 104) | func parseOptOrder(d *caddyfile.Dispenser, _ any) (any, error) {
function parseOptStorage (line 179) | func parseOptStorage(d *caddyfile.Dispenser, _ any) (any, error) {
function parseStorageCheck (line 198) | func parseStorageCheck(d *caddyfile.Dispenser, _ any) (any, error) {
function parseStorageCleanInterval (line 213) | func parseStorageCleanInterval(d *caddyfile.Dispenser, _ any) (any, erro...
function parseOptDuration (line 232) | func parseOptDuration(d *caddyfile.Dispenser, _ any) (any, error) {
function parseOptACMEEAB (line 246) | func parseOptACMEEAB(d *caddyfile.Dispenser, _ any) (any, error) {
function parseOptCertIssuer (line 273) | func parseOptCertIssuer(d *caddyfile.Dispenser, existing any) (any, erro...
function parseOptSingleString (line 298) | func parseOptSingleString(d *caddyfile.Dispenser, _ any) (any, error) {
function parseOptTLSResolvers (line 310) | func parseOptTLSResolvers(d *caddyfile.Dispenser, _ any) (any, error) {
function parseOptDefaultBind (line 319) | func parseOptDefaultBind(d *caddyfile.Dispenser, _ any) (any, error) {
function parseOptAdmin (line 347) | func parseOptAdmin(d *caddyfile.Dispenser, _ any) (any, error) {
function parseOptOnDemand (line 383) | func parseOptOnDemand(d *caddyfile.Dispenser, _ any) (any, error) {
function parseOptPersistConfig (line 444) | func parseOptPersistConfig(d *caddyfile.Dispenser, _ any) (any, error) {
function parseOptAutoHTTPS (line 459) | func parseOptAutoHTTPS(d *caddyfile.Dispenser, _ any) (any, error) {
function unmarshalCaddyfileMetricsOptions (line 478) | func unmarshalCaddyfileMetricsOptions(d *caddyfile.Dispenser) (any, erro...
function parseMetricsOptions (line 494) | func parseMetricsOptions(d *caddyfile.Dispenser, _ any) (any, error) {
function parseServerOptions (line 498) | func parseServerOptions(d *caddyfile.Dispenser, _ any) (any, error) {
function parseOCSPStaplingOptions (line 502) | func parseOCSPStaplingOptions(d *caddyfile.Dispenser, _ any) (any, error) {
function parseLogOptions (line 528) | func parseLogOptions(d *caddyfile.Dispenser, existingVal any) (any, erro...
function parseOptPreferredChains (line 563) | func parseOptPreferredChains(d *caddyfile.Dispenser, _ any) (any, error) {
function parseOptDNS (line 568) | func parseOptDNS(d *caddyfile.Dispenser, _ any) (any, error) {
function parseOptECH (line 596) | func parseOptECH(d *caddyfile.Dispenser, _ any) (any, error) {
function parseOptRenewalWindowRatio (line 639) | func parseOptRenewalWindowRatio(d *caddyfile.Dispenser, _ any) (any, err...
FILE: caddyconfig/httpcaddyfile/options_test.go
function TestGlobalLogOptionSyntax (line 12) | func TestGlobalLogOptionSyntax(t *testing.T) {
function TestGlobalResolversOption (line 68) | func TestGlobalResolversOption(t *testing.T) {
FILE: caddyconfig/httpcaddyfile/pkiapp.go
function init (line 27) | func init() {
function parsePKIApp (line 55) | func parsePKIApp(d *caddyfile.Dispenser, existingVal any) (any, error) {
method buildPKIApp (line 197) | func (st ServerType) buildPKIApp(
FILE: caddyconfig/httpcaddyfile/pkiapp_test.go
function TestParsePKIApp_maintenanceIntervalAndRenewalWindowRatio (line 25) | func TestParsePKIApp_maintenanceIntervalAndRenewalWindowRatio(t *testing...
function TestParsePKIApp_renewalWindowRatioInvalid (line 70) | func TestParsePKIApp_renewalWindowRatioInvalid(t *testing.T) {
FILE: caddyconfig/httpcaddyfile/serveroptions.go
type serverOptions (line 32) | type serverOptions struct
function unmarshalCaddyfileServerOptions (line 65) | func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
function applyServerOptions (line 334) | func applyServerOptions(
FILE: caddyconfig/httpcaddyfile/shorthands.go
type ComplexShorthandReplacer (line 10) | type ComplexShorthandReplacer struct
type ShorthandReplacer (line 15) | type ShorthandReplacer struct
method ApplyToSegment (line 94) | func (s ShorthandReplacer) ApplyToSegment(segment *caddyfile.Segment) {
function NewShorthandReplacer (line 20) | func NewShorthandReplacer() ShorthandReplacer {
function placeholderShorthands (line 53) | func placeholderShorthands() []string {
FILE: caddyconfig/httpcaddyfile/tlsapp.go
method buildTLSApp (line 36) | func (st ServerType) buildTLSApp(
type acmeCapable (line 514) | type acmeCapable interface
function fillInGlobalACMEDefaults (line 516) | func fillInGlobalACMEDefaults(issuer certmagic.Issuer, options map[strin...
function newBaseAutomationPolicy (line 620) | func newBaseAutomationPolicy(
function consolidateAutomationPolicies (line 686) | func consolidateAutomationPolicies(aps []*caddytls.AutomationPolicy) []*...
function automationPolicyIsSubset (line 779) | func automationPolicyIsSubset(a, b *caddytls.AutomationPolicy) bool {
function automationPolicyShadows (line 803) | func automationPolicyShadows(i int, aps []*caddytls.AutomationPolicy) int {
function subjectQualifiesForPublicCert (line 825) | func subjectQualifiesForPublicCert(ap *caddytls.AutomationPolicy, subj s...
function automationPolicyHasAllPublicNames (line 833) | func automationPolicyHasAllPublicNames(ap *caddytls.AutomationPolicy) bo...
function isTailscaleDomain (line 839) | func isTailscaleDomain(name string) bool {
FILE: caddyconfig/httpcaddyfile/tlsapp_test.go
function TestAutomationPolicyIsSubset (line 9) | func TestAutomationPolicyIsSubset(t *testing.T) {
FILE: caddyconfig/httploader.go
function init (line 29) | func init() {
type HTTPLoader (line 41) | type HTTPLoader struct
method CaddyModule (line 76) | func (HTTPLoader) CaddyModule() caddy.ModuleInfo {
method LoadConfig (line 84) | func (hl HTTPLoader) LoadConfig(ctx caddy.Context) ([]byte, error) {
method makeClient (line 170) | func (hl HTTPLoader) makeClient(ctx caddy.Context) (*http.Client, erro...
function attemptHttpCall (line 138) | func attemptHttpCall(client *http.Client, request *http.Request) (*http....
function doHttpCallWithRetries (line 149) | func doHttpCallWithRetries(ctx caddy.Context, client *http.Client, reque...
FILE: caddyconfig/load.go
function init (line 30) | func init() {
type adminLoad (line 44) | type adminLoad struct
method CaddyModule (line 47) | func (adminLoad) CaddyModule() caddy.ModuleInfo {
method Routes (line 55) | func (al adminLoad) Routes() []caddy.AdminRoute {
method handleLoad (line 73) | func (adminLoad) handleLoad(w http.ResponseWriter, r *http.Request) er...
method handleAdapt (line 137) | func (adminLoad) handleAdapt(w http.ResponseWriter, r *http.Request) e...
function adaptByContentType (line 179) | func adaptByContentType(contentType string, body []byte) ([]byte, []Warn...
FILE: caddytest/caddytest.go
type Config (line 35) | type Config struct
type Tester (line 60) | type Tester struct
method WithDefaultOverrides (line 87) | func (tc *Tester) WithDefaultOverrides(overrides Config) *Tester {
method InitServer (line 118) | func (tc *Tester) InitServer(rawConfig string, configType string) {
method initServer (line 131) | func (tc *Tester) initServer(rawConfig string, configType string) error {
method ensureConfigRunning (line 212) | func (tc *Tester) ensureConfigRunning(rawConfig string, configType str...
method AssertRedirect (line 376) | func (tc *Tester) AssertRedirect(requestURI string, expectedToLocation...
method AssertResponseCode (line 506) | func (tc *Tester) AssertResponseCode(req *http.Request, expectedStatus...
method AssertResponse (line 522) | func (tc *Tester) AssertResponse(req *http.Request, expectedStatusCode...
method AssertGetResponse (line 545) | func (tc *Tester) AssertGetResponse(requestURI string, expectedStatusC...
method AssertDeleteResponse (line 557) | func (tc *Tester) AssertDeleteResponse(requestURI string, expectedStat...
method AssertPostResponseBody (line 569) | func (tc *Tester) AssertPostResponseBody(requestURI string, requestHea...
method AssertPutResponseBody (line 584) | func (tc *Tester) AssertPutResponseBody(requestURI string, requestHead...
method AssertPatchResponseBody (line 599) | func (tc *Tester) AssertPatchResponseBody(requestURI string, requestHe...
function NewTester (line 68) | func NewTester(t testing.TB) *Tester {
type configLoadError (line 105) | type configLoadError struct
method Error (line 109) | func (e configLoadError) Error() string { return e.Response }
function timeElapsed (line 111) | func timeElapsed(start time.Time, name string) {
constant initConfig (line 260) | initConfig = `{
function validateTestPrerequisites (line 267) | func validateTestPrerequisites(tc *Tester) error {
function isCaddyAdminRunning (line 304) | func isCaddyAdminRunning(tc *Tester) error {
function getIntegrationDir (line 318) | func getIntegrationDir() string {
function prependCaddyFilePath (line 330) | func prependCaddyFilePath(rawConfig string) string {
function CreateTestingTransport (line 337) | func CreateTestingTransport() *http.Transport {
function AssertLoadError (line 364) | func AssertLoadError(t *testing.T, rawConfig string, configType string, ...
function CompareAdapt (line 415) | func CompareAdapt(t testing.TB, filename, rawConfig string, adapterName ...
function AssertAdapt (line 476) | func AssertAdapt(t testing.TB, rawConfig string, adapterName string, exp...
function applyHeaders (line 487) | func applyHeaders(t testing.TB, req *http.Request, requestHeaders []stri...
FILE: caddytest/caddytest_test.go
function TestReplaceCertificatePaths (line 10) | func TestReplaceCertificatePaths(t *testing.T) {
function TestLoadUnorderedJSON (line 37) | func TestLoadUnorderedJSON(t *testing.T) {
function TestCheckID (line 131) | func TestCheckID(t *testing.T) {
FILE: caddytest/integration/acme_test.go
constant acmeChallengePort (line 25) | acmeChallengePort = 9081
function TestACMEServerWithDefaults (line 28) | func TestACMEServerWithDefaults(t *testing.T) {
function TestACMEServerWithMismatchedChallenges (line 99) | func TestACMEServerWithMismatchedChallenges(t *testing.T) {
type naiveHTTPSolver (line 166) | type naiveHTTPSolver struct
method Present (line 171) | func (s *naiveHTTPSolver) Present(ctx context.Context, challenge acme....
method CleanUp (line 202) | func (s naiveHTTPSolver) CleanUp(ctx context.Context, challenge acme.C...
FILE: caddytest/integration/acmeserver_test.go
function TestACMEServerDirectory (line 20) | func TestACMEServerDirectory(t *testing.T) {
function TestACMEServerAllowPolicy (line 46) | func TestACMEServerAllowPolicy(t *testing.T) {
function TestACMEServerDenyPolicy (line 136) | func TestACMEServerDenyPolicy(t *testing.T) {
FILE: caddytest/integration/autohttps_test.go
function TestAutoHTTPtoHTTPSRedirectsImplicitPort (line 10) | func TestAutoHTTPtoHTTPSRedirectsImplicitPort(t *testing.T) {
function TestAutoHTTPtoHTTPSRedirectsExplicitPortSameAsHTTPSPort (line 26) | func TestAutoHTTPtoHTTPSRedirectsExplicitPortSameAsHTTPSPort(t *testing....
function TestAutoHTTPtoHTTPSRedirectsExplicitPortDifferentFromHTTPSPort (line 42) | func TestAutoHTTPtoHTTPSRedirectsExplicitPortDifferentFromHTTPSPort(t *t...
function TestAutoHTTPRedirectsWithHTTPListenerFirstInAddresses (line 58) | func TestAutoHTTPRedirectsWithHTTPListenerFirstInAddresses(t *testing.T) {
function TestAutoHTTPRedirectsInsertedBeforeUserDefinedCatchAll (line 100) | func TestAutoHTTPRedirectsInsertedBeforeUserDefinedCatchAll(t *testing.T) {
function TestAutoHTTPRedirectsInsertedBeforeUserDefinedCatchAllWithNoExplicitHTTPSite (line 125) | func TestAutoHTTPRedirectsInsertedBeforeUserDefinedCatchAllWithNoExplici...
function TestAutoHTTPSRedirectSortingExactMatchOverWildcard (line 147) | func TestAutoHTTPSRedirectSortingExactMatchOverWildcard(t *testing.T) {
FILE: caddytest/integration/caddyfile_adapt_test.go
function TestCaddyfileAdaptToJSON (line 17) | func TestCaddyfileAdaptToJSON(t *testing.T) {
FILE: caddytest/integration/caddyfile_test.go
function TestRespond (line 11) | func TestRespond(t *testing.T) {
function TestRedirect (line 33) | func TestRedirect(t *testing.T) {
function TestDuplicateHosts (line 61) | func TestDuplicateHosts(t *testing.T) {
function TestReadCookie (line 75) | func TestReadCookie(t *testing.T) {
function TestReplIndex (line 108) | func TestReplIndex(t *testing.T) {
function TestInvalidPrefix (line 134) | func TestInvalidPrefix(t *testing.T) {
function TestValidPrefix (line 179) | func TestValidPrefix(t *testing.T) {
function TestUriReplace (line 483) | func TestUriReplace(t *testing.T) {
function TestUriOps (line 500) | func TestUriOps(t *testing.T) {
function TestHttpRequestLocalPortPlaceholder (line 525) | func TestHttpRequestLocalPortPlaceholder(t *testing.T) {
function TestSetThenAddQueryParams (line 539) | func TestSetThenAddQueryParams(t *testing.T) {
function TestSetThenDeleteParams (line 556) | func TestSetThenDeleteParams(t *testing.T) {
function TestRenameAndOtherOps (line 573) | func TestRenameAndOtherOps(t *testing.T) {
function TestReplaceOps (line 591) | func TestReplaceOps(t *testing.T) {
function TestReplaceWithReplacementPlaceholder (line 606) | func TestReplaceWithReplacementPlaceholder(t *testing.T) {
function TestReplaceWithKeyPlaceholder (line 620) | func TestReplaceWithKeyPlaceholder(t *testing.T) {
function TestPartialReplacement (line 634) | func TestPartialReplacement(t *testing.T) {
function TestNonExistingSearch (line 648) | func TestNonExistingSearch(t *testing.T) {
function TestReplaceAllOps (line 662) | func TestReplaceAllOps(t *testing.T) {
function TestUriOpsBlock (line 677) | func TestUriOpsBlock(t *testing.T) {
function TestHandleErrorSimpleCodes (line 696) | func TestHandleErrorSimpleCodes(t *testing.T) {
function TestHandleErrorRange (line 716) | func TestHandleErrorRange(t *testing.T) {
function TestHandleErrorSort (line 736) | func TestHandleErrorSort(t *testing.T) {
function TestHandleErrorRangeAndCodes (line 760) | func TestHandleErrorRangeAndCodes(t *testing.T) {
function TestHandleErrorSubHandlers (line 785) | func TestHandleErrorSubHandlers(t *testing.T) {
function TestInvalidSiteAddressesAsDirectives (line 825) | func TestInvalidSiteAddressesAsDirectives(t *testing.T) {
FILE: caddytest/integration/forwardauth_test.go
function TestForwardAuthCopyHeadersStripsClientHeaders (line 37) | func TestForwardAuthCopyHeadersStripsClientHeaders(t *testing.T) {
function TestForwardAuthCopyHeadersAuthResponseWins (line 135) | func TestForwardAuthCopyHeadersAuthResponseWins(t *testing.T) {
FILE: caddytest/integration/h2listener_test.go
function newH2ListenerWithVersionsWithTLSTester (line 13) | func newH2ListenerWithVersionsWithTLSTester(t *testing.T, serverVersions...
function TestH2ListenerWithTLS (line 45) | func TestH2ListenerWithTLS(t *testing.T) {
function newH2ListenerWithVersionsWithoutTLSTester (line 73) | func newH2ListenerWithVersionsWithoutTLSTester(t *testing.T, serverVersi...
function TestH2ListenerWithoutTLS (line 103) | func TestH2ListenerWithoutTLS(t *testing.T) {
FILE: caddytest/integration/handler_test.go
function TestBrowse (line 11) | func TestBrowse(t *testing.T) {
function TestRespondWithJSON (line 34) | func TestRespondWithJSON(t *testing.T) {
FILE: caddytest/integration/intercept_test.go
function TestIntercept (line 9) | func TestIntercept(t *testing.T) {
FILE: caddytest/integration/leafcertloaders_test.go
function TestLeafCertLoaders (line 9) | func TestLeafCertLoaders(t *testing.T) {
FILE: caddytest/integration/listener_test.go
function setupListenerWrapperTest (line 15) | func setupListenerWrapperTest(t *testing.T, handlerFunc http.HandlerFunc...
function TestHTTPRedirectWrapperWithLargeUpload (line 53) | func TestHTTPRedirectWrapperWithLargeUpload(t *testing.T) {
function TestLargeHttpRequest (line 82) | func TestLargeHttpRequest(t *testing.T) {
FILE: caddytest/integration/map_test.go
function TestMap (line 10) | func TestMap(t *testing.T) {
function TestMapRespondWithDefault (line 40) | func TestMapRespondWithDefault(t *testing.T) {
function TestMapAsJSON (line 68) | func TestMapAsJSON(t *testing.T) {
FILE: caddytest/integration/mockdns_test.go
function init (line 13) | func init() {
type MockDNSProvider (line 18) | type MockDNSProvider struct
method CaddyModule (line 23) | func (MockDNSProvider) CaddyModule() caddy.ModuleInfo {
method Provision (line 31) | func (MockDNSProvider) Provision(ctx caddy.Context) error {
method UnmarshalCaddyfile (line 36) | func (p *MockDNSProvider) UnmarshalCaddyfile(d *caddyfile.Dispenser) e...
method AppendRecords (line 49) | func (MockDNSProvider) AppendRecords(ctx context.Context, zone string,...
method DeleteRecords (line 54) | func (MockDNSProvider) DeleteRecords(ctx context.Context, zone string,...
method GetRecords (line 59) | func (MockDNSProvider) GetRecords(ctx context.Context, zone string) ([...
method SetRecords (line 64) | func (MockDNSProvider) SetRecords(ctx context.Context, zone string, re...
FILE: caddytest/integration/pki_test.go
function TestLeafCertLifetimeLessThanIntermediate (line 9) | func TestLeafCertLifetimeLessThanIntermediate(t *testing.T) {
function TestIntermediateLifetimeLessThanRoot (line 59) | func TestIntermediateLifetimeLessThanRoot(t *testing.T) {
FILE: caddytest/integration/proxyprotocol_test.go
type proxyProtoBackend (line 67) | type proxyProtoBackend struct
method addr (line 130) | func (b *proxyProtoBackend) addr() string {
method recordedAddrs (line 136) | func (b *proxyProtoBackend) recordedAddrs() []string {
function newProxyProtoBackend (line 79) | func newProxyProtoBackend(t *testing.T) *proxyProtoBackend {
type tlsProxyProtoBackend (line 149) | type tlsProxyProtoBackend struct
method addr (line 206) | func (b *tlsProxyProtoBackend) addr() string {
method tlsConfig (line 212) | func (b *tlsProxyProtoBackend) tlsConfig() *tls.Config {
method recordedAddrs (line 217) | func (b *tlsProxyProtoBackend) recordedAddrs() []string {
function newTLSProxyProtoBackend (line 163) | func newTLSProxyProtoBackend(t *testing.T) *tlsProxyProtoBackend {
function proxyProtoTLSConfig (line 228) | func proxyProtoTLSConfig(listenPort int, backendAddr, ppVersion string, ...
function testTLSProxyProtocolMatrix (line 277) | func testTLSProxyProtocolMatrix(t *testing.T, ppVersion string, transpor...
function proxyProtoConfig (line 324) | func proxyProtoConfig(listenPort int, backendAddr, ppVersion string, tra...
function freePort (line 369) | func freePort(t *testing.T) int {
function TestProxyProtocolV1WithH1 (line 382) | func TestProxyProtocolV1WithH1(t *testing.T) {
function TestProxyProtocolV2WithH1 (line 388) | func TestProxyProtocolV2WithH1(t *testing.T) {
function TestProxyProtocolV1WithH2C (line 394) | func TestProxyProtocolV1WithH2C(t *testing.T) {
function TestProxyProtocolV2WithH2C (line 404) | func TestProxyProtocolV2WithH2C(t *testing.T) {
function TestProxyProtocolV2WithH2CMultipleRequests (line 415) | func TestProxyProtocolV2WithH2CMultipleRequests(t *testing.T) {
function TestProxyProtocolV1WithH2 (line 421) | func TestProxyProtocolV1WithH2(t *testing.T) {
function TestProxyProtocolV2WithH2 (line 427) | func TestProxyProtocolV2WithH2(t *testing.T) {
function TestProxyProtocolServerAndProxy (line 433) | func TestProxyProtocolServerAndProxy(t *testing.T) {
function testProxyProtocolMatrix (line 475) | func testProxyProtocolMatrix(t *testing.T, ppVersion string, transportVe...
function TestProxyProtocolListenerWrapper (line 544) | func TestProxyProtocolListenerWrapper(t *testing.T) {
FILE: caddytest/integration/reverseproxy_test.go
function TestSRVReverseProxy (line 15) | func TestSRVReverseProxy(t *testing.T) {
function TestDialWithPlaceholderUnix (line 58) | func TestDialWithPlaceholderUnix(t *testing.T) {
function TestReverseProxyWithPlaceholderDialAddress (line 140) | func TestReverseProxyWithPlaceholderDialAddress(t *testing.T) {
function TestReverseProxyWithPlaceholderTCPDialAddress (line 234) | func TestReverseProxyWithPlaceholderTCPDialAddress(t *testing.T) {
function TestReverseProxyHealthCheck (line 328) | func TestReverseProxyHealthCheck(t *testing.T) {
function TestReverseProxyHealthCheckPortUsed (line 392) | func TestReverseProxyHealthCheckPortUsed(t *testing.T) {
function TestReverseProxyHealthCheckUnixSocket (line 451) | func TestReverseProxyHealthCheckUnixSocket(t *testing.T) {
function TestReverseProxyHealthCheckUnixSocketWithoutPort (line 509) | func TestReverseProxyHealthCheckUnixSocketWithoutPort(t *testing.T) {
FILE: caddytest/integration/sni_test.go
function TestDefaultSNI (line 9) | func TestDefaultSNI(t *testing.T) {
function TestDefaultSNIWithNamedHostAndExplicitIP (line 108) | func TestDefaultSNIWithNamedHostAndExplicitIP(t *testing.T) {
function TestDefaultSNIWithPortMappingOnly (line 212) | func TestDefaultSNIWithPortMappingOnly(t *testing.T) {
function TestHttpOnlyOnDomainWithSNI (line 288) | func TestHttpOnlyOnDomainWithSNI(t *testing.T) {
FILE: caddytest/integration/stream_test.go
function TestH2ToH2CStream (line 23) | func TestH2ToH2CStream(t *testing.T) {
function testH2ToH2CStreamServeH2C (line 153) | func testH2ToH2CStreamServeH2C(t *testing.T) *http.Server {
function TestH2ToH1ChunkedResponse (line 207) | func TestH2ToH1ChunkedResponse(t *testing.T) {
function testH2ToH1ChunkedResponseServeH1 (line 362) | func testH2ToH1ChunkedResponseServeH1(t *testing.T) *http.Server {
function GenerateRandomBytes (line 415) | func GenerateRandomBytes(n int) ([]byte, error) {
function GenerateRandomString (line 430) | func GenerateRandomString(n int) (string, error) {
FILE: cmd/caddy/main.go
function main (line 40) | func main() {
FILE: cmd/cobra.go
constant fullDocsFooter (line 117) | fullDocsFooter = `Full documentation is available at:
function init (line 120) | func init() {
function onlyVersionText (line 127) | func onlyVersionText() string {
function caddyCmdToCobra (line 132) | func caddyCmdToCobra(caddyCmd Command) *cobra.Command {
function WrapCommandFuncForCobra (line 149) | func WrapCommandFuncForCobra(f CommandFunc) func(cmd *cobra.Command, _ [...
type exitError (line 161) | type exitError struct
method Error (line 166) | func (e *exitError) Error() string {
FILE: cmd/commandfactory.go
type rootCommandFactory (line 7) | type rootCommandFactory struct
method Use (line 18) | func (f *rootCommandFactory) Use(fn func(cmd *cobra.Command)) {
method Build (line 22) | func (f *rootCommandFactory) Build() *cobra.Command {
function newRootCommandFactory (line 12) | func newRootCommandFactory(fn func() *cobra.Command) *rootCommandFactory {
FILE: cmd/commandfuncs.go
function cmdStart (line 45) | func cmdStart(fl Flags) (int, error) {
function cmdRun (line 172) | func cmdRun(fl Flags) (int, error) {
function cmdStop (line 340) | func cmdStop(fl Flags) (int, error) {
function cmdReload (line 360) | func cmdReload(fl Flags) (int, error) {
function cmdVersion (line 399) | func cmdVersion(_ Flags) (int, error) {
function cmdBuildInfo (line 405) | func cmdBuildInfo(_ Flags) (int, error) {
type jsonModuleInfo (line 415) | type jsonModuleInfo struct
function cmdListModules (line 422) | func cmdListModules(fl Flags) (int, error) {
function cmdEnviron (line 525) | func cmdEnviron(fl Flags) (int, error) {
function cmdAdaptConfig (line 536) | func cmdAdaptConfig(fl Flags) (int, error) {
function cmdValidateConfig (line 627) | func cmdValidateConfig(fl Flags) (int, error) {
function cmdFmt (line 669) | func cmdFmt(fl Flags) (int, error) {
function handleEnvFileFlag (line 736) | func handleEnvFileFlag(fl Flags) error {
function AdminAPIRequest (line 758) | func AdminAPIRequest(adminAddr, method, uri string, headers http.Header,...
function DetermineAdminAPIAddress (line 846) | func DetermineAdminAPIAddress(address string, config []byte, configFile,...
function configFileWithRespectToDefault (line 892) | func configFileWithRespectToDefault(logger *zap.Logger, configFile strin...
type moduleInfo (line 915) | type moduleInfo struct
FILE: cmd/commands.go
type Command (line 33) | type Command struct
type CommandFunc (line 79) | type CommandFunc
function Commands (line 83) | func Commands() map[string]Command {
function init (line 95) | func init() {
function RegisterCommand (line 578) | func RegisterCommand(cmd Command) {
FILE: cmd/commands_test.go
function TestCommandsAreAvailable (line 10) | func TestCommandsAreAvailable(t *testing.T) {
FILE: cmd/main.go
function init (line 48) | func init() {
function Main (line 66) | func Main() {
function handlePingbackConn (line 83) | func handlePingbackConn(conn net.Conn, expect []byte) error {
function LoadConfig (line 108) | func LoadConfig(configFile, adapterName string) ([]byte, string, string,...
function isCaddyfile (line 112) | func isCaddyfile(configFile, adapterName string) (bool, error) {
function loadConfigWithLogger (line 146) | func loadConfigWithLogger(logger *zap.Logger, configFile, adapterName st...
function watchConfigFile (line 251) | func watchConfigFile(filename, adapterName string) {
type Flags (line 305) | type Flags struct
method String (line 312) | func (f Flags) String(name string) string {
method Bool (line 320) | func (f Flags) Bool(name string) bool {
method Int (line 329) | func (f Flags) Int(name string) int {
method Float64 (line 338) | func (f Flags) Float64(name string) float64 {
method Duration (line 347) | func (f Flags) Duration(name string) time.Duration {
function loadEnvFromFile (line 352) | func loadEnvFromFile(envFile string) error {
function parseEnvFile (line 385) | func parseEnvFile(envInput io.Reader) (map[string]string, error) {
function printEnvironment (line 452) | func printEnvironment() {
function setResourceLimits (line 475) | func setResourceLimits(logger *zap.Logger) func() {
type StringSlice (line 507) | type StringSlice
method String (line 509) | func (ss StringSlice) String() string { return "[" + strings.Join(ss, ...
method Set (line 511) | func (ss *StringSlice) Set(value string) error {
FILE: cmd/main_test.go
function TestParseEnvFile (line 9) | func TestParseEnvFile(t *testing.T) {
function Test_isCaddyfile (line 172) | func Test_isCaddyfile(t *testing.T) {
FILE: cmd/packagesfuncs.go
function cmdUpgrade (line 36) | func cmdUpgrade(fl Flags) (int, error) {
function splitModule (line 49) | func splitModule(arg string) (module, version string, err error) {
function cmdAddPackage (line 68) | func cmdAddPackage(fl Flags) (int, error) {
function cmdRemovePackage (line 96) | func cmdRemovePackage(fl Flags) (int, error) {
function upgradeBuild (line 124) | func upgradeBuild(pluginPkgs map[string]pluginPackage, fl Flags) (int, e...
function getModules (line 216) | func getModules() (standard, nonstandard, unknown []moduleInfo, err erro...
function listModules (line 266) | func listModules(path string) error {
function showVersion (line 273) | func showVersion(path string) error {
function downloadBuild (line 280) | func downloadBuild(qs url.Values) (*http.Response, error) {
function getPluginPackages (line 307) | func getPluginPackages(modules []moduleInfo) (map[string]pluginPackage, ...
function writeCaddyBinary (line 319) | func writeCaddyBinary(path string, body *io.ReadCloser, fileInfo os.File...
constant downloadPath (line 342) | downloadPath = "https://caddyserver.com/api/download"
type pluginPackage (line 344) | type pluginPackage struct
method String (line 349) | func (p pluginPackage) String() string {
FILE: cmd/removebinary.go
function removeCaddyBinary (line 27) | func removeCaddyBinary(path string) error {
FILE: cmd/removebinary_windows.go
function removeCaddyBinary (line 31) | func removeCaddyBinary(path string) error {
FILE: cmd/storagefuncs.go
type storVal (line 32) | type storVal struct
function determineStorage (line 38) | func determineStorage(configFile string, configAdapter string) (*storVal...
function cmdImportStorage (line 64) | func cmdImportStorage(fl Flags) (int, error) {
function cmdExportStorage (line 136) | func cmdExportStorage(fl Flags) (int, error) {
FILE: context.go
type Context (line 46) | type Context struct
method OnCancel (line 99) | func (ctx *Context) OnCancel(f func()) {
method FileSystems (line 105) | func (ctx *Context) FileSystems() FileSystems {
method GetMetricsRegistry (line 115) | func (ctx *Context) GetMetricsRegistry() *prometheus.Registry {
method initMetrics (line 119) | func (ctx *Context) initMetrics() {
method OnExit (line 136) | func (ctx *Context) OnExit(f func(context.Context)) {
method LoadModule (line 188) | func (ctx Context) LoadModule(structPointer any, fieldName string) (an...
method emitEvent (line 290) | func (ctx Context) emitEvent(name string, data map[string]any) Event {
method loadModulesFromSomeMap (line 300) | func (ctx Context) loadModulesFromSomeMap(namespace, inlineModuleKey s...
method loadModulesFromRegularMap (line 318) | func (ctx Context) loadModulesFromRegularMap(namespace, inlineModuleKe...
method loadModuleMap (line 335) | func (ctx Context) loadModuleMap(namespace string, val reflect.Value) ...
method LoadModuleByID (line 364) | func (ctx Context) LoadModuleByID(id string, rawMsg json.RawMessage) (...
method loadModuleInline (line 478) | func (ctx Context) loadModuleInline(moduleNameKey, moduleScope string,...
method App (line 505) | func (ctx Context) App(name string) (any, error) {
method AppIfConfigured (line 530) | func (ctx Context) AppIfConfigured(name string) (any, error) {
method Storage (line 552) | func (ctx Context) Storage() certmagic.Storage {
method Logger (line 573) | func (ctx Context) Logger(module ...Module) *zap.Logger {
method Slogger (line 612) | func (ctx Context) Slogger() *slog.Logger {
method Modules (line 655) | func (ctx Context) Modules() []Module {
method Module (line 663) | func (ctx Context) Module() Module {
method WithValue (line 671) | func (ctx *Context) WithValue(key, value any) Context {
function NewContext (line 65) | func NewContext(ctx Context) (Context, context.CancelFunc) {
function NewContextWithCause (line 72) | func NewContextWithCause(ctx Context) (Context, context.CancelCauseFunc) {
type slogHandlerFactory (line 595) | type slogHandlerFactory
function RegisterSlogHandlerFactory (line 604) | func RegisterSlogHandlerFactory(factory slogHandlerFactory) {
type eventEmitter (line 686) | type eventEmitter interface
FILE: context_test.go
function ExampleContext_LoadModule (line 22) | func ExampleContext_LoadModule() {
function ExampleContext_LoadModule_array (line 54) | func ExampleContext_LoadModule_array() {
function ExampleContext_LoadModule_map (line 87) | func ExampleContext_LoadModule_map() {
FILE: duration_fuzz.go
function FuzzParseDuration (line 19) | func FuzzParseDuration(data []byte) int {
FILE: filepath.go
function FastAbs (line 29) | func FastAbs(path string) (string, error) {
FILE: filepath_windows.go
function FastAbs (line 25) | func FastAbs(path string) (string, error) {
FILE: filesystem.go
type FileSystems (line 19) | type FileSystems interface
FILE: internal/filesystems/map.go
constant DefaultFileSystemKey (line 10) | DefaultFileSystemKey = "default"
type wrapperFs (line 16) | type wrapperFs struct
type FileSystemMap (line 24) | type FileSystemMap struct
method key (line 29) | func (f *FileSystemMap) key(k string) string {
method Register (line 38) | func (f *FileSystemMap) Register(k string, v fs.FS) {
method Unregister (line 50) | func (f *FileSystemMap) Unregister(k string) {
method Get (line 60) | func (f *FileSystemMap) Get(k string) (v fs.FS, ok bool) {
method Default (line 74) | func (f *FileSystemMap) Default() fs.FS {
FILE: internal/filesystems/os.go
type OsFS (line 16) | type OsFS struct
method Open (line 18) | func (OsFS) Open(name string) (fs.File, error) { return os.Op...
method Stat (line 19) | func (OsFS) Stat(name string) (fs.FileInfo, error) { return os.St...
method Glob (line 20) | func (OsFS) Glob(pattern string) ([]string, error) { return filep...
method ReadDir (line 21) | func (OsFS) ReadDir(name string) ([]fs.DirEntry, error) { return os.Re...
method ReadFile (line 22) | func (OsFS) ReadFile(name string) ([]byte, error) { return os.Re...
FILE: internal/logbuffer.go
type LogBufferCore (line 25) | type LogBufferCore struct
method Enabled (line 43) | func (c *LogBufferCore) Enabled(lvl zapcore.Level) bool {
method With (line 47) | func (c *LogBufferCore) With(fields []zapcore.Field) zapcore.Core {
method Check (line 51) | func (c *LogBufferCore) Check(entry zapcore.Entry, ce *zapcore.Checked...
method Write (line 58) | func (c *LogBufferCore) Write(entry zapcore.Entry, fields []zapcore.Fi...
method Sync (line 66) | func (c *LogBufferCore) Sync() error { return nil }
method FlushTo (line 69) | func (c *LogBufferCore) FlushTo(logger *zap.Logger) {
type LogBufferCoreInterface (line 32) | type LogBufferCoreInterface interface
function NewLogBufferCore (line 37) | func NewLogBufferCore(level zapcore.LevelEnabler) *LogBufferCore {
FILE: internal/logs.go
function MaxSizeSubjectsListForLog (line 9) | func MaxSizeSubjectsListForLog(subjects map[string]struct{}, maxToDispla...
FILE: internal/metrics/metrics.go
function SanitizeCode (line 8) | func SanitizeCode(s int) string {
function SanitizeMethod (line 33) | func SanitizeMethod(m string) string {
FILE: internal/metrics/metrics_test.go
function TestSanitizeMethod (line 8) | func TestSanitizeMethod(t *testing.T) {
FILE: internal/ranges.go
function PrivateRangesCIDR (line 5) | func PrivateRangesCIDR() []string {
FILE: internal/sockets.go
function SplitUnixSocketPermissionsBits (line 34) | func SplitUnixSocketPermissionsBits(addr string) (path string, fileMode ...
FILE: internal/testmocks/dummyverifier.go
function init (line 11) | func init() {
type dummyVerifier (line 15) | type dummyVerifier struct
method UnmarshalCaddyfile (line 18) | func (dummyVerifier) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
method CaddyModule (line 23) | func (dummyVerifier) CaddyModule() caddy.ModuleInfo {
method VerifyClientCertificate (line 33) | func (dummyVerifier) VerifyClientCertificate(rawCerts [][]byte, verifi...
FILE: listen.go
function reuseUnixSocket (line 33) | func reuseUnixSocket(_, _ string) (any, error) {
function listenReusable (line 37) | func listenReusable(ctx context.Context, lnKey string, network, address ...
type fakeCloseListener (line 122) | type fakeCloseListener struct
method Accept (line 132) | func (fcl *fakeCloseListener) Accept() (net.Conn, error) {
method Close (line 177) | func (fcl *fakeCloseListener) Close() error {
type canSetKeepAliveConfig (line 128) | type canSetKeepAliveConfig interface
type sharedListener (line 196) | type sharedListener struct
method clearDeadline (line 203) | func (sl *sharedListener) clearDeadline() error {
method setDeadline (line 217) | func (sl *sharedListener) setDeadline() error {
method Destruct (line 234) | func (sl *sharedListener) Destruct() error {
type fakeClosePacketConn (line 240) | type fakeClosePacketConn struct
method ReadFrom (line 245) | func (fcpc *fakeClosePacketConn) ReadFrom(p []byte) (n int, addr net.A...
method Close (line 275) | func (fcpc *fakeClosePacketConn) Close() error {
method Unwrap (line 283) | func (fcpc *fakeClosePacketConn) Unwrap() net.PacketConn {
type sharedPacketConn (line 288) | type sharedPacketConn struct
method Destruct (line 294) | func (spc *sharedPacketConn) Destruct() error {
method Unwrap (line 299) | func (spc *sharedPacketConn) Unwrap() net.PacketConn {
FILE: listen_unix.go
function reuseUnixSocket (line 43) | func reuseUnixSocket(network, addr string) (any, error) {
function listenReusable (line 94) | func listenReusable(ctx context.Context, lnKey string, network, address ...
function reusePort (line 201) | func reusePort(network, address string, conn syscall.RawConn) error {
type unixListener (line 216) | type unixListener struct
method Close (line 222) | func (uln *unixListener) Close() error {
type unixConn (line 242) | type unixConn struct
method Close (line 248) | func (uc *unixConn) Close() error {
method Unwrap (line 268) | func (uc *unixConn) Unwrap() net.PacketConn {
type deleteListener (line 288) | type deleteListener struct
method Close (line 293) | func (dl deleteListener) Close() error {
type deletePacketConn (line 300) | type deletePacketConn struct
method Close (line 305) | func (dl deletePacketConn) Close() error {
method Unwrap (line 310) | func (dl deletePacketConn) Unwrap() net.PacketConn {
FILE: listen_unix_setopt.go
constant unixSOREUSEPORT (line 7) | unixSOREUSEPORT = unix.SO_REUSEPORT
FILE: listen_unix_setopt_freebsd.go
constant unixSOREUSEPORT (line 7) | unixSOREUSEPORT = unix.SO_REUSEPORT_LB
FILE: listeners.go
type NetworkAddress (line 44) | type NetworkAddress struct
method ListenAll (line 64) | func (na NetworkAddress) ListenAll(ctx context.Context, config net.Lis...
method Listen (line 135) | func (na NetworkAddress) Listen(ctx context.Context, portOffset uint, ...
method listen (line 150) | func (na NetworkAddress) listen(ctx context.Context, portOffset uint, ...
method IsUnixNetwork (line 209) | func (na NetworkAddress) IsUnixNetwork() bool {
method IsFdNetwork (line 215) | func (na NetworkAddress) IsFdNetwork() bool {
method JoinHostPort (line 221) | func (na NetworkAddress) JoinHostPort(offset uint) string {
method Expand (line 229) | func (na NetworkAddress) Expand() []NetworkAddress {
method At (line 241) | func (na NetworkAddress) At(portOffset uint) NetworkAddress {
method PortRangeSize (line 251) | func (na NetworkAddress) PortRangeSize() uint {
method isLoopback (line 258) | func (na NetworkAddress) isLoopback() bool {
method isWildcardInterface (line 271) | func (na NetworkAddress) isWildcardInterface() bool {
method port (line 281) | func (na NetworkAddress) port() string {
method String (line 291) | func (na NetworkAddress) String() string {
method ListenQUIC (line 434) | func (na NetworkAddress) ListenQUIC(ctx context.Context, portOffset ui...
function IsUnixNetwork (line 299) | func IsUnixNetwork(netw string) bool {
function IsFdNetwork (line 304) | func IsFdNetwork(netw string) bool {
function ParseNetworkAddress (line 316) | func ParseNetworkAddress(addr string) (NetworkAddress, error) {
function ParseNetworkAddressWithDefaults (line 322) | func ParseNetworkAddressWithDefaults(addr, defaultNetwork string, defaul...
function SplitNetworkAddress (line 378) | func SplitNetworkAddress(a string) (network, host, port string, err erro...
function JoinNetworkAddress (line 413) | func JoinNetworkAddress(network, host, port string) string {
function ListenerUsage (line 507) | func ListenerUsage(network, addr string) int {
type contextAndCancelFunc (line 513) | type contextAndCancelFunc struct
type sharedQUICState (line 520) | type sharedQUICState struct
method getConfigForClient (line 537) | func (sqs *sharedQUICState) getConfigForClient(ch *tls.ClientHelloInfo...
method addState (line 545) | func (sqs *sharedQUICState) addState(tlsConfig *tls.Config) (context.C...
function newSharedQUICState (line 527) | func newSharedQUICState(tlsConfig *tls.Config) *sharedQUICState {
type sharedQuicListener (line 579) | type sharedQuicListener struct
method Destruct (line 587) | func (sql *sharedQuicListener) Destruct() error {
function fakeClosedErr (line 597) | func fakeClosedErr(l interface{ Addr() net.Addr }) error {
type fakeCloseQuicListener (line 613) | type fakeCloseQuicListener struct
method Accept (line 625) | func (fcql *fakeCloseQuicListener) Accept(_ context.Context) (*quic.Co...
method Close (line 638) | func (fcql *fakeCloseQuicListener) Close() error {
function RegisterNetwork (line 651) | func RegisterNetwork(network string, getListener ListenerFunc) {
function getListenerFromPlugin (line 674) | func getListenerFromPlugin(ctx context.Context, network, host, port stri...
function listenerKey (line 684) | func listenerKey(network, addr string) string {
type ListenerFunc (line 692) | type ListenerFunc
type ListenerWrapper (line 705) | type ListenerWrapper interface
type PacketConnWrapper (line 718) | type PacketConnWrapper interface
constant maxPortSpan (line 725) | maxPortSpan = 65535
FILE: listeners_fuzz.go
function FuzzParseNetworkAddress (line 19) | func FuzzParseNetworkAddress(data []byte) int {
FILE: listeners_test.go
function TestSplitNetworkAddress (line 24) | func TestSplitNetworkAddress(t *testing.T) {
function TestJoinNetworkAddress (line 117) | func TestJoinNetworkAddress(t *testing.T) {
function TestParseNetworkAddress (line 178) | func TestParseNetworkAddress(t *testing.T) {
function TestParseNetworkAddressWithDefaults (line 304) | func TestParseNetworkAddressWithDefaults(t *testing.T) {
function TestJoinHostPort (line 430) | func TestJoinHostPort(t *testing.T) {
function TestExpand (line 479) | func TestExpand(t *testing.T) {
function TestSplitUnixSocketPermissionsBits (line 561) | func TestSplitUnixSocketPermissionsBits(t *testing.T) {
FILE: logging.go
function init (line 35) | func init() {
type Logging (line 62) | type Logging struct
method openLogs (line 86) | func (logging *Logging) openLogs(ctx Context) error {
method setupNewDefault (line 137) | func (logging *Logging) setupNewDefault(ctx Context) error {
method closeLogs (line 208) | func (logging *Logging) closeLogs() error {
method Logger (line 219) | func (logging *Logging) Logger(mod Module) *zap.Logger {
method openWriter (line 250) | func (logging *Logging) openWriter(opener WriterOpener) (io.WriteClose...
type WriterOpener (line 267) | type WriterOpener interface
function IsWriterStandardStream (line 282) | func IsWriterStandardStream(wo WriterOpener) bool {
type writerDestructor (line 291) | type writerDestructor struct
method Destruct (line 295) | func (wdest writerDestructor) Destruct() error {
type BaseLog (line 300) | type BaseLog struct
method provisionCommon (line 342) | func (cl *BaseLog) provisionCommon(ctx Context, logging *Logging) error {
method buildCore (line 396) | func (cl *BaseLog) buildCore() {
method buildOptions (line 425) | func (cl *BaseLog) buildOptions() ([]zap.Option, error) {
type SinkLog (line 448) | type SinkLog struct
method provision (line 452) | func (sll *SinkLog) provision(ctx Context, logging *Logging) error {
type CustomLog (line 476) | type CustomLog struct
method provision (line 490) | func (cl *CustomLog) provision(ctx Context, logging *Logging) error {
method matchesModule (line 525) | func (cl *CustomLog) matchesModule(moduleID string) bool {
method loggerAllowed (line 533) | func (cl *CustomLog) loggerAllowed(name string, isModule bool) bool {
type filteringCore (line 596) | type filteringCore struct
method With (line 602) | func (fc *filteringCore) With(fields []zapcore.Field) zapcore.Core {
method Check (line 611) | func (fc *filteringCore) Check(e zapcore.Entry, ce *zapcore.CheckedEnt...
type LogSampling (line 619) | type LogSampling struct
type StdoutWriter (line 635) | type StdoutWriter struct
method CaddyModule (line 645) | func (StdoutWriter) CaddyModule() ModuleInfo {
method String (line 668) | func (StdoutWriter) String() string { return "stdout" }
method WriterKey (line 673) | func (StdoutWriter) WriterKey() string { return "std:out" }
method OpenWriter (line 682) | func (StdoutWriter) OpenWriter() (io.WriteCloser, error) {
type StderrWriter (line 638) | type StderrWriter struct
method CaddyModule (line 653) | func (StderrWriter) CaddyModule() ModuleInfo {
method String (line 669) | func (StderrWriter) String() string { return "stderr" }
method WriterKey (line 676) | func (StderrWriter) WriterKey() string { return "std:err" }
method OpenWriter (line 687) | func (StderrWriter) OpenWriter() (io.WriteCloser, error) {
type DiscardWriter (line 641) | type DiscardWriter struct
method CaddyModule (line 661) | func (DiscardWriter) CaddyModule() ModuleInfo {
method String (line 670) | func (DiscardWriter) String() string { return "discard" }
method WriterKey (line 679) | func (DiscardWriter) WriterKey() string { return "discard" }
method OpenWriter (line 692) | func (DiscardWriter) OpenWriter() (io.WriteCloser, error) {
type notClosable (line 697) | type notClosable struct
method Close (line 699) | func (fc notClosable) Close() error { return nil }
type defaultCustomLog (line 701) | type defaultCustomLog struct
function newDefaultProductionLog (line 710) | func newDefaultProductionLog() (*defaultCustomLog, error) {
function newDefaultProductionLogEncoder (line 735) | func newDefaultProductionLogEncoder(wo WriterOpener) zapcore.Encoder {
function parseLevel (line 751) | func parseLevel(levelInput string) (zapcore.LevelEnabler, error) {
function Log (line 779) | func Log() *zap.Logger {
function BufferedLog (line 791) | func BufferedLog() (*zap.Logger, *zap.Logger, *internal.LogBufferCore) {
type ConfiguresFormatterDefault (line 813) | type ConfiguresFormatterDefault interface
constant DefaultLoggerName (line 817) | DefaultLoggerName = "default"
FILE: logging_test.go
function TestCustomLog_loggerAllowed (line 19) | func TestCustomLog_loggerAllowed(t *testing.T) {
FILE: metrics.go
function init (line 12) | func init() {
function instrumentHandlerCounter (line 53) | func instrumentHandlerCounter(counter *prometheus.CounterVec, next http....
function newDelegator (line 64) | func newDelegator(w http.ResponseWriter) *delegator {
type delegator (line 70) | type delegator struct
method WriteHeader (line 75) | func (d *delegator) WriteHeader(code int) {
method Unwrap (line 82) | func (d *delegator) Unwrap() http.ResponseWriter {
FILE: modules.go
type Module (line 54) | type Module interface
type ModuleInfo (line 63) | type ModuleInfo struct
method String (line 120) | func (mi ModuleInfo) String() string { return string(mi.ID) }
type ModuleID (line 98) | type ModuleID
method Namespace (line 103) | func (id ModuleID) Namespace() string {
method Name (line 112) | func (id ModuleID) Name() string {
type ModuleMap (line 128) | type ModuleMap
function RegisterModule (line 138) | func RegisterModule(instance Module) {
function GetModule (line 164) | func GetModule(name string) (ModuleInfo, error) {
function GetModuleName (line 177) | func GetModuleName(instance any) string {
function GetModuleID (line 187) | func GetModuleID(instance any) string {
function GetModules (line 204) | func GetModules(scope string) []ModuleInfo {
function Modules (line 246) | func Modules() []string {
function getModuleNameInline (line 263) | func getModuleNameInline(moduleNameKey string, raw json.RawMessage) (str...
type Provisioner (line 296) | type Provisioner interface
type Validator (line 305) | type Validator interface
type CleanerUpper (line 315) | type CleanerUpper interface
function ParseStructTag (line 322) | func ParseStructTag(tag string) (map[string]string, error) {
function StrictUnmarshalJSON (line 342) | func StrictUnmarshalJSON(data []byte, v any) error {
function isJSONRawMessage (line 355) | func isJSONRawMessage(typ reflect.Type) bool {
function isModuleMapType (line 365) | func isModuleMapType(typ reflect.Type) bool {
type ProxyFuncProducer (line 375) | type ProxyFuncProducer interface
FILE: modules/caddyevents/app.go
function init (line 29) | func init() {
type App (line 72) | type App struct
method CaddyModule (line 112) | func (App) CaddyModule() caddy.ModuleInfo {
method Provision (line 120) | func (app *App) Provision(ctx caddy.Context) error {
method Start (line 145) | func (app *App) Start() error {
method Stop (line 158) | func (app *App) Stop() error {
method Subscribe (line 166) | func (app *App) Subscribe(s *Subscription) error {
method On (line 194) | func (app *App) On(eventName string, handler Handler) error {
method Emit (line 207) | func (app *App) Emit(ctx caddy.Context, eventName string, data map[str...
type Subscription (line 87) | type Subscription struct
type Handler (line 327) | type Handler interface
FILE: modules/caddyevents/eventsconfig/caddyfile.go
function init (line 30) | func init() {
function parseApp (line 42) | func parseApp(d *caddyfile.Dispenser, _ any) (any, error) {
FILE: modules/caddyfs/filesystem.go
function init (line 16) | func init() {
type moduleEntry (line 21) | type moduleEntry struct
method UnmarshalCaddyfile (line 89) | func (f *moduleEntry) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
type Filesystems (line 28) | type Filesystems struct
method CaddyModule (line 50) | func (Filesystems) CaddyModule() caddy.ModuleInfo {
method Start (line 57) | func (xs *Filesystems) Start() error { return nil }
method Stop (line 58) | func (xs *Filesystems) Stop() error { return nil }
method Provision (line 60) | func (xs *Filesystems) Provision(ctx caddy.Context) error {
method Cleanup (line 82) | func (f *Filesystems) Cleanup() error {
function parseFilesystems (line 34) | func parseFilesystems(d *caddyfile.Dispenser, existingVal any) (any, err...
FILE: modules/caddyhttp/app.go
function init (line 38) | func init() {
type App (line 111) | type App struct
method CaddyModule (line 166) | func (App) CaddyModule() caddy.ModuleInfo {
method Provision (line 174) | func (app *App) Provision(ctx caddy.Context) error {
method Validate (line 416) | func (app *App) Validate() error {
method Start (line 470) | func (app *App) Start() error {
method Stop (line 679) | func (app *App) Stop() error {
method Cleanup (line 826) | func (app *App) Cleanup() error {
method httpPort (line 833) | func (app *App) httpPort() int {
method httpsPort (line 840) | func (app *App) httpsPort() int {
function removeTLSALPN (line 449) | func removeTLSALPN(srv *Server, target string) {
constant defaultIdleTimeout (line 852) | defaultIdleTimeout = caddy.Duration(5 * time.Minute)
constant defaultReadHeaderTimeout (line 859) | defaultReadHeaderTimeout = caddy.Duration(time.Minute)
FILE: modules/caddyhttp/autohttps.go
type AutoHTTPSConfig (line 36) | type AutoHTTPSConfig struct
method automaticHTTPSPhase1 (line 77) | func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Repl...
method makeRedirRoute (line 520) | func (app *App) makeRedirRoute(redirToPort uint, matcherSet MatcherSet) ...
method createAutomationPolicies (line 559) | func (app *App) createAutomationPolicies(ctx caddy.Context, internalName...
method fillInACMEIssuer (line 789) | func (app *App) fillInACMEIssuer(acmeIssuer *caddytls.ACMEIssuer) error {
method automaticHTTPSPhase2 (line 831) | func (app *App) automaticHTTPSPhase2() error {
function isTailscaleDomain (line 846) | func isTailscaleDomain(name string) bool {
type acmeCapable (line 850) | type acmeCapable interface
function getFirstHostFromRoute (line 855) | func getFirstHostFromRoute(r Route) string {
FILE: modules/caddyhttp/caddyauth/argon2id.go
function init (line 30) | func init() {
constant argon2idName (line 35) | argon2idName = "argon2id"
constant defaultArgon2idTime (line 36) | defaultArgon2idTime = 1
constant defaultArgon2idMemory (line 37) | defaultArgon2idMemory = 46 * 1024
constant defaultArgon2idThreads (line 38) | defaultArgon2idThreads = 1
constant defaultArgon2idKeylen (line 39) | defaultArgon2idKeylen = 32
constant defaultSaltLength (line 40) | defaultSaltLength = 16
type Argon2idHash (line 44) | type Argon2idHash struct
method CaddyModule (line 53) | func (Argon2idHash) CaddyModule() caddy.ModuleInfo {
method Compare (line 61) | func (Argon2idHash) Compare(hashed, plaintext []byte) (bool, error) {
method Hash (line 80) | func (b Argon2idHash) Hash(plaintext []byte) ([]byte, error) {
method FakeHash (line 170) | func (Argon2idHash) FakeHash() []byte {
function DecodeHash (line 112) | func DecodeHash(hash []byte) (*Argon2idHash, []byte, error) {
function generateSalt (line 182) | func generateSalt(length int) ([]byte, error) {
FILE: modules/caddyhttp/caddyauth/basicauth.go
function init (line 32) | func init() {
type HTTPBasicAuth (line 37) | type HTTPBasicAuth struct
method CaddyModule (line 71) | func (HTTPBasicAuth) CaddyModule() caddy.ModuleInfo {
method Provision (line 79) | func (hba *HTTPBasicAuth) Provision(ctx caddy.Context) error {
method Authenticate (line 143) | func (hba HTTPBasicAuth) Authenticate(w http.ResponseWriter, req *http...
method correctPassword (line 165) | func (hba HTTPBasicAuth) correctPassword(account Account, plaintextPas...
method promptForCredentials (line 207) | func (hba HTTPBasicAuth) promptForCredentials(w http.ResponseWriter, e...
type Cache (line 222) | type Cache struct
method makeRoom (line 233) | func (c *Cache) makeRoom() {
type Comparer (line 264) | type Comparer interface
type Hasher (line 278) | type Hasher interface
type Account (line 284) | type Account struct
FILE: modules/caddyhttp/caddyauth/bcrypt.go
function init (line 25) | func init() {
constant bcryptName (line 31) | bcryptName = "bcrypt"
constant defaultBcryptCost (line 32) | defaultBcryptCost = 14
type BcryptHash (line 36) | type BcryptHash struct
method CaddyModule (line 43) | func (BcryptHash) CaddyModule() caddy.ModuleInfo {
method Compare (line 51) | func (BcryptHash) Compare(hashed, plaintext []byte) (bool, error) {
method Hash (line 63) | func (b BcryptHash) Hash(plaintext []byte) ([]byte, error) {
method FakeHash (line 73) | func (BcryptHash) FakeHash() []byte {
FILE: modules/caddyhttp/caddyauth/caddyauth.go
function init (line 28) | func init() {
type Authentication (line 45) | type Authentication struct
method CaddyModule (line 56) | func (Authentication) CaddyModule() caddy.ModuleInfo {
method Provision (line 65) | func (a *Authentication) Provision(ctx caddy.Context) error {
method ServeHTTP (line 78) | func (a Authentication) ServeHTTP(w http.ResponseWriter, r *http.Reque...
type Authenticator (line 114) | type Authenticator interface
type User (line 119) | type User struct
FILE: modules/caddyhttp/caddyauth/caddyfile.go
function init (line 24) | func init() {
function parseCaddyfile (line 37) | func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler...
FILE: modules/caddyhttp/caddyauth/command.go
function init (line 32) | func init() {
function cmdHashPassword (line 89) | func cmdHashPassword(fs caddycmd.Flags) (int, error) {
FILE: modules/caddyhttp/caddyhttp.go
function init (line 32) | func init() {
type RequestMatcher (line 43) | type RequestMatcher interface
type RequestMatcherWithError (line 55) | type RequestMatcherWithError interface
type Handler (line 65) | type Handler interface
type HandlerFunc (line 70) | type HandlerFunc
method ServeHTTP (line 73) | func (f HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request)...
type Middleware (line 79) | type Middleware
type MiddlewareHandler (line 90) | type MiddlewareHandler interface
type ResponseHandler (line 126) | type ResponseHandler struct
method Provision (line 143) | func (rh *ResponseHandler) Provision(ctx caddy.Context) error {
type WeakString (line 162) | type WeakString
method UnmarshalJSON (line 166) | func (ws *WeakString) UnmarshalJSON(b []byte) error {
method MarshalJSON (line 188) | func (ws WeakString) MarshalJSON() ([]byte, error) {
method Int (line 203) | func (ws WeakString) Int() int {
method Float64 (line 210) | func (ws WeakString) Float64() float64 {
method Bool (line 217) | func (ws WeakString) Bool() bool {
method String (line 222) | func (ws WeakString) String() string {
function StatusCodeMatches (line 230) | func StatusCodeMatches(actual, configured int) bool {
function SanitizedPathJoin (line 252) | func SanitizedPathJoin(root, reqPath string) string {
function CleanPath (line 279) | func CleanPath(p string, collapseSlashes bool) string {
function cleanPath (line 302) | func cleanPath(p string) string {
type tlsPlaceholderWrapper (line 314) | type tlsPlaceholderWrapper struct
method CaddyModule (line 316) | func (tlsPlaceholderWrapper) CaddyModule() caddy.ModuleInfo {
method WrapListener (line 323) | func (tlsPlaceholderWrapper) WrapListener(ln net.Listener) net.Listene...
method UnmarshalCaddyfile (line 325) | func (tlsPlaceholderWrapper) UnmarshalCaddyfile(d *caddyfile.Dispenser...
constant DefaultHTTPPort (line 329) | DefaultHTTPPort = 80
constant DefaultHTTPSPort (line 332) | DefaultHTTPSPort = 443
constant separator (line 335) | separator = string(filepath.Separator)
FILE: modules/caddyhttp/caddyhttp_test.go
function TestSanitizedPathJoin (line 10) | func TestSanitizedPathJoin(t *testing.T) {
function TestCleanPath (line 150) | func TestCleanPath(t *testing.T) {
FILE: modules/caddyhttp/celmatcher.go
function init (line 45) | func init() {
type MatchExpression (line 61) | type MatchExpression struct
method CaddyModule (line 80) | func (MatchExpression) CaddyModule() caddy.ModuleInfo {
method MarshalJSON (line 88) | func (m MatchExpression) MarshalJSON() ([]byte, error) {
method UnmarshalJSON (line 105) | func (m *MatchExpression) UnmarshalJSON(data []byte) error {
method Provision (line 122) | func (m *MatchExpression) Provision(ctx caddy.Context) error {
method Match (line 204) | func (m MatchExpression) Match(r *http.Request) bool {
method MatchWithError (line 213) | func (m MatchExpression) MatchWithError(r *http.Request) (bool, error) {
method UnmarshalCaddyfile (line 227) | func (m *MatchExpression) UnmarshalCaddyfile(d *caddyfile.Dispenser) e...
method caddyPlaceholderFunc (line 259) | func (m MatchExpression) caddyPlaceholderFunc(lhs, rhs ref.Val) ref.Val {
type celHTTPRequest (line 290) | type celHTTPRequest struct
method ResolveName (line 292) | func (cr celHTTPRequest) ResolveName(name string) (any, bool) {
method Parent (line 299) | func (cr celHTTPRequest) Parent() interpreter.Activation {
method ConvertToNative (line 303) | func (cr celHTTPRequest) ConvertToNative(typeDesc reflect.Type) (any, ...
method ConvertToType (line 307) | func (celHTTPRequest) ConvertToType(typeVal ref.Type) ref.Val {
method Equal (line 311) | func (cr celHTTPRequest) Equal(other ref.Val) ref.Val {
method Type (line 317) | func (celHTTPRequest) Type() ref.Type { return httpRequestCELType }
method Value (line 318) | func (cr celHTTPRequest) Value() any { return cr }
type celPkixName (line 324) | type celPkixName struct
method ConvertToNative (line 326) | func (pn celPkixName) ConvertToNative(typeDesc reflect.Type) (any, err...
method ConvertToType (line 330) | func (pn celPkixName) ConvertToType(typeVal ref.Type) ref.Val {
method Equal (line 337) | func (pn celPkixName) Equal(other ref.Val) ref.Val {
method Type (line 343) | func (celPkixName) Type() ref.Type { return pkixNameCELType }
method Value (line 344) | func (pn celPkixName) Value() any { return pn }
type celTypeAdapter (line 347) | type celTypeAdapter struct
method NativeToValue (line 349) | func (celTypeAdapter) NativeToValue(value any) ref.Val {
type CELLibraryProducer (line 366) | type CELLibraryProducer interface
function CELMatcherImpl (line 391) | func CELMatcherImpl(macroName, funcName string, matcherDataTypes []*cel....
type matcherCELLibrary (line 444) | type matcherCELLibrary struct
method CompileOptions (line 457) | func (lib *matcherCELLibrary) CompileOptions() []cel.EnvOption {
method ProgramOptions (line 461) | func (lib *matcherCELLibrary) ProgramOptions() []cel.ProgramOption {
function NewMatcherCELLibrary (line 450) | func NewMatcherCELLibrary(envOptions []cel.EnvOption, programOptions []c...
function CELMatcherDecorator (line 469) | func CELMatcherDecorator(funcName string, fac any) interpreter.Interpret...
function CELMatcherRuntimeFunction (line 554) | func CELMatcherRuntimeFunction(funcName string, fac any) functions.Binar...
function celMatcherStringListMacroExpander (line 592) | func celMatcherStringListMacroExpander(funcName string) cel.MacroFactory {
function celMatcherStringMacroExpander (line 613) | func celMatcherStringMacroExpander(funcName string) parser.MacroExpander {
function celMatcherJSONMacroExpander (line 629) | func celMatcherJSONMacroExpander(funcName string) parser.MacroExpander {
function CELValueToMapStrList (line 669) | func CELValueToMapStrList(data ref.Val) (map[string][]string, error) {
function isCELStringExpr (line 733) | func isCELStringExpr(e ast.Expr) bool {
function isCELStringLiteral (line 738) | func isCELStringLiteral(e ast.Expr) bool {
function isCELCaddyPlaceholderCall (line 753) | func isCELCaddyPlaceholderCall(e ast.Expr) bool {
function isCELConcatCall (line 768) | func isCELConcatCall(e ast.Expr) bool {
function isCELStringListLiteral (line 792) | func isCELStringListLiteral(e ast.Expr) bool {
constant CELPlaceholderFuncName (line 828) | CELPlaceholderFuncName = "ph"
constant CELRequestVarName (line 831) | CELRequestVarName = "req"
constant MatcherNameCtxKey (line 833) | MatcherNameCtxKey = "matcher_name"
FILE: modules/caddyhttp/celmatcher_test.go
function TestMatchExpressionMatch (line 450) | func TestMatchExpressionMatch(t *testing.T) {
function BenchmarkMatchExpressionMatch (line 503) | func BenchmarkMatchExpressionMatch(b *testing.B) {
function TestMatchExpressionProvision (line 545) | func TestMatchExpressionProvision(t *testing.T) {
FILE: modules/caddyhttp/encode/brotli/brotli_precompressed.go
function init (line 8) | func init() {
type BrotliPrecompressed (line 13) | type BrotliPrecompressed struct
method CaddyModule (line 16) | func (BrotliPrecompressed) CaddyModule() caddy.ModuleInfo {
method AcceptEncoding (line 25) | func (BrotliPrecompressed) AcceptEncoding() string { return "br" }
method Suffix (line 28) | func (BrotliPrecompressed) Suffix() string { return ".br" }
FILE: modules/caddyhttp/encode/caddyfile.go
function init (line 27) | func init() {
function parseCaddyfile (line 31) | func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler...
method UnmarshalCaddyfile (line 56) | func (enc *Encode) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
FILE: modules/caddyhttp/encode/encode.go
function init (line 37) | func init() {
type Encode (line 42) | type Encode struct
method CaddyModule (line 61) | func (Encode) CaddyModule() caddy.ModuleInfo {
method Provision (line 69) | func (enc *Encode) Provision(ctx caddy.Context) error {
method Validate (line 134) | func (enc *Encode) Validate() error {
method ServeHTTP (line 154) | func (enc *Encode) ServeHTTP(w http.ResponseWriter, r *http.Request, n...
method addEncoding (line 193) | func (enc *Encode) addEncoding(e Encoding) error {
method openResponseWriter (line 215) | func (enc *Encode) openResponseWriter(encodingName string, w http.Resp...
method initResponseWriter (line 222) | func (enc *Encode) initResponseWriter(rw *responseWriter, encodingName...
method Match (line 278) | func (enc *Encode) Match(rw *responseWriter) bool {
function isEncodeAllowed (line 150) | func isEncodeAllowed(h http.Header) bool {
type responseWriter (line 238) | type responseWriter struct
method WriteHeader (line 251) | func (rw *responseWriter) WriteHeader(status int) {
method FlushError (line 284) | func (rw *responseWriter) FlushError() error {
method Write (line 313) | func (rw *responseWriter) Write(p []byte) (int, error) {
method ReadFrom (line 374) | func (rw *responseWriter) ReadFrom(r io.Reader) (int64, error) {
method Close (line 407) | func (rw *responseWriter) Close() error {
method Unwrap (line 433) | func (rw *responseWriter) Unwrap() http.ResponseWriter {
method init (line 438) | func (rw *responseWriter) init() {
type writerOnly (line 363) | type writerOnly struct
constant sniffLen (line 368) | sniffLen = 512
function hasVaryValue (line 472) | func hasVaryValue(hdr http.Header, target string) bool {
function AcceptedEncodings (line 490) | func AcceptedEncodings(r *http.Request, preferredOrder []string) []string {
type encodingPreference (line 557) | type encodingPreference struct
type Encoder (line 564) | type Encoder interface
type Encoding (line 572) | type Encoding interface
type Precompressed (line 579) | type Precompressed interface
constant defaultMinLength (line 585) | defaultMinLength = 512
FILE: modules/caddyhttp/encode/encode_test.go
function BenchmarkOpenResponseWriter (line 10) | func BenchmarkOpenResponseWriter(b *testing.B) {
function TestPreferOrder (line 17) | func TestPreferOrder(t *testing.T) {
function TestValidate (line 125) | func TestValidate(t *testing.T) {
function TestIsEncodeAllowed (line 252) | func TestIsEncodeAllowed(t *testing.T) {
FILE: modules/caddyhttp/encode/gzip/gzip.go
function init (line 28) | func init() {
type Gzip (line 33) | type Gzip struct
method CaddyModule (line 38) | func (Gzip) CaddyModule() caddy.ModuleInfo {
method UnmarshalCaddyfile (line 46) | func (g *Gzip) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
method Provision (line 61) | func (g *Gzip) Provision(ctx caddy.Context) error {
method Validate (line 69) | func (g Gzip) Validate() error {
method AcceptEncoding (line 81) | func (Gzip) AcceptEncoding() string { return "gzip" }
method NewEncoder (line 84) | func (g Gzip) NewEncoder() encode.Encoder {
FILE: modules/caddyhttp/encode/gzip/gzip_precompressed.go
function init (line 8) | func init() {
type GzipPrecompressed (line 13) | type GzipPrecompressed struct
method CaddyModule (line 18) | func (GzipPrecompressed) CaddyModule() caddy.ModuleInfo {
method Suffix (line 26) | func (GzipPrecompressed) Suffix() string { return ".gz" }
FILE: modules/caddyhttp/encode/zstd/zstd.go
function init (line 27) | func init() {
type Zstd (line 32) | type Zstd struct
method CaddyModule (line 41) | func (Zstd) CaddyModule() caddy.ModuleInfo {
method UnmarshalCaddyfile (line 49) | func (z *Zstd) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
method Provision (line 68) | func (z *Zstd) Provision(ctx caddy.Context) error {
method AcceptEncoding (line 86) | func (Zstd) AcceptEncoding() string { return "zstd" }
method NewEncoder (line 89) | func (z Zstd) NewEncoder() encode.Encoder {
FILE: modules/caddyhttp/encode/zstd/zstd_precompressed.go
function init (line 8) | func init() {
type ZstdPrecompressed (line 13) | type ZstdPrecompressed struct
method CaddyModule (line 18) | func (ZstdPrecompressed) CaddyModule() caddy.ModuleInfo {
method Suffix (line 26) | func (ZstdPrecompressed) Suffix() string { return ".zst" }
FILE: modules/caddyhttp/errors.go
function Error (line 32) | func Error(statusCode int, err error) HandlerError {
type HandlerError (line 57) | type HandlerError struct
method Error (line 65) | func (e HandlerError) Error() string {
method Unwrap (line 83) | func (e HandlerError) Unwrap() error { return e.Err }
function randString (line 90) | func randString(n int, sameCase bool) string {
function trace (line 106) | func trace() string {
constant ErrorCtxKey (line 117) | ErrorCtxKey = caddy.CtxKey("handler_chain_error")
FILE: modules/caddyhttp/fileserver/browse.go
type Browse (line 53) | type Browse struct
constant defaultDirEntryLimit (line 75) | defaultDirEntryLimit = 10000
method serveBrowse (line 78) | func (fsrv *FileServer) serveBrowse(fileSystem fs.FS, root, dirPath stri...
method loadDirectoryContents (line 216) | func (fsrv *FileServer) loadDirectoryContents(ctx context.Context, fileS...
method browseApplyQueryParams (line 239) | func (fsrv *FileServer) browseApplyQueryParams(w http.ResponseWriter, r ...
method makeBrowseTemplate (line 303) | func (fsrv *FileServer) makeBrowseTemplate(tplCtx *templateContext) (*te...
method isSymlinkTargetDir (line 326) | func (fsrv *FileServer) isSymlinkTargetDir(fileSystem fs.FS, f fs.FileIn...
function isSymlink (line 339) | func isSymlink(f fs.FileInfo) bool {
type templateContext (line 346) | type templateContext struct
FILE: modules/caddyhttp/fileserver/browsetplcontext.go
method directoryListing (line 38) | func (fsrv *FileServer) directoryListing(ctx context.Context, fileSystem...
type browseTemplateContext (line 143) | type browseTemplateContext struct
method Breadcrumbs (line 193) | func (l browseTemplateContext) Breadcrumbs() []crumb {
method applySortAndLimit (line 220) | func (l *browseTemplateContext) applySortAndLimit(sortParam, orderPara...
method HumanTotalFileSize (line 305) | func (btc browseTemplateContext) HumanTotalFileSize() string {
method HumanTotalFileSizeFollowingSymlinks (line 311) | func (btc browseTemplateContext) HumanTotalFileSizeFollowingSymlinks()...
type crumb (line 268) | type crumb struct
type fileInfo (line 274) | type fileInfo struct
method HasExt (line 289) | func (fi fileInfo) HasExt(exts ...string) bool {
method HumanSize (line 298) | func (fi fileInfo) HumanSize() string {
method HumanModTime (line 317) | func (fi fileInfo) HumanModTime(format string) string {
type byName (line 322) | type byName
method Len (line 328) | func (l byName) Len() int { return len(l.Items) }
method Swap (line 329) | func (l byName) Swap(i, j int) { l.Items[i], l.Items[j] = l.Items[j], ...
method Less (line 331) | func (l byName) Less(i, j int) bool {
type byNameDirFirst (line 323) | type byNameDirFirst
method Len (line 335) | func (l byNameDirFirst) Len() int { return len(l.Items) }
method Swap (line 336) | func (l byNameDirFirst) Swap(i, j int) { l.Items[i], l.Items[j] = l.It...
method Less (line 338) | func (l byNameDirFirst) Less(i, j int) bool {
type bySize (line 324) | type bySize
method Len (line 347) | func (l bySize) Len() int { return len(l.Items) }
method Swap (line 348) | func (l bySize) Swap(i, j int) { l.Items[i], l.Items[j] = l.Items[j], ...
method Less (line 350) | func (l bySize) Less(i, j int) bool {
type byTime (line 325) | type byTime
method Len (line 371) | func (l byTime) Len() int { return len(l.Items) }
method Swap (line 372) | func (l byTime) Swap(i, j int) { l.Items[i], l.Items[j] = l.Items...
method Less (line 373) | func (l byTime) Less(i, j int) bool { return l.Items[i].ModTime.Before...
constant sortByName (line 376) | sortByName = "name"
constant sortByNameDirFirst (line 377) | sortByNameDirFirst = "namedirfirst"
constant sortBySize (line 378) | sortBySize = "size"
constant sortByTime (line 379) | sortByTime = "time"
constant sortOrderAsc (line 381) | sortOrderAsc = "asc"
constant sortOrderDesc (line 382) | sortOrderDesc = "desc"
FILE: modules/caddyhttp/fileserver/browsetplcontext_test.go
function TestBreadcrumbs (line 21) | func TestBreadcrumbs(t *testing.T) {
FILE: modules/caddyhttp/fileserver/caddyfile.go
function init (line 31) | func init() {
function parseCaddyfile (line 38) | func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler...
method UnmarshalCaddyfile (line 67) | func (fsrv *FileServer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
method FinalizeUnmarshalCaddyfile (line 207) | func (fsrv *FileServer) FinalizeUnmarshalCaddyfile(h httpcaddyfile.Helpe...
function parseTryFiles (line 255) | func parseTryFiles(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue,...
FILE: modules/caddyhttp/fileserver/command.go
function init (line 39) | func init() {
function cmdFileServer (line 86) | func cmdFileServer(fs caddycmd.Flags) (int, error) {
FILE: modules/caddyhttp/fileserver/matcher.go
function init (line 43) | func init() {
type MatchFile (line 65) | type MatchFile struct
method CaddyModule (line 118) | func (MatchFile) CaddyModule() caddy.ModuleInfo {
method UnmarshalCaddyfile (line 132) | func (m *MatchFile) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
method CELLibrary (line 174) | func (MatchFile) CELLibrary(ctx caddy.Context) (cel.Library, error) {
method Provision (line 274) | func (m *MatchFile) Provision(ctx caddy.Context) error {
method Validate (line 296) | func (m MatchFile) Validate() error {
method Match (line 317) | func (m MatchFile) Match(r *http.Request) bool {
method MatchWithError (line 327) | func (m MatchFile) MatchWithError(r *http.Request) (bool, error) {
method selectFile (line 333) | func (m MatchFile) selectFile(r *http.Request) (bool, error) {
method strictFileExists (line 550) | func (m MatchFile) strictFileExists(fileSystem fs.FS, file string) (os...
method firstSplit (line 579) | func (m MatchFile) firstSplit(path string) (splitPart, remainder strin...
function celFileMatcherMacroExpander (line 225) | func celFileMatcherMacroExpander() parser.MacroExpander {
function parseErrorCode (line 533) | func parseErrorCode(input string) error {
function indexFold (line 596) | func indexFold(haystack, needle string) int {
function isCELTryFilesLiteral (line 608) | func isCELTryFilesLiteral(e ast.Expr) bool {
function isCELStringExpr (line 641) | func isCELStringExpr(e ast.Expr) bool {
function isCELStringLiteral (line 646) | func isCELStringLiteral(e ast.Expr) bool {
function isCELCaddyPlaceholderCall (line 661) | func isCELCaddyPlaceholderCall(e ast.Expr) bool {
function isCELConcatCall (line 676) | func isCELConcatCall(e ast.Expr) bool {
function isCELStringListLiteral (line 700) | func isCELStringListLiteral(e ast.Expr) bool {
constant tryPolicyFirstExist (line 727) | tryPolicyFirstExist = "first_exist"
constant tryPolicyFirstExistFallback (line 728) | tryPolicyFirstExistFallback = "first_exist_fallback"
constant tryPolicyLargestSize (line 729) | tryPolicyLargestSize = "largest_size"
constant tryPolicySmallestSize (line 730) | tryPolicySmallestSize = "smallest_size"
constant tryPolicyMostRecentlyMod (line 731) | tryPolicyMostRecentlyMod = "most_recently_modified"
FILE: modules/caddyhttp/fileserver/matcher_test.go
type testCase (line 33) | type testCase struct
function TestFileMatcher (line 40) | func TestFileMatcher(t *testing.T) {
function TestFileMatcherNonWindows (line 127) | func TestFileMatcherNonWindows(t *testing.T) {
function fileMatcherTest (line 150) | func fileMatcherTest(t *testing.T, i int, tc testCase) {
function TestPHPFileMatcher (line 191) | func TestPHPFileMatcher(t *testing.T) {
function TestFirstSplit (line 304) | func TestFirstSplit(t *testing.T) {
function TestMatchExpressionMatch (line 406) | func TestMatchExpressionMatch(t *testing.T) {
FILE: modules/caddyhttp/fileserver/staticfiles.go
function init (line 41) | func init() {
type FileServer (line 97) | type FileServer struct
method CaddyModule (line 185) | func (FileServer) CaddyModule() caddy.ModuleInfo {
method Provision (line 193) | func (fsrv *FileServer) Provision(ctx caddy.Context) error {
method ServeHTTP (line 268) | func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Reque...
method openFile (line 591) | func (fsrv *FileServer) openFile(fileSystem fs.FS, filename string, w ...
method mapDirOpenError (line 625) | func (fsrv *FileServer) mapDirOpenError(fileSystem fs.FS, originalErr ...
method transformHidePaths (line 655) | func (fsrv *FileServer) transformHidePaths(repl *caddy.Replacer) []str...
method notFound (line 722) | func (fsrv *FileServer) notFound(w http.ResponseWriter, r *http.Reques...
method getEtagFromFile (line 758) | func (fsrv *FileServer) getEtagFromFile(fileSystem fs.FS, filename str...
function fileHidden (line 673) | func fileHidden(filename string, hide []string) bool {
function calculateEtag (line 744) | func calculateEtag(d os.FileInfo) string {
function redirect (line 779) | func redirect(w http.ResponseWriter, r *http.Request, toPath string) err...
type statusOverrideResponseWriter (line 795) | type statusOverrideResponseWriter struct
method WriteHeader (line 802) | func (wr statusOverrideResponseWriter) WriteHeader(int) {
method Unwrap (line 808) | func (wr statusOverrideResponseWriter) Unwrap() http.ResponseWriter {
constant minBackoff (line 815) | minBackoff, maxBackoff = 2, 5
constant separator (line 816) | separator = string(filepath.Separator)
FILE: modules/caddyhttp/fileserver/staticfiles_test.go
function TestFileHidden (line 24) | func TestFileHidden(t *testing.T) {
FILE: modules/caddyhttp/headers/caddyfile.go
function init (line 27) | func init() {
function parseCaddyfile (line 49) | func parseCaddyfile(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue...
function parseReqHdrCaddyfile (line 163) | func parseReqHdrCaddyfile(h httpcaddyfile.Helper) ([]httpcaddyfile.Confi...
function CaddyfileHeaderOp (line 221) | func CaddyfileHeaderOp(ops *HeaderOps, field, value string, replacement ...
function applyHeaderOp (line 225) | func applyHeaderOp(ops *HeaderOps, respHeaderOps *RespHeaderOps, field, ...
FILE: modules/caddyhttp/headers/headers.go
function init (line 27) | func init() {
type Handler (line 43) | type Handler struct
method CaddyModule (line 49) | func (Handler) CaddyModule() caddy.ModuleInfo {
method Provision (line 57) | func (h *Handler) Provision(ctx caddy.Context) error {
method Validate (line 74) | func (h Handler) Validate() error {
method ServeHTTP (line 90) | func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, nex...
type HeaderOps (line 114) | type HeaderOps struct
method Provision (line 135) | func (ops *HeaderOps) Provision(_ caddy.Context) error {
method validate (line 176) | func (ops HeaderOps) validate() error {
method ApplyTo (line 220) | func (ops *HeaderOps) ApplyTo(hdr http.Header, repl *caddy.Replacer) {
method ApplyToRequest (line 344) | func (ops HeaderOps) ApplyToRequest(r *http.Request) {
function containsPlaceholders (line 163) | func containsPlaceholders(s string) bool {
type Replacement (line 190) | type Replacement struct
type RespHeaderOps (line 204) | type RespHeaderOps struct
type responseWriterWrapper (line 379) | type responseWriterWrapper struct
method WriteHeader (line 387) | func (rww *responseWriterWrapper) WriteHeader(status int) {
method Write (line 403) | func (rww *responseWriterWrapper) Write(d []byte) (int, error) {
FILE: modules/caddyhttp/headers/headers_test.go
function TestHandler (line 29) | func TestHandler(t *testing.T) {
type nextHandler (line 270) | type nextHandler
method ServeHTTP (line 272) | func (f nextHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)...
function TestContainsPlaceholders (line 276) | func TestContainsPlaceholders(t *testing.T) {
function TestHeaderProvisionSkipsPlaceholders (line 296) | func TestHeaderProvisionSkipsPlaceholders(t *testing.T) {
function TestPlaceholderInSearchRegexp (line 324) | func TestPlaceholderInSearchRegexp(t *testing.T) {
FILE: modules/caddyhttp/http2listener.go
type connectionStater (line 12) | type connectionStater interface
type http2Listener (line 25) | type http2Listener struct
method Accept (line 33) | func (h *http2Listener) Accept() (net.Conn, error) {
type tlsStateConn (line 77) | type tlsStateConn struct
method tlsNetConn (line 81) | func (conn tlsStateConn) tlsNetConn() net.Conn {
type http2StateConn (line 85) | type http2StateConn struct
method ConnectionState (line 89) | func (conn http2StateConn) ConnectionState() tls.ConnectionState {
type http2Conn (line 93) | type http2Conn struct
method Read (line 104) | func (c *http2Conn) Read(p []byte) (int, error) {
FILE: modules/caddyhttp/httpredirectlistener.go
function init (line 29) | func init() {
type HTTPRedirectListenerWrapper (line 46) | type HTTPRedirectListenerWrapper struct
method CaddyModule (line 52) | func (HTTPRedirectListenerWrapper) CaddyModule() caddy.ModuleInfo {
method UnmarshalCaddyfile (line 59) | func (h *HTTPRedirectListenerWrapper) UnmarshalCaddyfile(d *caddyfile....
method WrapListener (line 63) | func (h *HTTPRedirectListenerWrapper) WrapListener(l net.Listener) net...
type httpRedirectListener (line 70) | type httpRedirectListener struct
method Accept (line 77) | func (l *httpRedirectListener) Accept() (net.Conn, error) {
type httpRedirectConn (line 95) | type httpRedirectConn struct
method Read (line 106) | func (c *httpRedirectConn) Read(p []byte) (int, error) {
function firstBytesLookLikeHTTP (line 163) | func firstBytesLookLikeHTTP(hdr []byte) bool {
FILE: modules/caddyhttp/intercept/intercept.go
function init (line 35) | func init() {
type Intercept (line 45) | type Intercept struct
method CaddyModule (line 70) | func (Intercept) CaddyModule() caddy.ModuleInfo {
method Provision (line 80) | func (irh *Intercept) Provision(ctx caddy.Context) error {
method ServeHTTP (line 128) | func (ir Intercept) ServeHTTP(w http.ResponseWriter, r *http.Request, ...
method UnmarshalCaddyfile (line 226) | func (i *Intercept) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
method FinalizeUnmarshalCaddyfile (line 294) | func (i *Intercept) FinalizeUnmarshalCaddyfile(helper httpcaddyfile.He...
type interceptedResponseHandler (line 103) | type interceptedResponseHandler struct
method WriteHeader (line 112) | func (irh interceptedResponseHandler) WriteHeader(statusCode int) {
method Unwrap (line 123) | func (irh interceptedResponseHandler) Unwrap() http.ResponseWriter {
constant matcherPrefix (line 363) | matcherPrefix = "@"
function parseCaddyfile (line 365) | func parseCaddyfile(helper httpcaddyfile.Helper) (caddyhttp.MiddlewareHa...
FILE: modules/caddyhttp/invoke.go
function init (line 24) | func init() {
type Invoke (line 32) | type Invoke struct
method CaddyModule (line 38) | func (Invoke) CaddyModule() caddy.ModuleInfo {
method ServeHTTP (line 45) | func (invoke *Invoke) ServeHTTP(w http.ResponseWriter, r *http.Request...
FILE: modules/caddyhttp/ip_matchers.go
type MatchRemoteIP (line 37) | type MatchRemoteIP struct
method CaddyModule (line 67) | func (MatchRemoteIP) CaddyModule() caddy.ModuleInfo {
method UnmarshalCaddyfile (line 75) | func (m *MatchRemoteIP) UnmarshalCaddyfile(d *caddyfile.Dispenser) err...
method CELLibrary (line 101) | func (MatchRemoteIP) CELLibrary(ctx caddy.Context) (cel.Library, error) {
method Provision (line 133) | func (m *MatchRemoteIP) Provision(ctx caddy.Context) error {
method Match (line 146) | func (m MatchRemoteIP) Match(r *http.Request) bool {
method MatchWithError (line 155) | func (m MatchRemoteIP) MatchWithError(r *http.Request) (bool, error) {
type MatchClientIP (line 50) | type MatchClientIP struct
method CaddyModule (line 183) | func (MatchClientIP) CaddyModule() caddy.ModuleInfo {
method UnmarshalCaddyfile (line 191) | func (m *MatchClientIP) UnmarshalCaddyfile(d *caddyfile.Dispenser) err...
method CELLibrary (line 214) | func (MatchClientIP) CELLibrary(ctx caddy.Context) (cel.Library, error) {
method Provision (line 241) | func (m *MatchClientIP) Provision(ctx caddy.Context) error {
method Match (line 253) | func (m MatchClientIP) Match(r *http.Request) bool {
method MatchWithError (line 262) | func (m MatchClientIP) MatchWithError(r *http.Request) (bool, error) {
function init (line 61) | func init() {
function provisionCidrsZonesFromRanges (line 284) | func provisionCidrsZonesFromRanges(ranges []string) ([]*netip.Prefix, []...
function parseIPZoneFromString (line 317) | func parseIPZoneFromString(address string) (netip.Addr, string, error) {
function matchIPByCidrZones (line 340) | func matchIPByCidrZones(clientIP netip.Addr, zoneID string, cidrs []*net...
FILE: modules/caddyhttp/ip_range.go
function init (line 28) | func init() {
type IPRangeSource (line 52) | type IPRangeSource interface
type StaticIPRange (line 57) | type StaticIPRange struct
method CaddyModule (line 66) | func (StaticIPRange) CaddyModule() caddy.ModuleInfo {
method Provision (line 73) | func (s *StaticIPRange) Provision(ctx caddy.Context) error {
method GetIPRanges (line 85) | func (s *StaticIPRange) GetIPRanges(_ *http.Request) []netip.Prefix {
method UnmarshalCaddyfile (line 90) | func (m *StaticIPRange) UnmarshalCaddyfile(d *caddyfile.Dispenser) err...
function CIDRExpressionToPrefix (line 106) | func CIDRExpressionToPrefix(expr string) (netip.Prefix, error) {
function PrivateRangesCIDR (line 135) | func PrivateRangesCIDR() []string {
FILE: modules/caddyhttp/logging.go
function init (line 34) | func init() {
type ServerLogConfig (line 44) | type ServerLogConfig struct
method wrapLogger (line 93) | func (slc ServerLogConfig) wrapLogger(logger *zap.Logger, req *http.Re...
method getLoggerHosts (line 135) | func (slc ServerLogConfig) getLoggerHosts(host string) []string {
method clone (line 157) | func (slc *ServerLogConfig) clone() *ServerLogConfig {
type StringArray (line 174) | type StringArray
method UnmarshalJSON (line 177) | func (sa *StringArray) UnmarshalJSON(b []byte) error {
function errLogValues (line 206) | func errLogValues(err error) (status int, msg string, fields func() []za...
type ExtraLogFields (line 235) | type ExtraLogFields struct
method Add (line 241) | func (e *ExtraLogFields) Add(field zap.Field) {
method Set (line 248) | func (e *ExtraLogFields) Set(field zap.Field) {
method getSloggerHandler (line 260) | func (e *ExtraLogFields) getSloggerHandler(handler *extraFieldsSlogHan...
constant LogSkipVar (line 286) | LogSkipVar string = "log_skip"
constant ExtraLogFieldsCtxKey (line 289) | ExtraLogFieldsCtxKey caddy.CtxKey = "extra_log_fields"
constant AccessLoggerNameVarKey (line 292) | AccessLoggerNameVarKey string = "access_logger_names"
type extraFieldsSlogHandler (line 295) | type extraFieldsSlogHandler struct
method Enabled (line 303) | func (e *extraFieldsSlogHandler) Enabled(ctx context.Context, level sl...
method Handle (line 307) | func (e *extraFieldsSlogHandler) Handle(ctx context.Context, record sl...
method WithAttrs (line 315) | func (e *extraFieldsSlogHandler) WithAttrs(attrs []slog.Attr) slog.Han...
method WithGroup (line 325) | func (e *extraFieldsSlogHandler) WithGroup(name string) slog.Handler {
FILE: modules/caddyhttp/logging/caddyfile.go
function init (line 25) | func init() {
function parseCaddyfile (line 32) | func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler...
method UnmarshalCaddyfile (line 39) | func (h *LogAppend) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
FILE: modules/caddyhttp/logging/logappend.go
function init (line 29) | func init() {
type LogAppend (line 36) | type LogAppend struct
method CaddyModule (line 56) | func (LogAppend) CaddyModule() caddy.ModuleInfo {
method ServeHTTP (line 63) | func (h LogAppend) ServeHTTP(w http.ResponseWriter, r *http.Request, n...
method addLogField (line 120) | func (h LogAppend) addLogField(r *http.Request, buf *bytes.Buffer) {
constant placeholderRequestBody (line 164) | placeholderRequestBody = "{http.request.body}"
constant placeholderRequestBodyBase64 (line 165) | placeholderRequestBodyBase64 = "{http.request.body_base64}"
constant placeholderResponseBody (line 166) | placeholderResponseBody = "{http.response.body}"
constant placeholderResponseBodyBase64 (line 167) | placeholderResponseBodyBase64 = "{http.response.body_base64}"
FILE: modules/caddyhttp/map/caddyfile.go
function init (line 24) | func init() {
function parseCaddyfile (line 44) | func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler...
FILE: modules/caddyhttp/map/map.go
function init (line 28) | func init() {
type Handler (line 39) | type Handler struct
method CaddyModule (line 57) | func (Handler) CaddyModule() caddy.ModuleInfo {
method Provision (line 65) | func (h *Handler) Provision(_ caddy.Context) error {
method Validate (line 91) | func (h *Handler) Validate() error {
method ServeHTTP (line 124) | func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, nex...
type Mapping (line 175) | type Mapping struct
FILE: modules/caddyhttp/map/map_test.go
function TestHandler (line 14) | func TestHandler(t *testing.T) {
FILE: modules/caddyhttp/marshalers.go
type LoggableHTTPRequest (line 27) | type LoggableHTTPRequest struct
method MarshalLogObject (line 34) | func (r LoggableHTTPRequest) MarshalLogObject(enc zapcore.ObjectEncode...
type LoggableHTTPHeader (line 66) | type LoggableHTTPHeader struct
method MarshalLogObject (line 73) | func (h LoggableHTTPHeader) MarshalLogObject(enc zapcore.ObjectEncoder...
type LoggableStringArray (line 90) | type LoggableStringArray
method MarshalLogArray (line 93) | func (sa LoggableStringArray) MarshalLogArray(enc zapcore.ArrayEncoder...
type LoggableTLSConnState (line 104) | type LoggableTLSConnState
method MarshalLogObject (line 107) | func (t LoggableTLSConnState) MarshalLogObject(enc zapcore.ObjectEncod...
FILE: modules/caddyhttp/matchers.go
type MatchHost (line 65) | type MatchHost
method CaddyModule (line 236) | func (MatchHost) CaddyModule() caddy.ModuleInfo {
method UnmarshalCaddyfile (line 244) | func (m *MatchHost) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
method Provision (line 256) | func (m MatchHost) Provision(_ caddy.Context) error {
method Match (line 301) | func (m MatchHost) Match(r *http.Request) bool {
method MatchWithError (line 307) | func (m MatchHost) MatchWithError(r *http.Request) (bool, error) {
method CELLibrary (line 374) | func (MatchHost) CELLibrary(ctx caddy.Context) (cel.Library, error) {
method fuzzy (line 394) | func (MatchHost) fuzzy(h string) bool { return strings.ContainsAny(h, ...
method large (line 399) | func (m MatchHost) large() bool { return len(m) > 100 }
type MatchPath (line 108) | type MatchPath
method CaddyModule (line 402) | func (MatchPath) CaddyModule() caddy.ModuleInfo {
method Provision (line 410) | func (m MatchPath) Provision(_ caddy.Context) error {
method Match (line 423) | func (m MatchPath) Match(r *http.Request) bool {
method MatchWithError (line 429) | func (m MatchPath) MatchWithError(r *http.Request) (bool, error) {
method matchPatternWithEscapeSequence (line 540) | func (MatchPath) matchPatternWithEscapeSequence(escapedPath, matchPath...
method CELLibrary (line 652) | func (MatchPath) CELLibrary(ctx caddy.Context) (cel.Library, error) {
method UnmarshalCaddyfile (line 675) | func (m *MatchPath) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
type MatchPathRE (line 118) | type MatchPathRE struct
method CaddyModule (line 687) | func (MatchPathRE) CaddyModule() caddy.ModuleInfo {
method Match (line 695) | func (m MatchPathRE) Match(r *http.Request) bool {
method MatchWithError (line 701) | func (m MatchPathRE) MatchWithError(r *http.Request) (bool, error) {
method CELLibrary (line 718) | func (MatchPathRE) CELLibrary(ctx caddy.Context) (cel.Library, error) {
type MatchMethod (line 121) | type MatchMethod
method CaddyModule (line 768) | func (MatchMethod) CaddyModule() caddy.ModuleInfo {
method UnmarshalCaddyfile (line 776) | func (m *MatchMethod) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
method Match (line 788) | func (m MatchMethod) Match(r *http.Request) bool {
method MatchWithError (line 794) | func (m MatchMethod) MatchWithError(r *http.Request) (bool, error) {
method CELLibrary (line 804) | func (MatchMethod) CELLibrary(_ caddy.Context) (cel.Library, error) {
type MatchQuery (line 147) | type MatchQuery
method CaddyModule (line 821) | func (MatchQuery) CaddyModule() caddy.ModuleInfo {
method UnmarshalCaddyfile (line 829) | func (m *MatchQuery) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
method Match (line 853) | func (m MatchQuery) Match(r *http.Request) bool {
method MatchWithError (line 860) | func (m MatchQuery) MatchWithError(r *http.Request) (bool, error) {
method CELLibrary (line 911) | func (MatchQuery) CELLibrary(_ caddy.Context) (cel.Library, error) {
type MatchHeader (line 163) | type MatchHeader
method CaddyModule (line 927) | func (MatchHeader) CaddyModule() caddy.ModuleInfo {
method UnmarshalCaddyfile (line 935) | func (m *MatchHeader) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
method Match (line 977) | func (m MatchHeader) Match(r *http.Request) bool {
method MatchWithError (line 983) | func (m MatchHeader) MatchWithError(r *http.Request) (bool, error) {
method CELLibrary (line 995) | func (MatchHeader) CELLibrary(_ caddy.Context) (cel.Library, error) {
type MatchHeaderRE (line 172) | type MatchHeaderRE
method CaddyModule (line 1072) | func (MatchHeaderRE) CaddyModule() caddy.ModuleInfo {
method UnmarshalCaddyfile (line 1080) | func (m *MatchHeaderRE) UnmarshalCaddyfile(d *caddyfile.Dispenser) err...
method Match (line 1122) | func (m MatchHeaderRE) Match(r *http.Request) bool {
method MatchWithError (line 1128) | func (m MatchHeaderRE) MatchWithError(r *http.Request) (bool, error) {
method Provision (line 1148) | func (m MatchHeaderRE) Provision(ctx caddy.Context) error {
method Validate (line 1159) | func (m MatchHeaderRE) Validate() error {
method CELLibrary (line 1175) | func (MatchHeaderRE) CELLibrary(ctx caddy.Context) (cel.Library, error) {
type MatchProtocol (line 178) | type MatchProtocol
method CaddyModule (line 1232) | func (MatchProtocol) CaddyModule() caddy.ModuleInfo {
method Match (line 1240) | func (m MatchProtocol) Match(r *http.Request) bool {
method MatchWithError (line 1246) | func (m MatchProtocol) MatchWithError(r *http.Request) (bool, error) {
method UnmarshalCaddyfile (line 1275) | func (m *MatchProtocol) UnmarshalCaddyfile(d *caddyfile.Dispenser) err...
method CELLibrary (line 1293) | func (MatchProtocol) CELLibrary(_ caddy.Context) (cel.Library, error) {
type MatchTLS (line 185) | type MatchTLS struct
method CaddyModule (line 1309) | func (MatchTLS) CaddyModule() caddy.ModuleInfo {
method Match (line 1317) | func (m MatchTLS) Match(r *http.Request) bool {
method MatchWithError (line 1323) | func (m MatchTLS) MatchWithError(r *http.Request) (bool, error) {
method UnmarshalCaddyfile (line 1341) | func (m *MatchTLS) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
type MatchNot (line 216) | type MatchNot struct
method CaddyModule (line 1364) | func (MatchNot) CaddyModule() caddy.ModuleInfo {
method UnmarshalCaddyfile (line 1372) | func (m *MatchNot) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
method UnmarshalJSON (line 1386) | func (m *MatchNot) UnmarshalJSON(data []byte) error {
method MarshalJSON (line 1392) | func (m MatchNot) MarshalJSON() ([]byte, error) {
method Provision (line 1397) | func (m *MatchNot) Provision(ctx caddy.Context) error {
method Match (line 1423) | func (m MatchNot) Match(r *http.Request) bool {
method MatchWithError (line 1431) | func (m MatchNot) MatchWithError(r *http.Request) (bool, error) {
function init (line 222) | func init() {
function getHeaderFieldVals (line 1014) | func getHeaderFieldVals(input http.Header, fieldName, host string, trans...
function matchHeaders (line 1028) | func matchHeaders(input, against http.Header, host string, transferEncod...
type MatchRegexp (line 1447) | type MatchRegexp struct
method Provision (line 1467) | func (mre *MatchRegexp) Provision(caddy.Context) error {
method Validate (line 1477) | func (mre *MatchRegexp) Validate() error {
method Match (line 1488) | func (mre *MatchRegexp) Match(input string, repl *caddy.Replacer) bool {
method UnmarshalCaddyfile (line 1521) | func (mre *MatchRegexp) UnmarshalCaddyfile(d *caddyfile.Dispenser) err...
function ParseCaddyfileNestedMatcherSet (line 1556) | func ParseCaddyfileNestedMatcherSet(d *caddyfile.Dispenser) (caddy.Modul...
constant regexpPlaceholderPrefix (line 1610) | regexpPlaceholderPrefix = "http.regexp"
constant MatcherErrorVarKey (line 1620) | MatcherErrorVarKey = "matchers.error"
FILE: modules/caddyhttp/matchers_test.go
function TestHostMatcher (line 30) | func TestHostMatcher(t *testing.T) {
function TestPathMatcher (line 172) | func TestPathMatcher(t *testing.T) {
function TestPathMatcherWindows (line 457) | func TestPathMatcherWindows(t *testing.T) {
function TestPathREMatcher (line 479) | func TestPathREMatcher(t *testing.T) {
function TestHeaderMatcher (line 599) | func TestHeaderMatcher(t *testing.T) {
function TestQueryMatcher (line 727) | func TestQueryMatcher(t *testing.T) {
function TestHeaderREMatcher (line 857) | func TestHeaderREMatcher(t *testing.T) {
function BenchmarkHeaderREMatcher (line 940) | func BenchmarkHeaderREMatcher(b *testing.B) {
function TestVarREMatcher (line 965) | func TestVarREMatcher(t *testing.T) {
function TestNotMatcher (line 1062) | func TestNotMatcher(t *testing.T) {
function BenchmarkLargeHostMatcher (line 1178) | func BenchmarkLargeHostMatcher(b *testing.B) {
function BenchmarkHostMatcherWithoutPlaceholder (line 1205) | func BenchmarkHostMatcherWithoutPlaceholder(b *testing.B) {
function BenchmarkHostMatcherWithPlaceholder (line 1218) | func BenchmarkHostMatcherWithPlaceholder(b *testing.B) {
FILE: modules/caddyhttp/metrics.go
type Metrics (line 46) | type Metrics struct
method scanConfigForHosts (line 152) | func (m *Metrics) scanConfigForHosts(app *App) {
method shouldAllowHostMetrics (line 190) | func (m *Metrics) shouldAllowHostMetrics(host string, isHTTPS bool) bo...
type httpMetrics (line 76) | type httpMetrics struct
function initHTTPMetrics (line 86) | func initHTTPMetrics(ctx caddy.Context, metrics *Metrics) {
function serverNameFromContext (line 209) | func serverNameFromContext(ctx context.Context) string {
type metricsInstrumentedRoute (line 220) | type metricsInstrumentedRoute struct
method ServeHTTP (line 234) | func (h *metricsInstrumentedRoute) ServeHTTP(w http.ResponseWriter, r ...
function newMetricsInstrumentedRoute (line 226) | func newMetricsInstrumentedRoute(ctx caddy.Context, handler string, next...
function computeApproximateRequestSize (line 308) | func computeApproximateRequestSize(r *http.Request) int {
FILE: modules/caddyhttp/metrics_test.go
function TestServerNameFromContext (line 18) | func TestServerNameFromContext(t *testing.T) {
function TestMetricsInstrumentedHandler (line 32) | func TestMetricsInstrumentedHandler(t *testing.T) {
function TestMetricsInstrumentedHandlerPerHost (line 203) | func TestMetricsInstrumentedHandlerPerHost(t *testing.T) {
function TestMetricsCardinalityProtection (line 377) | func TestMetricsCardinalityProtection(t *testing.T) {
function TestMetricsHTTPSCatchAll (line 434) | func TestMetricsHTTPSCatchAll(t *testing.T) {
function TestMetricsInstrumentedRoute (line 483) | func TestMetricsInstrumentedRoute(t *testing.T) {
function BenchmarkMetricsInstrumentedRoute (line 526) | func BenchmarkMetricsInstrumentedRoute(b *testing.B) {
function BenchmarkSingleRouteMetrics (line 552) | func BenchmarkSingleRouteMetrics(b *testing.B) {
FILE: modules/caddyhttp/proxyprotocol/listenerwrapper.go
type ListenerWrapper (line 37) | type ListenerWrapper struct
method Provision (line 89) | func (pp *ListenerWrapper) Provision(ctx caddy.Context) error {
method WrapListener (line 137) | func (pp *ListenerWrapper) WrapListener(l net.Listener) net.Listener {
FILE: modules/caddyhttp/proxyprotocol/module.go
function init (line 22) | func init() {
method CaddyModule (line 26) | func (ListenerWrapper) CaddyModule() caddy.ModuleInfo {
method UnmarshalCaddyfile (line 41) | func (w *ListenerWrapper) UnmarshalCaddyfile(d *caddyfile.Dispenser) err...
FILE: modules/caddyhttp/proxyprotocol/policy.go
type Policy (line 11) | type Policy
method MarshalText (line 60) | func (x Policy) MarshalText() ([]byte, error) {
method UnmarshalText (line 65) | func (x *Policy) UnmarshalText(text []byte) error {
constant PolicyIGNORE (line 16) | PolicyIGNORE Policy = iota
constant PolicyUSE (line 18) | PolicyUSE
constant PolicyREJECT (line 23) | PolicyREJECT
constant PolicyREQUIRE (line 28) | PolicyREQUIRE
constant PolicySKIP (line 32) | PolicySKIP
function parsePolicy (line 75) | func parsePolicy(name string) (Policy, error) {
FILE: modules/caddyhttp/push/caddyfile.go
function init (line 23) | func init() {
function parseCaddyfile (line 46) | func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler...
FILE: modules/caddyhttp/push/handler.go
function init (line 30) | func init() {
type Handler (line 46) | type Handler struct
method CaddyModule (line 57) | func (Handler) CaddyModule() caddy.ModuleInfo {
method Provision (line 65) | func (h *Handler) Provision(ctx caddy.Context) error {
method ServeHTTP (line 76) | func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, nex...
method initializePushHeaders (line 136) | func (h Handler) initializePushHeaders(r *http.Request, repl *caddy.Re...
method servePreloadLinks (line 166) | func (h Handler) servePreloadLinks(pusher http.Pusher, hdr http.Header...
type Resource (line 186) | type Resource struct
type HeaderConfig (line 196) | type HeaderConfig struct
type linkPusher (line 204) | type linkPusher struct
method WriteHeader (line 212) | func (lp linkPusher) WriteHeader(statusCode int) {
function isRemoteResource (line 228) | func isRemoteResource(resource string) bool {
constant pushHeader (line 248) | pushHeader = "Caddy-Push"
constant pushedLink (line 255) | pushedLink = "http.handlers.push.pushed_link"
FILE: modules/caddyhttp/push/link.go
type linkResource (line 22) | type linkResource struct
function parseLinkHeader (line 37) | func parseLinkHeader(header string) []linkResource {
constant comma (line 74) | comma = ","
constant semicolon (line 75) | semicolon = ";"
constant equal (line 76) | equal = "="
FILE: modules/caddyhttp/push/link_test.go
function TestParseLinkHeader (line 21) | func TestParseLinkHeader(t *testing.T) {
FILE: modules/caddyhttp/replacer.go
function NewTestReplacer (line 50) | func NewTestReplacer(req *http.Request) *caddy.Replacer {
function addHTTPVarsToReplacer (line 58) | func addHTTPVarsToReplacer(repl *caddy.Replacer, req *http.Request, w ht...
function getReqTLSReplacement (line 409) | func getReqTLSReplacement(req *http.Request, key string) (any, bool) {
function marshalPublicKey (line 545) | func marshalPublicKey(pubKey any) ([]byte, error) {
function getTLSPeerCert (line 563) | func getTLSPeerCert(cs *tls.ConnectionState) *x509.Certificate {
type requestID (line 570) | type requestID struct
method String (line 575) | func (rid *requestID) String() string {
constant reqCookieReplPrefix (line 585) | reqCookieReplPrefix = "http.request.cookie."
constant reqHeaderReplPrefix (line 586) | reqHeaderReplPrefix = "http.request.header."
constant reqHostLabelsReplPrefix (line 587) | reqHostLabelsReplPrefix = "http.request.host.labels."
constant reqTLSReplPrefix (line 588) | reqTLSReplPrefix = "http.request.tls."
constant reqURIPathReplPrefix (line 589) | reqURIPathReplPrefix = "http.request.uri.path."
constant reqURIQueryReplPrefix (line 590) | reqURIQueryReplPrefix = "http.request.uri.query."
constant respHeaderReplPrefix (line 591) | respHeaderReplPrefix = "http.response.header."
constant varsReplPrefix (line 592) | varsReplPrefix = "http.vars."
constant reqOrigURIPathReplPrefix (line 593) | reqOrigURIPathReplPrefix = "http.request.orig_uri.path."
FILE: modules/caddyhttp/replacer_test.go
function TestHTTPVarReplacement (line 30) | func TestHTTPVarReplacement(t *testing.T) {
FILE: modules/caddyhttp/requestbody/caddyfile.go
function init (line 26) | func init() {
function parseCaddyfile (line 30) | func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler...
FILE: modules/caddyhttp/requestbody/requestbody.go
function init (line 31) | func init() {
type RequestBody (line 36) | type RequestBody struct
method CaddyModule (line 55) | func (RequestBody) CaddyModule() caddy.ModuleInfo {
method Provision (line 62) | func (rb *RequestBody) Provision(ctx caddy.Context) error {
method ServeHTTP (line 67) | func (rb RequestBody) ServeHTTP(w http.ResponseWriter, r *http.Request...
type errorWrapper (line 109) | type errorWrapper struct
method Read (line 113) | func (ew errorWrapper) Read(p []byte) (n int, err error) {
FILE: modules/caddyhttp/responsematchers.go
type ResponseMatcher (line 27) | type ResponseMatcher struct
method Match (line 40) | func (rm ResponseMatcher) Match(statusCode int, hdr http.Header) bool {
method matchStatusCode (line 47) | func (rm ResponseMatcher) matchStatusCode(statusCode int) bool {
function ParseNamedResponseMatcher (line 69) | func ParseNamedResponseMatcher(d *caddyfile.Dispenser, matchers map[stri...
FILE: modules/caddyhttp/responsematchers_test.go
function TestResponseMatcher (line 22) | func TestResponseMatcher(t *testing.T) {
FILE: modules/caddyhttp/responsewriter.go
type ResponseWriterWrapper (line 31) | type ResponseWriterWrapper struct
method Push (line 38) | func (rww *ResponseWriterWrapper) Push(target string, opts *http.PushO...
method ReadFrom (line 48) | func (rww *ResponseWriterWrapper) ReadFrom(r io.Reader) (n int64, err ...
method Unwrap (line 57) | func (rww *ResponseWriterWrapper) Unwrap() http.ResponseWriter {
type responseRecorder (line 65) | type responseRecorder struct
method WriteHeader (line 148) | func (rr *responseRecorder) WriteHeader(statusCode int) {
method Write (line 175) | func (rr *responseRecorder) Write(data []byte) (int, error) {
method ReadFrom (line 189) | func (rr *responseRecorder) ReadFrom(r io.Reader) (int64, error) {
method Status (line 204) | func (rr *responseRecorder) Status() int {
method Size (line 210) | func (rr *responseRecorder) Size() int {
method Buffer (line 216) | func (rr *responseRecorder) Buffer() *bytes.Buffer {
method Buffered (line 221) | func (rr *responseRecorder) Buffered() bool {
method WriteResponse (line 225) | func (rr *responseRecorder) WriteResponse() error {
method FlushError (line 241) | func (rr *responseRecorder) FlushError() error {
method setReadSize (line 251) | func (rr *responseRecorder) setReadSize(size *int) {
method Hijack (line 255) | func (rr *responseRecorder) Hijack() (net.Conn, *bufio.ReadWriter, err...
function NewResponseRecorder (line 137) | func NewResponseRecorder(w http.ResponseWriter, buf *bytes.Buffer, shoul...
type hijackedConn (line 279) | type hijackedConn struct
method updateReadSize (line 284) | func (hc *hijackedConn) updateReadSize(n int) {
method Read (line 290) | func (hc *hijackedConn) Read(p []byte) (int, error) {
method WriteTo (line 296) | func (hc *hijackedConn) WriteTo(w io.Writer) (int64, error) {
method Write (line 302) | func (hc *hijackedConn) Write(p []byte) (int, error) {
method ReadFrom (line 308) | func (hc *hijackedConn) ReadFrom(r io.Reader) (int64, error) {
type ResponseRecorder (line 317) | type ResponseRecorder interface
type ShouldBufferFunc (line 329) | type ShouldBufferFunc
FILE: modules/caddyhttp/responsewriter_test.go
type responseWriterSpy (line 11) | type responseWriterSpy interface
type baseRespWriter (line 23) | type baseRespWriter
method Write (line 25) | func (brw *baseRespWriter) Write(d []byte) (int, error) {
method Header (line 29) | func (brw *baseRespWriter) Header() http.Header { return nil }
method WriteHeader (line 30) | func (brw *baseRespWriter) WriteHeader(statusCode int) {}
method Written (line 31) | func (brw *baseRespWriter) Written() string { return string...
method CalledReadFrom (line 32) | func (brw *baseRespWriter) CalledReadFrom() bool { return false }
type readFromRespWriter (line 35) | type readFromRespWriter struct
method ReadFrom (line 40) | func (rf *readFromRespWriter) ReadFrom(r io.Reader) (int64, error) {
method CalledReadFrom (line 45) | func (rf *readFromRespWriter) CalledReadFrom() bool { return rf.called }
function TestResponseWriterWrapperReadFrom (line 47) | func TestResponseWriterWrapperReadFrom(t *testing.T) {
function TestResponseWriterWrapperUnwrap (line 96) | func TestResponseWriterWrapperUnwrap(t *testing.T) {
function TestResponseRecorderReadFrom (line 104) | func TestResponseRecorderReadFrom(t *testing.T) {
FILE: modules/caddyhttp/reverseproxy/addresses.go
type parsedAddr (line 26) | type parsedAddr struct
method dialAddr (line 31) | func (p parsedAddr) dialAddr() string {
method rangedPort (line 49) | func (p parsedAddr) rangedPort() bool {
method replaceablePort (line 53) | func (p parsedAddr) replaceablePort() bool {
method isUnix (line 57) | func (p parsedAddr) isUnix() bool {
function parseUpstreamDialAddress (line 65) | func parseUpstreamDialAddress(upstreamAddr string) (parsedAddr, error) {
FILE: modules/caddyhttp/reverseproxy/addresses_test.go
function TestParseUpstreamDialAddress (line 19) | func TestParseUpstreamDialAddress(t *testing.T) {
FILE: modules/caddyhttp/reverseproxy/admin.go
function init (line 25) | func init() {
type adminUpstreams (line 33) | type adminUpstreams struct
method CaddyModule (line 43) | func (adminUpstreams) CaddyModule() caddy.ModuleInfo {
method Routes (line 51) | func (al adminUpstreams) Routes() []caddy.AdminRoute {
method handleUpstreams (line 62) | func (adminUpstreams) handleUpstreams(w http.ResponseWriter, r *http.R...
type upstreamStatus (line 36) | type upstreamStatus struct
FILE: modules/caddyhttp/reverseproxy/admin_test.go
function adminHandlerFixture (line 30) | func adminHandlerFixture(t *testing.T, staticAddrs, dynamicAddrs []strin...
function callAdminUpstreams (line 60) | func callAdminUpstreams(t *testing.T) []upstreamStatus {
function resultsByAddress (line 85) | func resultsByAddress(statuses []upstreamStatus) map[string]upstreamStat...
function TestAdminUpstreamsMethodNotAllowed (line 94) | func TestAdminUpstreamsMethodNotAllowed(t *testing.T) {
function TestAdminUpstreamsEmpty (line 117) | func TestAdminUpstreamsEmpty(t *testing.T) {
function TestAdminUpstreamsStaticOnly (line 131) | func TestAdminUpstreamsStaticOnly(t *testing.T) {
function TestAdminUpstreamsDynamicOnly (line 154) | func TestAdminUpstreamsDynamicOnly(t *testing.T) {
function TestAdminUpstreamsBothPools (line 177) | func TestAdminUpstreamsBothPools(t *testing.T) {
function TestAdminUpstreamsNoOverlapBetweenPools (line 202) | func TestAdminUpstreamsNoOverlapBetweenPools(t *testing.T) {
function TestAdminUpstreamsReportsFailCounts (line 224) | func TestAdminUpstreamsReportsFailCounts(t *testing.T) {
function TestAdminUpstreamsReportsNumRequests (line 254) | func TestAdminUpstreamsReportsNumRequests(t *testing.T) {
FILE: modules/caddyhttp/reverseproxy/ascii.go
function asciiEqualFold (line 28) | func asciiEqualFold(s, t string) bool {
function asciiLower (line 41) | func asciiLower(b byte) byte {
function asciiIsPrint (line 50) | func asciiIsPrint(s string) bool {
FILE: modules/caddyhttp/reverseproxy/ascii_test.go
function TestEqualFold (line 28) | func TestEqualFold(t *testing.T) {
function TestIsPrint (line 66) | func TestIsPrint(t *testing.T) {
FILE: modules/caddyhttp/reverseproxy/buffering_test.go
type zeroReader (line 8) | type zeroReader struct
method Read (line 10) | func (zeroReader) Read(p []byte) (int, error) {
function TestBuffering (line 17) | func TestBuffering(t *testing.T) {
FILE: modules/caddyhttp/reverseproxy/caddyfile.go
function init (line 39) | func init() {
function parseCaddyfile (line 45) | func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler...
method UnmarshalCaddyfile (line 142) | func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
method FinalizeUnmarshalCaddyfile (line 912) | func (h *Handler) FinalizeUnmarshalCaddyfile(helper httpcaddyfile.Helper...
method UnmarshalCaddyfile (line 1015) | func (h *HTTPTransport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
function parseCopyResponseCaddyfile (line 1364) | func parseCopyResponseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.Middl...
method UnmarshalCaddyfile (line 1378) | func (h *CopyResponseHandler) UnmarshalCaddyfile(d *caddyfile.Dispenser)...
function parseCopyResponseHeadersCaddyfile (line 1403) | func parseCopyResponseHeadersCaddyfile(h httpcaddyfile.Helper) (caddyhtt...
method UnmarshalCaddyfile (line 1418) | func (h *CopyResponseHeadersHandler) UnmarshalCaddyfile(d *caddyfile.Dis...
method UnmarshalCaddyfile (line 1453) | func (u *SRVUpstreams) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
method UnmarshalCaddyfile (line 1559) | func (u *AUpstreams) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
method UnmarshalCaddyfile (line 1666) | func (u *MultiUpstreams) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
constant matcherPrefix (line 1689) | matcherPrefix = "@"
FILE: modules/caddyhttp/reverseproxy/command.go
function init (line 37) | func init() {
function cmdReverseProxy (line 87) | func cmdReverseProxy(fs caddycmd.Flags) (int, error) {
FILE: modules/caddyhttp/reverseproxy/copyresponse.go
function init (line 27) | func init() {
type CopyResponseHandler (line 35) | type CopyResponseHandler struct
method CaddyModule (line 44) | func (CopyResponseHandler) CaddyModule() caddy.ModuleInfo {
method Provision (line 52) | func (h *CopyResponseHandler) Provision(ctx caddy.Context) error {
method ServeHTTP (line 58) | func (h CopyResponseHandler) ServeHTTP(rw http.ResponseWriter, req *ht...
type CopyResponseHeadersHandler (line 90) | type CopyResponseHeadersHandler struct
method CaddyModule (line 105) | func (CopyResponseHeadersHandler) CaddyModule() caddy.ModuleInfo {
method Validate (line 113) | func (h *CopyResponseHeadersHandler) Validate() error {
method Provision (line 122) | func (h *CopyResponseHeadersHandler) Provision(ctx caddy.Context) error {
method ServeHTTP (line 145) | func (h CopyResponseHeadersHandler) ServeHTTP(rw http.ResponseWriter, ...
FILE: modules/caddyhttp/reverseproxy/dynamic_upstreams_test.go
function resetDynamicHosts (line 26) | func resetDynamicHosts() {
function TestFillDynamicHostCreatesEntry (line 36) | func TestFillDynamicHostCreatesEntry(t *testing.T) {
function TestFillDynamicHostReusesSameHost (line 63) | func TestFillDynamicHostReusesSameHost(t *testing.T) {
function TestFillDynamicHostUpdatesLastSeen (line 79) | func TestFillDynamicHostUpdatesLastSeen(t *testing.T) {
function TestFillDynamicHostIndependentAddresses (line 106) | func TestFillDynamicHostIndependentAddresses(t *testing.T) {
function TestFillDynamicHostPreservesFailCount (line 123) | func TestFillDynamicHostPreservesFailCount(t *testing.T) {
function TestProvisionUpstreamDynamic (line 147) | func TestProvisionUpstreamDynamic(t *testing.T) {
function TestProvisionUpstreamStatic (line 189) | func TestProvisionUpstreamStatic(t *testing.T) {
function TestDynamicHostHealthyConsultsFails (line 224) | func TestDynamicHostHealthyConsultsFails(t *testing.T) {
function TestDynamicHostCleanupEvictsStaleEntries (line 253) | func TestDynamicHostCleanupEvictsStaleEntries(t *testing.T) {
function TestDynamicHostCleanupRetainsFreshEntries (line 286) | func TestDynamicHostCleanupRetainsFreshEntries(t *testing.T) {
function TestDynamicHostConcurrentFillHost (line 319) | func TestDynamicHostConcurrentFillHost(t *testing.T) {
FILE: modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go
function init (line 34) | func init() {
method UnmarshalCaddyfile (line 50) | func (t *Transport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
function parsePHPFastCGI (line 172) | func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValu...
FILE: modules/caddyhttp/reverseproxy/fastcgi/client.go
constant FCGIListenSockFileno (line 49) | FCGIListenSockFileno uint8 = 0
constant FCGIHeaderLen (line 52) | FCGIHeaderLen uint8 = 8
constant Version1 (line 55) | Version1 uint8 = 1
constant FCGINullRequestID (line 58) | FCGINullRequestID uint8 = 0
constant FCGIKeepConn (line 61) | FCGIKeepConn uint8 = 1
constant BeginRequest (line 65) | BeginRequest uint8 = iota + 1
constant AbortRequest (line 67) | AbortRequest
constant EndRequest (line 69) | EndRequest
constant Params (line 71) | Params
constant Stdin (line 73) | Stdin
constant Stdout (line 75) | Stdout
constant Stderr (line 77) | Stderr
constant Data (line 79) | Data
constant GetValues (line 81) | GetValues
constant GetValuesResult (line 83) | GetValuesResult
constant UnknownType (line 85) | UnknownType
constant MaxType (line 87) | MaxType = UnknownType
constant Responder (line 92) | Responder uint8 = iota + 1
constant Authorizer (line 94) | Authorizer
constant Filter (line 96) | Filter
constant RequestComplete (line 101) | RequestComplete uint8 = iota
constant CantMultiplexConns (line 103) | CantMultiplexConns
constant Overloaded (line 105) | Overloaded
constant UnknownRole (line 107) | UnknownRole
constant MaxConns (line 112) | MaxConns string = "MAX_CONNS"
constant MaxRequests (line 114) | MaxRequests string = "MAX_REQS"
constant MultiplexConns (line 116) | MultiplexConns string = "MPXS_CONNS"
constant maxWrite (line 120) | maxWrite = 65500
constant maxPad (line 121) | maxPad = 255
type client (line 130) | type client struct
method Do (line 140) | func (c *client) Do(p map[string]string, req io.Reader) (r io.Reader, ...
method Request (line 213) | func (c *client) Request(p map[string]string, req io.Reader) (resp *ht...
method Get (line 267) | func (c *client) Get(p map[string]string, body io.Reader, l int64) (re...
method Head (line 275) | func (c *client) Head(p map[string]string) (resp *http.Response, err e...
method Options (line 283) | func (c *client) Options(p map[string]string) (resp *http.Response, er...
method Post (line 292) | func (c *client) Post(p map[string]string, method string, bodyType str...
method PostForm (line 315) | func (c *client) PostForm(p map[string]string, data url.Values) (resp ...
method PostFile (line 323) | func (c *client) PostFile(p map[string]string, data url.Values, file m...
method SetReadTimeout (line 364) | func (c *client) SetReadTimeout(t time.Duration) error {
method SetWriteTimeout (line 373) | func (c *client) SetWriteTimeout(t time.Duration) error {
type clientCloser (line 184) | type clientCloser struct
method Close (line 193) | func (f clientCloser) Close() error {
function chunked (line 381) | func chunked(te []string) bool { return len(te) > 0 && te[0] == "chunked" }
FILE: modules/caddyhttp/reverseproxy/fastcgi/client_test.go
constant scriptFile (line 50) | scriptFile = "/tank/www/fcgic_test.php"
constant ipPort (line 52) | ipPort = "127.0.0.1:59000"
type FastCGIServer (line 57) | type FastCGIServer struct
method ServeHTTP (line 59) | func (s FastCGIServer) ServeHTTP(resp http.ResponseWriter, req *http.R...
function sendFcgi (line 119) | func sendFcgi(reqType int, fcgiParams map[string]string, data []byte, po...
function generateRandFile (line 182) | func generateRandFile(size int) (p string, m string) {
function DisabledTest (line 212) | func DisabledTest(t *testing.T) {
FILE: modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go
function init (line 46) | func init() {
type Transport (line 51) | type Transport struct
method CaddyModule (line 99) | func (Transport) CaddyModule() caddy.ModuleInfo {
method Provision (line 107) | func (t *Transport) Provision(ctx caddy.Context) error {
method DefaultBufferSizes (line 158) | func (t Transport) DefaultBufferSizes() (int64, int64) {
method RoundTrip (line 163) | func (t Transport) RoundTrip(r *http.Request) (*http.Response, error) {
method buildEnv (line 259) | func (t Transport) buildEnv(r *http.Request) (envVars, error) {
method splitPos (line 430) | func (t Transport) splitPos(path string) int {
type envVars (line 490) | type envVars
type loggableEnv (line 493) | type loggableEnv struct
method MarshalLogObject (line 498) | func (env loggableEnv) MarshalLogObject(enc zapcore.ObjectEncoder) err...
FILE: modules/caddyhttp/reverseproxy/fastcgi/fastcgi_test.go
function TestProvisionSplitPath (line 13) | func TestProvisionSplitPath(t *testing.T) {
function TestSplitPos (line 73) | func TestSplitPos(t *testing.T) {
function TestSplitPosUnicodeSecurityRegression (line 226) | func TestSplitPosUnicodeSecurityRegression(t *testing.T) {
FILE: modules/caddyhttp/reverseproxy/fastcgi/header.go
type header (line 17) | type header struct
method init (line 26) | func (h *header) init(recType uint8, reqID uint16, contentLength int) {
FILE: modules/caddyhttp/reverseproxy/fastcgi/reader.go
type streamReader (line 22) | type streamReader struct
method Read (line 28) | func (w *streamReader) Read(p []byte) (n int, err error) {
FILE: modules/caddyhttp/reverseproxy/fastcgi/record.go
type record (line 23) | type record struct
method fill (line 29) | func (rec *record) fill(r io.Reader) (err error) {
method Read (line 52) | func (rec *record) Read(p []byte) (n int, err error) {
method hasMore (line 56) | func (rec *record) hasMore() bool {
FILE: modules/caddyhttp/reverseproxy/fastcgi/writer.go
type streamWriter (line 24) | type streamWriter struct
method writeRecord (line 31) | func (w *streamWriter) writeRecord(recType uint8, content []byte) (err...
method writeBeginRequest (line 41) | func (w *streamWriter) writeBeginRequest(role uint16, flags uint8) err...
method Write (line 46) | func (w *streamWriter) Write(p []byte) (int, error) {
method endStream (line 73) | func (w *streamWriter) endStream() error {
method writePairs (line 78) | func (w *streamWriter) writePairs(pairs map[string]string) error {
method writeHeader (line 120) | func (w *streamWriter) writeHeader() {
method Flush (line 131) | func (w *streamWriter) Flush() error {
method FlushStream (line 140) | func (w *streamWriter) FlushStream() error {
function encodeSize (line 109) | func encodeSize(b []byte, size uint32) int {
FILE: modules/caddyhttp/reverseproxy/forwardauth/caddyfile.go
function init (line 32) | func init() {
function parseCaddyfile (line 64) | func parseCaddyfile(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue...
FILE: modules/caddyhttp/reverseproxy/headers_test.go
function TestAddForwardedHeadersNonIP (line 11) | func TestAddForwardedHeadersNonIP(t *testing.T) {
function TestAddForwardedHeaders_UnixSocketTrusted (line 36) | func TestAddForwardedHeaders_UnixSocketTrusted(t *testing.T) {
function TestAddForwardedHeaders_UnixSocketUntrusted (line 68) | func TestAddForwardedHeaders_UnixSocketUntrusted(t *testing.T) {
function TestAddForwardedHeaders_UnixSocketTrustedNoExistingHeaders (line 100) | func TestAddForwardedHeaders_UnixSocketTrustedNoExistingHeaders(t *testi...
FILE: modules/caddyhttp/reverseproxy/healthchecks.go
type HealthChecks (line 38) | type HealthChecks struct
type ActiveHealthChecks (line 73) | type ActiveHealthChecks struct
method Provision (line 137) | func (a *ActiveHealthChecks) Provision(ctx caddy.Context, h *Handler) ...
method IsEnabled (line 226) | func (a *ActiveHealthChecks) IsEnabled() bool {
type PassiveHealthChecks (line 233) | type PassiveHealthChecks struct
type CircuitBreaker (line 263) | type CircuitBreaker interface
method activeHealthChecker (line 271) | func (h *Handler) activeHealthChecker() {
method doActiveHealthCheckForAllHosts (line 297) | func (h *Handler) doActiveHealthCheckForAllHosts() {
method doActiveHealthCheck (line 391) | func (h *Handler) doActiveHealthCheck(dialInfo DialInfo, hostAddr string...
method countFailure (line 587) | func (h *Handler) countFailure(upstream *Upstream) {
FILE: modules/caddyhttp/reverseproxy/hosts.go
type UpstreamPool (line 31) | type UpstreamPool
type Upstream (line 36) | type Upstream struct
method String (line 71) | func (u *Upstream) String() string { return u.Dial }
method Available (line 78) | func (u *Upstream) Available() bool {
method Healthy (line 85) | func (u *Upstream) Healthy() bool {
method Full (line 98) | func (u *Upstream) Full() bool {
method fillDialInfo (line 104) | func (u *Upstream) fillDialInfo(repl *caddy.Replacer) (DialInfo, error) {
method fillHost (line 128) | func (u *Upstream) fillHost() {
method fillDynamicHost (line 143) | func (u *Upstream) fillDynamicHost() {
method healthy (line 251) | func (u *Upstream) healthy() bool {
method setHealthy (line 258) | func (u *Upstream) setHealthy(healthy bool) bool {
type Host (line 176) | type Host struct
method NumRequests (line 184) | func (h *Host) NumRequests() int {
method Fails (line 189) | func (h *Host) Fails() int {
method activeHealthPasses (line 194) | func (h *Host) activeHealthPasses() int {
method activeHealthFails (line 199) | func (h *Host) activeHealthFails() int {
method countRequest (line 205) | func (h *Host) countRequest(delta int) error {
method countFail (line 215) | func (h *Host) countFail(delta int) error {
method countHealthPass (line 225) | func (h *Host) countHealthPass(delta int) error {
method countHealthFail (line 235) | func (h *Host) countHealthFail(delta int) error {
method resetHealth (line 244) | func (h *Host) resetHealth() {
type DialInfo (line 272) | type DialInfo struct
method String (line 293) | func (di DialInfo) String() string {
function GetDialInfo (line 299) | func GetDialInfo(ctx context.Context) (DialInfo, bool) {
type dynamicHostEntry (line 327) | type dynamicHostEntry struct
constant dialInfoVarKey (line 334) | dialInfoVarKey = "reverse_proxy.dial_info"
constant proxyProtocolInfoVarKey (line 338) | proxyProtocolInfoVarKey = "reverse_proxy.proxy_protocol_info"
type ProxyProtocolInfo (line 342) | type ProxyProtocolInfo struct
constant tlsH1OnlyVarKey (line 348) | tlsH1OnlyVarKey = "reverse_proxy.tls_h1_only"
constant proxyVarKey (line 351) | proxyVarKey = "reverse_proxy.proxy"
FILE: modules/caddyhttp/reverseproxy/httptransport.go
function init (line 48) | func init() {
type HTTPTransport (line 55) | type HTTPTransport struct
method CaddyModule (line 168) | func (HTTPTransport) CaddyModule() caddy.ModuleInfo {
method Provision (line 182) | func (h *HTTPTransport) Provision(ctx caddy.Context) error {
method NewTransport (line 205) | func (h *HTTPTransport) NewTransport(caddyCtx caddy.Context) (*http.Tr...
method RequestHeaderOps (line 537) | func (h *HTTPTransport) RequestHeaderOps() *headers.HeaderOps {
method RoundTrip (line 551) | func (h *HTTPTransport) RoundTrip(req *http.Request) (*http.Response, ...
method SetScheme (line 568) | func (h *HTTPTransport) SetScheme(req *http.Request) {
method shouldUseTLS (line 580) | func (h *HTTPTransport) shouldUseTLS(req *http.Request) bool {
method TLSEnabled (line 590) | func (h HTTPTransport) TLSEnabled() bool {
method EnableTLS (line 595) | func (h *HTTPTransport) EnableTLS(base *TLSConfig) error {
method EnableH2C (line 601) | func (h *HTTPTransport) EnableH2C() error {
method OverrideHealthCheckScheme (line 608) | func (h HTTPTransport) OverrideHealthCheckScheme(base *url.URL, port s...
method ProxyProtocolEnabled (line 616) | func (h HTTPTransport) ProxyProtocolEnabled() bool {
method Cleanup (line 621) | func (h HTTPTransport) Cleanup() error {
type TLSConfig (line 631) | type TLSConfig struct
method MakeTLSClientConfig (line 694) | func (t *TLSConfig) MakeTLSClientConfig(ctx caddy.Context) (*tls.Confi...
type KeepAlive (line 811) | type KeepAlive struct
type tcpRWTimeoutConn (line 832) | type tcpRWTimeoutConn struct
method Read (line 838) | func (c *tcpRWTimeoutConn) Read(b []byte) (int, error) {
method Write (line 850) | func (c *tcpRWTimeoutConn) Write(b []byte) (int, error) {
function decodeBase64DERCert (line 863) | func decodeBase64DERCert(certStr string) (*x509.Certificate, error) {
FILE: modules/caddyhttp/reverseproxy/httptransport_test.go
function TestHTTPTransportUnmarshalCaddyFileWithCaPools (line 14) | func TestHTTPTransportUnmarshalCaddyFileWithCaPools(t *testing.T) {
function TestHTTPTransport_RequestHeaderOps_TLS (line 100) | func TestHTTPTransport_RequestHeaderOps_TLS(t *testing.T) {
function TestHTTPTransport_DialTLSContext_ProxyProtocol (line 127) | func TestHTTPTransport_DialTLSContext_ProxyProtocol(t *testing.T) {
FILE: modules/caddyhttp/reverseproxy/metrics.go
function initReverseProxyMetrics (line 22) | func initReverseProxyMetrics(handler *Handler, registry *prometheus.Regi...
type metricsUpstreamsHealthyUpdater (line 49) | type metricsUpstreamsHealthyUpdater struct
method init (line 60) | func (m *metricsUpstreamsHealthyUpdater) init() {
method update (line 88) | func (m *metricsUpstreamsHealthyUpdater) update() {
function newMetricsUpstreamsHealthyUpdater (line 53) | func newMetricsUpstreamsHealthyUpdater(handler *Handler, ctx caddy.Conte...
FILE: modules/caddyhttp/reverseproxy/passive_health_test.go
function newPassiveHandler (line 28) | func newPassiveHandler(t *testing.T, maxFails int, failDuration time.Dur...
function provisionedStaticUpstream (line 45) | func provisionedStaticUpstream(t *testing.T, h *Handler, addr string) (*...
function provisionedDynamicUpstream (line 54) | func provisionedDynamicUpstream(t *testing.T, h *Handler, addr string) (...
function TestCountFailureNoopWhenNoHealthChecks (line 69) | func TestCountFailureNoopWhenNoHealthChecks(t *testing.T) {
function TestCountFailureNoopWhenZeroDuration (line 83) | func TestCountFailureNoopWhenZeroDuration(t *testing.T) {
function TestCountFailureIncrementsCount (line 104) | func TestCountFailureIncrementsCount(t *testing.T) {
function TestCountFailureDecrementsAfterDuration (line 119) | func TestCountFailureDecrementsAfterDuration(t *testing.T) {
function TestCountFailureCancelledContextForgets (line 142) | func TestCountFailureCancelledContextForgets(t *testing.T) {
function TestStaticUpstreamHealthyWithNoFailures (line 166) | func TestStaticUpstreamHealthyWithNoFailures(t *testing.T) {
function TestStaticUpstreamUnhealthyAtMaxFails (line 181) | func TestStaticUpstreamUnhealthyAtMaxFails(t *testing.T) {
function TestStaticUpstreamRecoversAfterFailDuration (line 202) | func TestStaticUpstreamRecoversAfterFailDuration(t *testing.T) {
function TestStaticUpstreamHealthPersistedAcrossReprovisioning (line 226) | func TestStaticUpstreamHealthPersistedAcrossReprovisioning(t *testing.T) {
function TestDynamicUpstreamHealthyWithNoFailures (line 254) | func TestDynamicUpstreamHealthyWithNoFailures(t *testing.T) {
function TestDynamicUpstreamUnhealthyAtMaxFails (line 269) | func TestDynamicUpstreamUnhealthyAtMaxFails(t *testing.T) {
function TestDynamicUpstreamFailCountPersistedBetweenRequests (line 292) | func TestDynamicUpstreamFailCountPersistedBetweenRequests(t *testing.T) {
function TestDynamicUpstreamRecoveryAfterFailDuration (line 334) | func TestDynamicUpstreamRecoveryAfterFailDuration(t *testing.T) {
function TestDynamicUpstreamMaxRequestsFromUnhealthyRequestCount (line 361) | func TestDynamicUpstreamMaxRequestsFromUnhealthyRequestCount(t *testing....
FILE: modules/caddyhttp/reverseproxy/retries_test.go
function prepareTestRequest (line 25) | func prepareTestRequest(req *http.Request) *http.Request {
type closeOnCloseReader (line 34) | type closeOnCloseReader struct
method Read (line 44) | func (c *closeOnCloseReader) Read(p []byte) (int, error) {
method Close (line 53) | func (c *closeOnCloseReader) Close() error {
function newCloseOnCloseReader (line 40) | func newCloseOnCloseReader(s string) *closeOnCloseReader {
function deadUpstreamAddr (line 63) | func deadUpstreamAddr(t *testing.T) string {
type testTransport (line 82) | type testTransport struct
method RoundTrip (line 84) | func (t testTransport) RoundTrip(req *http.Request) (*http.Response, e...
function minimalHandler (line 106) | func minimalHandler(retries int, upstreams ...*Upstream) *Handler {
function TestDialErrorBodyRetry (line 137) | func TestDialErrorBodyRetry(t *testing.T) {
FILE: modules/caddyhttp/reverseproxy/reverseproxy.go
function incInFlightRequest (line 53) | func incInFlightRequest(address string) {
function decInFlightRequest (line 58) | func decInFlightRequest(address string) {
function getInFlightRequests (line 66) | func getInFlightRequests() map[string]int64 {
function init (line 75) | func init() {
type Handler (line 100) | type Handler struct
method CaddyModule (line 248) | func (Handler) CaddyModule() caddy.ModuleInfo {
method Provision (line 256) | func (h *Handler) Provision(ctx caddy.Context) error {
method Cleanup (line 435) | func (h *Handler) Cleanup() error {
method ServeHTTP (line 446) | func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, ne...
method proxyLoopIteration (line 565) | func (h *Handler) proxyLoopIteration(r *http.Request, origReq *http.Re...
method prepareRequest (line 709) | func (h Handler) prepareRequest(req *http.Request, repl *caddy.Replace...
method addForwardedHeaders (line 835) | func (h Handler) addForwardedHeaders(req *http.Request) error {
method reverseProxy (line 937) | func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Reque...
method finalizeResponse (line 1126) | func (h *Handler) finalizeResponse(
method directRequest (line 1307) | func (h *Handler) directRequest(req *http.Request, di DialInfo) {
method provisionUpstream (line 1333) | func (h Handler) provisionUpstream(upstream *Upstream, dynamic bool) {
method bufferedBody (line 1376) | func (h Handler) bufferedBody(originalBody io.ReadCloser, limit int64)...
function normalizeWebsocketHeaders (line 693) | func normalizeWebsocketHeaders(header http.Header) {
function cloneRequest (line 1410) | func cloneRequest(origReq *http.Request) *http.Request {
function copyHeader (line 1439) | func copyHeader(dst, src http.Header) {
function allHeaderValues (line 1454) | func allHeaderValues(h http.Header, field string) (value string, ok bool...
function lastHeaderValue (line 1472) | func lastHeaderValue(h http.Header, field string) (value string, ok bool...
function upgradeType (line 1483) | func upgradeType(h http.Header) string {
function removeConnectionHeaders (line 1492) | func removeConnectionHeaders(h http.Header) {
function statusError (line 1503) | func statusError(err error) error {
type LoadBalancing (line 1529) | type LoadBalancing struct
method tryAgain (line 1240) | func (lb LoadBalancing) tryAgain(ctx caddy.Context, start time.Time, r...
type Selector (line 1569) | type Selector interface
type UpstreamSource (line 1583) | type UpstreamSource interface
type DialError (line 1607) | type DialError struct
type TLSTransport (line 1611) | type TLSTransport interface
type H2CTransport (line 1624) | type H2CTransport interface
type ProxyProtocolTransport (line 1630) | type ProxyProtocolTransport interface
type HealthCheckSchemeOverriderTransport (line 1636) | type HealthCheckSchemeOverriderTransport interface
type BufferedTransport (line 1642) | type BufferedTransport interface
type RequestHeaderOpsTransport (line 1651) | type RequestHeaderOpsTransport interface
type roundtripSucceededError (line 1661) | type roundtripSucceededError struct
type bodyReadCloser (line 1665) | type bodyReadCloser struct
method Close (line 1671) | func (brc bodyReadCloser) Close() error {
type handleResponseContext (line 1693) | type handleResponseContext struct
constant proxyHandleResponseContextCtxKey (line 1721) | proxyHandleResponseContextCtxKey caddy.CtxKey = "reverse_proxy_handle_re...
FILE: modules/caddyhttp/reverseproxy/selectionpolicies.go
function init (line 39) | func init() {
type RandomSelection (line 56) | type RandomSelection struct
method CaddyModule (line 59) | func (RandomSelection) CaddyModule() caddy.ModuleInfo {
method Select (line 67) | func (r RandomSelection) Select(pool UpstreamPool, request *http.Reque...
method UnmarshalCaddyfile (line 72) | func (r *RandomSelection) UnmarshalCaddyfile(d *caddyfile.Dispenser) e...
type WeightedRoundRobinSelection (line 82) | type WeightedRoundRobinSelection struct
method CaddyModule (line 91) | func (WeightedRoundRobinSelection) CaddyModule() caddy.ModuleInfo {
method UnmarshalCaddyfile (line 101) | func (r *WeightedRoundRobinSelection) UnmarshalCaddyfile(d *caddyfile....
method Provision (line 123) | func (r *WeightedRoundRobinSelection) Provision(ctx caddy.Context) err...
method Select (line 131) | func (r *WeightedRoundRobinSelection) Select(pool UpstreamPool, _ *htt...
type RandomChoiceSelection (line 174) | type RandomChoiceSelection struct
method CaddyModule (line 181) | func (RandomChoiceSelection) CaddyModule() caddy.ModuleInfo {
method UnmarshalCaddyfile (line 189) | func (r *RandomChoiceSelection) UnmarshalCaddyfile(d *caddyfile.Dispen...
method Provision (line 205) | func (r *RandomChoiceSelection) Provision(ctx caddy.Context) error {
method Validate (line 213) | func (r RandomChoiceSelection) Validate() error {
method Select (line 221) | func (r RandomChoiceSelection) Select(pool UpstreamPool, _ *http.Reque...
type LeastConnSelection (line 245) | type LeastConnSelection struct
method CaddyModule (line 248) | func (LeastConnSelection) CaddyModule() caddy.ModuleInfo {
method Select (line 258) | func (LeastConnSelection) Select(pool UpstreamPool, _ *http.Request, _...
method UnmarshalCaddyfile (line 287) | func (r *LeastConnSelection) UnmarshalCaddyfile(d *caddyfile.Dispenser...
type RoundRobinSelection (line 297) | type RoundRobinSelection struct
method CaddyModule (line 302) | func (RoundRobinSelection) CaddyModule() caddy.ModuleInfo {
method Select (line 310) | func (r *RoundRobinSelection) Select(pool UpstreamPool, _ *http.Reques...
method UnmarshalCaddyfile (line 326) | func (r *RoundRobinSelection) UnmarshalCaddyfile(d *caddyfile.Dispense...
type FirstSelection (line 336) | type FirstSelection struct
method CaddyModule (line 339) | func (FirstSelection) CaddyModule() caddy.ModuleInfo {
method Select (line 347) | func (FirstSelection) Select(pool UpstreamPool, _ *http.Request, _ htt...
method UnmarshalCaddyfile (line 357) | func (r *FirstSelection) UnmarshalCaddyfile(d *caddyfile.Dispenser) er...
type IPHashSelection (line 367) | type IPHashSelection struct
method CaddyModule (line 370) | func (IPHashSelection) CaddyModule() caddy.ModuleInfo {
method Select (line 378) | func (IPHashSelection) Select(pool UpstreamPool, req *http.Request, _ ...
method UnmarshalCaddyfile (line 387) | func (r *IPHashSelection) UnmarshalCaddyfile(d *caddyfile.Dispenser) e...
type ClientIPHashSelection (line 398) | type ClientIPHashSelection struct
method CaddyModule (line 401) | func (ClientIPHashSelection) CaddyModule() caddy.ModuleInfo {
method Select (line 409) | func (ClientIPHashSelection) Select(pool UpstreamPool, req *http.Reque...
method UnmarshalCaddyfile (line 419) | func (r *ClientIPHashSelection) UnmarshalCaddyfile(d *caddyfile.Dispen...
type URIHashSelection (line 429) | type URIHashSelection struct
method CaddyModule (line 432) | func (URIHashSelection) CaddyModule() caddy.ModuleInfo {
method Select (line 440) | func (URIHashSelection) Select(pool UpstreamPool, req *http.Request, _...
method UnmarshalCaddyfile (line 445) | func (r *URIHashSelection) UnmarshalCaddyfile(d *caddyfile.Dispenser) ...
type QueryHashSelection (line 455) | type QueryHashSelection struct
method CaddyModule (line 465) | func (QueryHashSelection) CaddyModule() caddy.ModuleInfo {
method Provision (line 473) | func (s *QueryHashSelection) Provision(ctx caddy.Context) error {
method Select (line 489) | func (s QueryHashSelection) Select(pool UpstreamPool, req *http.Reques...
method UnmarshalCaddyfile (line 505) | func (s *QueryHashSelection) UnmarshalCaddyfile(d *caddyfile.Dispenser...
type HeaderHashSelection (line 536) | type HeaderHashSelection struct
method CaddyModule (line 546) | func (HeaderHashSelection) CaddyModule() caddy.ModuleInfo {
method Provision (line 554) | func (s *HeaderHashSelection) Provision(ctx caddy.Context) error {
method Select (line 570) | func (s HeaderHashSelection) Select(pool UpstreamPool, req *http.Reque...
method UnmarshalCaddyfile (line 585) | func (s *HeaderHashSelection) UnmarshalCaddyfile(d *caddyfile.Dispense...
type CookieHashSelection (line 616) | type CookieHashSelection struct
method CaddyModule (line 630) | func (CookieHashSelection) CaddyModule() caddy.ModuleInfo {
method Provision (line 638) | func (s *CookieHashSelection) Provision(ctx caddy.Context) error {
method Select (line 654) | func (s CookieHashSelection) Select(pool UpstreamPool, req *http.Reque...
method UnmarshalCaddyfile (line 716) | func (s *CookieHashSelection) UnmarshalCaddyfile(d *caddyfile.Dispense...
function hashCookie (line 768) | func hashCookie(secret string, data string) (string, error) {
function selectRandomHost (line 778) | func selectRandomHost(pool []*Upstream) *Upstream {
function leastRequests (line 803) | func leastRequests(upstreams []*Upstream) *Upstream {
function hostByHashing (line 834) | func hostByHashing(pool []*Upstream, s string) *Upstream {
function hash (line 856) | func hash(s string) uint64 {
function loadFallbackPolicy (line 862) | func loadFallbackPolicy(d *caddyfile.Dispenser) (json.RawMessage, error) {
FILE: modules/caddyhttp/reverseproxy/selectionpolicies_test.go
function testPool (line 28) | func testPool() UpstreamPool {
function TestRoundRobinPolicy (line 36) | func TestRoundRobinPolicy(t *testing.T) {
function TestWeightedRoundRobinPolicy (line 77) | func TestWeightedRoundRobinPolicy(t *testing.T) {
function TestWeightedRoundRobinPolicyWithZeroWeight (line 134) | func TestWeightedRoundRobinPolicyWithZeroWeight(t *testing.T) {
function TestLeastConnPolicy (line 186) | func TestLeastConnPolicy(t *testing.T) {
function TestIPHashPolicy (line 204) | func TestIPHashPolicy(t *testing.T) {
function TestClientIPHashPolicy (line 346) | func TestClientIPHashPolicy(t *testing.T) {
function TestFirstPolicy (line 489) | func TestFirstPolicy(t *testing.T) {
function TestQueryHashPolicy (line 506) | func TestQueryHashPolicy(t *testing.T) {
function TestURIHashPolicy (line 582) | func TestURIHashPolicy(t *testing.T) {
function TestLeastRequests (line 637) | func TestLeastRequests(t *testing.T) {
function TestRandomChoicePolicy (line 660) | func TestRandomChoicePolicy(t *testing.T) {
function TestCookieHashPolicy (line 686) | func TestCookieHashPolicy(t *testing.T) {
function TestCookieHashPolicyWithSecureRequest (line 745) | func TestCookieHashPolicyWithSecureRequest(t *testing.T) {
function TestCookieHashPolicyWithFirstFallback (line 796) | func TestCookieHashPolicyWithFirstFallback(t *testing.T) {
FILE: modules/caddyhttp/reverseproxy/streaming.go
type h2ReadWriteCloser (line 41) | type h2ReadWriteCloser struct
method Write (line 46) | func (rwc h2ReadWriteCloser) Write(p []byte) (n int, err error) {
method handleUpgradeResponse (line 60) | func (h *Handler) handleUpgradeResponse(logger *zap.Logger, wg *sync.Wai...
method flushInterval (line 238) | func (h Handler) flushInterval(req *http.Request, res *http.Response) ti...
method isBidirectionalStream (line 264) | func (h Handler) isBidirectionalStream(req *http.Request, res *http.Resp...
method copyResponse (line 278) | func (h Handler) copyResponse(dst http.ResponseWriter, src io.Reader, fl...
method copyBuffer (line 320) | func (h Handler) copyBuffer(dst io.Writer, src io.Reader, buf []byte, lo...
method registerConnection (line 378) | func (h *Handler) registerConnection(conn io.ReadWriteCloser, gracefulCl...
method closeConnections (line 398) | func (h *Handler) closeConnections() error {
method cleanupConnections (line 424) | func (h *Handler) cleanupConnections() error {
function writeCloseControl (line 458) | func writeCloseControl(conn io.Writer, isClient bool) error {
function maskBytes (line 509) | func maskBytes(key [4]byte, pos int, b []byte) int {
function newMaskKey (line 553) | func newMaskKey() [4]byte {
function isWebsocket (line 560) | func isWebsocket(r *http.Request) bool {
type openConnection (line 567) | type openConnection struct
type maxLatencyWriter (line 572) | type maxLatencyWriter struct
method Write (line 583) | func (m *maxLatencyWriter) Write(p []byte) (n int, err error) {
method delayedFlush (line 612) | func (m *maxLatencyWriter) delayedFlush() {
method stop (line 625) | func (m *maxLatencyWriter) stop() {
type switchProtocolCopier (line 636) | type switchProtocolCopier struct
method copyFromBackend (line 641) | func (c switchProtocolCopier) copyFromBackend(errc chan<- error) {
method copyToBackend (line 647) | func (c switchProtocolCopier) copyToBackend(errc chan<- error) {
constant defaultBufferSize (line 665) | defaultBufferSize = 32 * 1024
constant wordSize (line 666) | wordSize = int(unsafe.Sizeof(uintptr(0)))
FILE: modules/caddyhttp/reverseproxy/streaming_test.go
function TestHandlerCopyResponse (line 12) | func TestHandlerCopyResponse(t *testing.T) {
FILE: modules/caddyhttp/reverseproxy/upstreams.go
function init (line 20) | func init() {
type SRVUpstreams (line 38) | type SRVUpstreams struct
method CaddyModule (line 84) | func (SRVUpstreams) CaddyModule() caddy.ModuleInfo {
method Provision (line 91) | func (su *SRVUpstreams) Provision(ctx caddy.Context) error {
method GetUpstreams (line 122) | func (su SRVUpstreams) GetUpstreams(r *http.Request) ([]*Upstream, err...
method String (line 208) | func (su SRVUpstreams) String() string {
method expandedAddr (line 219) | func (su SRVUpstreams) expandedAddr(r *http.Request) (addr, service, p...
method formattedAddr (line 234) | func (SRVUpstreams) formattedAddr(service, proto, name string) string {
type srvLookup (line 238) | type srvLookup struct
method isFresh (line 244) | func (sl srvLookup) isFresh() bool {
type IPVersions (line 248) | type IPVersions struct
function resolveIpVersion (line 253) | func resolveIpVersion(versions *IPVersions) string {
type AUpstreams (line 269) | type AUpstreams struct
method CaddyModule (line 304) | func (AUpstreams) CaddyModule() caddy.ModuleInfo {
method Provision (line 311) | func (au *AUpstreams) Provision(ctx caddy.Context) error {
method GetUpstreams (line 345) | func (au AUpstreams) GetUpstreams(r *http.Request) ([]*Upstream, error) {
method String (line 423) | func (au AUpstreams) String() string { return net.JoinHostPort(au.Name...
type aLookup (line 425) | type aLookup struct
method isFresh (line 431) | func (al aLookup) isFresh() bool {
type MultiUpstreams (line 447) | type MultiUpstreams struct
method CaddyModule (line 458) | func (MultiUpstreams) CaddyModule() caddy.ModuleInfo {
method Provision (line 465) | func (mu *MultiUpstreams) Provision(ctx caddy.Context) error {
method GetUpstreams (line 481) | func (mu MultiUpstreams) GetUpstreams(r *http.Request) ([]*Upstream, e...
type UpstreamResolver (line 513) | type UpstreamResolver struct
method ParseAddresses (line 525) | func (u *UpstreamResolver) ParseAddresses() error {
function allNew (line 539) | func allNew(upstreams []Upstream) []*Upstream {
FILE: modules/caddyhttp/reverseproxy/upstreams_test.go
function TestResolveIpVersion (line 5) | func TestResolveIpVersion(t *testing.T) {
FILE: modules/caddyhttp/rewrite/caddyfile.go
function init (line 28) | func init() {
function parseCaddyfileRewrite (line 41) | func parseCaddyfileRewrite(h httpcaddyfile.Helper) ([]httpcaddyfile.Conf...
function parseCaddyfileMethod (line 75) | func parseCaddyfileMethod(h httpcaddyfile.Helper) (caddyhttp.MiddlewareH...
function parseCaddyfileURI (line 97) | func parseCaddyfileURI(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHand...
function applyQueryOps (line 191) | func applyQueryOps(h httpcaddyfile.Helper, qo *queryOps, args []string) ...
function parseCaddyfileHandlePath (line 234) | func parseCaddyfileHandlePath(h httpcaddyfile.Helper) ([]httpcaddyfile.C...
FILE: modules/caddyhttp/rewrite/rewrite.go
function init (line 31) | func init() {
type Rewrite (line 54) | type Rewrite struct
method CaddyModule (line 99) | func (Rewrite) CaddyModule() caddy.ModuleInfo {
method Provision (line 107) | func (rewr *Rewrite) Provision(ctx caddy.Context) error {
method ServeHTTP (line 132) | func (rewr Rewrite) ServeHTTP(w http.ResponseWriter, r *http.Request, ...
method Rewrite (line 158) | func (rewr Rewrite) Rewrite(r *http.Request, repl *caddy.Replacer) bool {
function buildQueryString (line 307) | func buildQueryString(qs string, repl *caddy.Replacer) string {
function trimPathPrefix (line 379) | func trimPathPrefix(escapedPath, prefix string) string {
function reverse (line 414) | func reverse(s string) string {
type substrReplacer (line 423) | type substrReplacer struct
method do (line 436) | func (rep substrReplacer) do(r *http.Request, repl *caddy.Replacer) {
type regexReplacer (line 459) | type regexReplacer struct
method do (line 470) | func (rep regexReplacer) do(r *http.Request, repl *caddy.Replacer) {
function changePath (line 480) | func changePath(req *http.Request, newVal func(pathOrRawPath string) str...
type queryOps (line 494) | type queryOps struct
method do (line 524) | func (q *queryOps) do(r *http.Request, repl *caddy.Replacer) {
type queryOpsArguments (line 595) | type queryOpsArguments struct
type queryOpsReplacement (line 605) | type queryOpsReplacement struct
method Provision (line 513) | func (replacement *queryOpsReplacement) Provision(_ caddy.Context) err...
FILE: modules/caddyhttp/rewrite/rewrite_test.go
function TestRewrite (line 25) | func TestRewrite(t *testing.T) {
function newRequest (line 400) | func newRequest(t *testing.T, method, uri string) *http.Request {
function reqEqual (line 410) | func reqEqual(r1, r2 *http.Request) bool {
FILE: modules/caddyhttp/routes.go
type Route (line 30) | type Route struct
method Empty (line 107) | func (r Route) Empty() bool {
method String (line 116) | func (r Route) String() string {
method Provision (line 130) | func (r *Route) Provision(ctx caddy.Context, metrics *Metrics) error {
method ProvisionMatchers (line 142) | func (r *Route) ProvisionMatchers(ctx caddy.Context) error {
method ProvisionHandlers (line 159) | func (r *Route) ProvisionHandlers(ctx caddy.Context, metrics *Metrics)...
method Compile (line 189) | func (r Route) Compile(next Handler) Handler {
type RouteList (line 195) | type RouteList
method Provision (line 198) | func (routes RouteList) Provision(ctx caddy.Context) error {
method ProvisionMatchers (line 210) | func (routes RouteList) ProvisionMatchers(ctx caddy.Context) error {
method ProvisionHandlers (line 224) | func (routes RouteList) ProvisionHandlers(ctx caddy.Context, metrics *...
method Compile (line 238) | func (routes RouteList) Compile(next Handler) Handler {
function wrapRoute (line 255) | func wrapRoute(route Route) Middleware {
function wrapMiddleware (line 330) | func wrapMiddleware(ctx caddy.Context, mh MiddlewareHandler) Middleware {
type MatcherSet (line 345) | type MatcherSet
method Match (line 349) | func (mset MatcherSet) Match(r *http.Request) bool {
method MatchWithError (line 370) | func (mset MatcherSet) MatchWithError(r *http.Request) (bool, error) {
type RawMatcherSets (line 399) | type RawMatcherSets
type MatcherSets (line 404) | type MatcherSets
method AnyMatch (line 411) | func (ms MatcherSets) AnyMatch(req *http.Request) bool {
method AnyMatchWithError (line 429) | func (ms MatcherSets) AnyMatchWithError(req *http.Request) (bool, erro...
method FromInterface (line 440) | func (ms *MatcherSets) FromInterface(matcherSets any) error {
method String (line 460) | func (ms MatcherSets) String() string {
FILE: modules/caddyhttp/server.go
type Server (line 47) | type Server struct
method ServeHTTP (line 310) | func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
method serveHTTP (line 471) | func (s *Server) serveHTTP(w http.ResponseWriter, r *http.Request) err...
method wrapPrimaryRoute (line 502) | func (s *Server) wrapPrimaryRoute(stack Handler) Handler {
method enforcementHandler (line 510) | func (s *Server) enforcementHandler(w http.ResponseWriter, r *http.Req...
method listenersUseAnyPortOtherThan (line 533) | func (s *Server) listenersUseAnyPortOtherThan(otherPort int) bool {
method hasListenerAddress (line 550) | func (s *Server) hasListenerAddress(fullAddr string) bool {
method hasTLSClientAuth (line 606) | func (s *Server) hasTLSClientAuth() bool {
method findLastRouteWithHostMatcher (line 617) | func (s *Server) findLastRouteWithHostMatcher() int {
method serveHTTP3 (line 657) | func (s *Server) serveHTTP3(addr caddy.NetworkAddress, tlsCfg *tls.Con...
method configureServer (line 691) | func (s *Server) configureServer(server *http.Server) {
method RegisterConnState (line 721) | func (s *Server) RegisterConnState(f func(net.Conn, http.ConnState)) {
method RegisterConnContext (line 726) | func (s *Server) RegisterConnContext(f func(ctx context.Context, c net...
method RegisterOnShutdown (line 731) | func (s *Server) RegisterOnShutdown(f func()) {
method RegisterOnStop (line 738) | func (s *Server) RegisterOnStop(f func(context.Context) error) {
method shouldLogRequest (line 789) | func (s *Server) shouldLogRequest(r *http.Request) bool {
method logTrace (line 819) | func (s *Server) logTrace(mh MiddlewareHandler) {
method logRequest (line 829) | func (s *Server) logRequest(
method protocol (line 901) | func (s *Server) protocol(proto string) bool {
method Listeners (line 925) | func (s *Server) Listeners() []net.Listener { return s.listeners }
method Name (line 928) | func (s *Server) Name() string { return s.name }
type HTTPErrorConfig (line 744) | type HTTPErrorConfig struct
method WithError (line 764) | func (*HTTPErrorConfig) WithError(r *http.Request, err error) *http.Re...
function PrepareRequest (line 932) | func PrepareRequest(r *http.Request, repl *caddy.Replacer, w http.Respon...
function originalRequest (line 965) | func originalRequest(req *http.Request, urlCopy *url.URL) http.Request {
function determineTrustedProxy (line 979) | func determineTrustedProxy(r *http.Request, s *Server) (bool, string) {
function isTrustedClientIP (line 1031) | func isTrustedClientIP(ipAddr netip.Addr, trusted []netip.Prefix) bool {
function trustedRealClientIP (line 1041) | func trustedRealClientIP(r *http.Request, headers []string, clientIP str...
function strictUntrustedClientIp (line 1085) | func strictUntrustedClientIp(r *http.Request, headers []string, trusted ...
function cloneURL (line 1115) | func cloneURL(from, to *url.URL) {
type lengthReader (line 1126) | type lengthReader struct
method Read (line 1131) | func (r *lengthReader) Read(b []byte) (int, error) {
method Close (line 1137) | func (r *lengthReader) Close() error {
constant ServerCtxKey (line 1144) | ServerCtxKey caddy.CtxKey = "server"
constant VarsCtxKey (line 1147) | VarsCtxKey caddy.CtxKey = "vars"
constant OriginalRequestCtxKey (line 1151) | OriginalRequestCtxKey caddy.CtxKey = "original_request"
constant ConnCtxKey (line 1156) | ConnCtxKey caddy.CtxKey = "conn"
constant tlsConnectionStateFuncCtxKey (line 1159) | tlsConnectionStateFuncCtxKey caddy.CtxKey = "tls_connection_state_func"
constant TrustedProxyVarKey (line 1162) | TrustedProxyVarKey string = "trusted_proxy"
constant ClientIPVarKey (line 1165) | ClientIPVarKey string = "client_ip"
function RegisterNetworkHTTP3 (line 1184) | func RegisterNetworkHTTP3(originalNetwork, h3Network string) {
function getHTTP3Network (line 1191) | func getHTTP3Network(originalNetwork string) (string, error) {
FILE: modules/caddyhttp/server_test.go
type writeFunc (line 18) | type writeFunc
type nopSyncer (line 20) | type nopSyncer
method Write (line 22) | func (n nopSyncer) Write(p []byte) (int, error) {
method Sync (line 26) | func (n nopSyncer) Sync() error {
function testLogger (line 32) | func testLogger(wf writeFunc) *zap.Logger {
function TestServer_LogRequest (line 47) | func TestServer_LogRequest(t *testing.T) {
function TestServer_LogRequest_WithTrace (line 72) | func TestServer_LogRequest_WithTrace(t *testing.T) {
function BenchmarkServer_LogRequest (line 102) | func BenchmarkServer_LogRequest(b *testing.B) {
function BenchmarkServer_LogRequest_NopLogger (line 124) | func BenchmarkServer_LogRequest_NopLogger(b *testing.B) {
function BenchmarkServer_LogRequest_WithTrace (line 145) | func BenchmarkServer_LogRequest_WithTrace(b *testing.B) {
function TestServer_TrustedRealClientIP_NoTrustedHeaders (line 169) | func TestServer_TrustedRealClientIP_NoTrustedHeaders(t *testing.T) {
function TestServer_TrustedRealClientIP_OneTrustedHeaderEmpty (line 177) | func TestServer_TrustedRealClientIP_OneTrustedHeaderEmpty(t *testing.T) {
function TestServer_TrustedRealClientIP_OneTrustedHeaderInvalid (line 185) | func TestServer_TrustedRealClientIP_OneTrustedHeaderInvalid(t *testing.T) {
function TestServer_TrustedRealClientIP_OneTrustedHeaderValid (line 194) | func TestServer_TrustedRealClientIP_OneTrustedHeaderValid(t *testing.T) {
function TestServer_TrustedRealClientIP_OneTrustedHeaderValidArray (line 203) | func TestServer_TrustedRealClientIP_OneTrustedHeaderValidArray(t *testin...
function TestServer_TrustedRealClientIP_IncludesPort (line 212) | func TestServer_TrustedRealClientIP_IncludesPort(t *testing.T) {
function TestServer_TrustedRealClientIP_SkipsInvalidIps (line 221) | func TestServer_TrustedRealClientIP_SkipsInvalidIps(t *testing.T) {
function TestServer_TrustedRealClientIP_MultipleTrustedHeaderValidArray (line 230) | func TestServer_TrustedRealClientIP_MultipleTrustedHeaderValidArray(t *t...
function TestServer_DetermineTrustedProxy_NoConfig (line 244) | func TestServer_DetermineTrustedProxy_NoConfig(t *testing.T) {
function TestServer_DetermineTrustedProxy_NoConfigIpv6 (line 256) | func TestServer_DetermineTrustedProxy_NoConfigIpv6(t *testing.T) {
function TestServer_DetermineTrustedProxy_NoConfigIpv6Zones (line 268) | func TestServer_DetermineTrustedProxy_NoConfigIpv6Zones(t *testing.T) {
function TestServer_DetermineTrustedProxy_TrustedLoopback (line 280) | func TestServer_DetermineTrustedProxy_TrustedLoopback(t *testing.T) {
function TestServer_DetermineTrustedProxy_UnixSocket (line 300) | func TestServer_DetermineTrustedProxy_UnixSocket(t *testing.T) {
function TestServer_DetermineTrustedProxy_UnixSocketStrict (line 316) | func TestServer_DetermineTrustedProxy_UnixSocketStrict(t *testing.T) {
function TestServer_DetermineTrustedProxy_UntrustedPrefix (line 333) | func TestServer_DetermineTrustedProxy_UntrustedPrefix(t *testing.T) {
function TestServer_DetermineTrustedProxy_MultipleTrustedPrefixes (line 353) | func TestServer_DetermineTrustedProxy_MultipleTrustedPrefixes(t *testing...
function TestServer_DetermineTrustedProxy_MultipleTrustedClientHeaders (line 374) | func TestServer_DetermineTrustedProxy_MultipleTrustedClientHeaders(t *te...
function TestServer_DetermineTrustedProxy_MatchLeftMostValidIp (line 396) | func TestServer_DetermineTrustedProxy_MatchLeftMostValidIp(t *testing.T) {
function TestServer_DetermineTrustedProxy_MatchRightMostUntrusted (line 416) | func TestServer_DetermineTrustedProxy_MatchRightMostUntrusted(t *testing...
function TestServer_DetermineTrustedProxy_MatchRightMostUntrustedSkippingEmpty (line 437) | func TestServer_DetermineTrustedProxy_MatchRightMostUntrustedSkippingEmp...
function TestServer_DetermineTrustedProxy_MatchRightMostUntrustedSkippingTrusted (line 459) | func TestServer_DetermineTrustedProxy_MatchRightMostUntrustedSkippingTru...
function TestServer_DetermineTrustedProxy_MatchRightMostUntrustedFirst (line 481) | func TestServer_DetermineTrustedProxy_MatchRightMostUntrustedFirst(t *te...
FILE: modules/caddyhttp/staticerror.go
function init (line 26) | func init() {
type StaticError (line 37) | type StaticError struct
method CaddyModule (line 47) | func (StaticError) CaddyModule() caddy.ModuleInfo {
method UnmarshalCaddyfile (line 62) | func (e *StaticError) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
method ServeHTTP (line 97) | func (e StaticError) ServeHTTP(w http.ResponseWriter, r *http.Request,...
FILE: modules/caddyhttp/staticresp.go
function init (line 41) | func init() {
type StaticResponse (line 89) | type StaticResponse struct
method CaddyModule (line 125) | func (StaticResponse) CaddyModule() caddy.ModuleInfo {
method UnmarshalCaddyfile (line 141) | func (s *StaticResponse) UnmarshalCaddyfile(d *caddyfile.Dispenser) er...
method ServeHTTP (line 181) | func (s StaticResponse) ServeHTTP(w http.ResponseWriter, r *http.Reque...
function buildHTTPServer (line 260) | func buildHTTPServer(
function cmdRespond (line 316) | func cmdRespond(fl caddycmd.Flags) (int, error) {
FILE: modules/caddyhttp/staticresp_test.go
function TestStaticResponseHandler (line 28) | func TestStaticResponseHandler(t *testing.T) {
function fakeRequest (line 60) | func fakeRequest() *http.Request {
FILE: modules/caddyhttp/subroute.go
function init (line 24) | func init() {
type Subroute (line 38) | type Subroute struct
method CaddyModule (line 48) | func (Subroute) CaddyModule() caddy.ModuleInfo {
method Provision (line 56) | func (sr *Subroute) Provision(ctx caddy.Context) error {
method ServeHTTP (line 72) | func (sr *Subroute) ServeHTTP(w http.ResponseWriter, r *http.Request, ...
FILE: modules/caddyhttp/templates/caddyfile.go
function init (line 25) | func init() {
function parseCaddyfile (line 36) | func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler...
FILE: modules/caddyhttp/templates/frontmatter.go
function extractFrontMatter (line 13) | func extractFrontMatter(input string) (map[string]any, string, error) {
function yamlFrontMatter (line 81) | func yamlFrontMatter(input []byte) (map[string]any, error) {
function tomlFrontMatter (line 87) | func tomlFrontMatter(input []byte) (map[string]any, error) {
function jsonFrontMatter (line 93) | func jsonFrontMatter(input []byte) (map[string]any, error) {
type parsedMarkdownDoc (line 101) | type parsedMarkdownDoc struct
type frontMatterType (line 106) | type frontMatterType struct
FILE: modules/caddyhttp/templates/frontmatter_fuzz.go
function FuzzExtractFrontMatter (line 19) | func FuzzExtractFrontMatter(data []byte) int {
FILE: modules/caddyhttp/templates/templates.go
function init (line 32) | func init() {
type Templates (line 345) | type Templates struct
method CaddyModule (line 375) | func (Templates) CaddyModule() caddy.ModuleInfo {
method Provision (line 383) | func (t *Templates) Provision(ctx caddy.Context) error {
method Validate (line 403) | func (t *Templates) Validate() error {
method ServeHTTP (line 410) | func (t *Templates) ServeHTTP(w http.ResponseWriter, r *http.Request, ...
method executeTemplate (line 455) | func (t *Templates) executeTemplate(rr caddyhttp.ResponseRecorder, r *...
type CustomFunctions (line 369) | type CustomFunctions interface
type virtualResponseWriter (line 486) | type virtualResponseWriter struct
method Header (line 492) | func (vrw *virtualResponseWriter) Header() http.Header {
method WriteHeader (line 496) | func (vrw *virtualResponseWriter) WriteHeader(statusCode int) {
method Write (line 500) | func (vrw *virtualResponseWriter) Write(data []byte) (int, error) {
FILE: modules/caddyhttp/templates/tplcontext.go
type TemplateContext (line 49) | type TemplateContext struct
method NewTemplate (line 62) | func (c *TemplateContext) NewTemplate(tplName string) *template.Templa...
method OriginalReq (line 103) | func (c TemplateContext) OriginalReq() http.Request {
method funcInclude (line 112) | func (c TemplateContext) funcInclude(filename string, args ...any) (st...
method funcReadFile (line 136) | func (c TemplateContext) funcReadFile(filename string) (string, error) {
method readFileToBuffer (line 150) | func (c TemplateContext) readFileToBuffer(filename string, bodyBuf *by...
method funcHTTPInclude (line 173) | func (c TemplateContext) funcHTTPInclude(uri string) (string, error) {
method funcImport (line 223) | func (c *TemplateContext) funcImport(filename string) (string, error) {
method executeTemplateInBuffer (line 240) | func (c *TemplateContext) executeTemplateInBuffer(tplName string, buf ...
method funcPlaceholder (line 253) | func (c TemplateContext) funcPlaceholder(name string) string {
method funcEnv (line 265) | func (TemplateContext) funcEnv(varName string) string {
method Cookie (line 270) | func (c TemplateContext) Cookie(name string) string {
method RemoteIP (line 281) | func (c TemplateContext) RemoteIP() string {
method ClientIP (line 292) | func (c TemplateContext) ClientIP() string {
method Host (line 303) | func (c TemplateContext) Host() (string, error) {
method funcStripHTML (line 317) | func (TemplateContext) funcStripHTML(s string) string {
method funcMarkdown (line 350) | func (TemplateContext) funcMarkdown(input any) (string, error) {
method funcSplitFrontMatter (line 386) | func (TemplateContext) funcSplitFrontMatter(input any) (parsedMarkdown...
method funcListFiles (line 396) | func (c TemplateContext) funcListFiles(name string) ([]string, error) {
method funcFileExists (line 429) | func (c TemplateContext) funcFileExists(filename string) (bool, error) {
method funcFileStat (line 442) | func (c TemplateContext) funcFileStat(filename string) (fs.FileInfo, e...
method funcHTTPError (line 458) | func (c TemplateContext) funcHTTPError(statusCode int) (bool, error) {
method funcHumanize (line 478) | func (c TemplateContext) funcHumanize(formatType, data string) (string...
method funcMaybe (line 520) | func (c TemplateContext) funcMaybe(functionName string, args ...any) (...
type WrappedHeader (line 555) | type WrappedHeader struct
method Add (line 560) | func (h WrappedHeader) Add(field, val string) string {
method Set (line 568) | func (h WrappedHeader) Set(field, va
Condensed preview — 588 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (3,770K chars).
[
{
"path": ".editorconfig",
"chars": 96,
"preview": "[*]\nend_of_line = lf\n\n[caddytest/integration/caddyfile_adapt/*.caddyfiletest]\nindent_style = tab"
},
{
"path": ".gitattributes",
"chars": 16,
"preview": "*.go text eol=lf"
},
{
"path": ".github/CONTRIBUTING.md",
"chars": 18242,
"preview": "Contributing to Caddy\n=====================\n\nWelcome! Thank you for choosing to be a part of our community. Caddy wouldn"
},
{
"path": ".github/FUNDING.yml",
"chars": 719,
"preview": "# These are supported funding model platforms\n\ngithub: [mholt] # Replace with up to 4 GitHub Sponsors-enabled usernames "
},
{
"path": ".github/ISSUE_TEMPLATE/ISSUE.yml",
"chars": 1317,
"preview": "name: Issue\ndescription: An actionable development item, like a bug report or feature request\nbody:\n - type: markdown\n "
},
{
"path": ".github/ISSUE_TEMPLATE/config.yml",
"chars": 184,
"preview": "blank_issues_enabled: false\ncontact_links:\n - name: Caddy forum\n url: https://caddy.community\n about: If you have"
},
{
"path": ".github/SECURITY.md",
"chars": 5554,
"preview": "# Security Policy\n\nThe Caddy project would like to make sure that it stays on top of all relevant and practically-exploi"
},
{
"path": ".github/dependabot.yml",
"chars": 413,
"preview": "---\nversion: 2\nupdates:\n - package-ecosystem: \"github-actions\"\n directory: \"/\"\n open-pull-requests-limit: 1\n g"
},
{
"path": ".github/pull_request_template.md",
"chars": 844,
"preview": "\n\n\n## Assistance Disclosure\n<!--\nThank you for contributing! Please note:\n\nThe use of AI/LLM tools is allowed so long as"
},
{
"path": ".github/workflows/ai.yml",
"chars": 910,
"preview": "name: AI Moderator\npermissions: read-all\non:\n issues:\n types: [opened]\n issue_comment:\n types: [created]\n pull_"
},
{
"path": ".github/workflows/auto-release-pr.yml",
"chars": 9350,
"preview": "name: Release Proposal Approval Tracker\n\non:\n pull_request_review:\n types: [submitted, dismissed]\n pull_request:\n "
},
{
"path": ".github/workflows/ci.yml",
"chars": 8633,
"preview": "# Used as inspiration: https://github.com/mvdan/github-actions-golang\n\nname: Tests\n\non:\n push:\n branches:\n - ma"
},
{
"path": ".github/workflows/cross-build.yml",
"chars": 2036,
"preview": "name: Cross-Build\n\non:\n push:\n branches:\n - master\n - 2.*\n pull_request:\n branches:\n - master\n "
},
{
"path": ".github/workflows/lint.yml",
"chars": 3049,
"preview": "name: Lint\n\non:\n push:\n branches:\n - master\n - 2.*\n pull_request:\n branches:\n - master\n - 2."
},
{
"path": ".github/workflows/release-proposal.yml",
"chars": 9978,
"preview": "name: Release Proposal\n\n# This workflow creates a release proposal as a PR that requires approval from maintainers\n# Tri"
},
{
"path": ".github/workflows/release.yml",
"chars": 23423,
"preview": "name: Release\n\non:\n push:\n tags:\n - 'v*.*.*'\n\nenv:\n # https://github.com/actions/setup-go/issues/491\n GOTOOLC"
},
{
"path": ".github/workflows/release_published.yml",
"chars": 1378,
"preview": "name: Release Published\n\n# Event payload: https://developer.github.com/webhooks/event-payloads/#release\non:\n release:\n "
},
{
"path": ".github/workflows/scorecard.yml",
"chars": 3741,
"preview": "# This workflow uses actions that are not certified by GitHub. They are provided\n# by a third-party and are governed by "
},
{
"path": ".gitignore",
"chars": 342,
"preview": "_gitignore/\n*.log\nCaddyfile\nCaddyfile.*\n!caddyfile/\n!caddyfile.go\n\n# artifacts from pprof tooling\n*.prof\n*.test\n\n# build"
},
{
"path": ".golangci.yml",
"chars": 3133,
"preview": "version: \"2\"\nrun:\n issues-exit-code: 1\n tests: false\n build-tags:\n - nobadger\n - nomysql\n - nopgx\noutput:\n "
},
{
"path": ".goreleaser.yml",
"chars": 5992,
"preview": "version: 2\n\nbefore:\n hooks:\n # The build is done in this particular way to build Caddy in a designated directory nam"
},
{
"path": ".pre-commit-config.yaml",
"chars": 481,
"preview": "repos:\n- repo: https://github.com/gitleaks/gitleaks\n rev: v8.16.3\n hooks:\n - id: gitleaks\n- repo: https://github.com/"
},
{
"path": "AUTHORS",
"chars": 399,
"preview": "# This is the official list of Caddy Authors for copyright purposes.\n# Authors may be either individual people or legal "
},
{
"path": "LICENSE",
"chars": 11358,
"preview": "\n Apache License\n Version 2.0, January 2004\n "
},
{
"path": "README.md",
"chars": 12719,
"preview": "<p align=\"center\">\n\t<a href=\"https://caddyserver.com\">\n\t\t<picture>\n\t\t\t<source media=\"(prefers-color-scheme: dark)\" srcse"
},
{
"path": "admin.go",
"chars": 48380,
"preview": "// Copyright 2015 Matthew Holt and The Caddy Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\""
},
{
"path": "admin_test.go",
"chars": 24592,
"preview": "// Copyright 2015 Matthew Holt and The Caddy Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\""
},
{
"path": "caddy.go",
"chars": 41594,
"preview": "// Copyright 2015 Matthew Holt and The Caddy Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\""
},
{
"path": "caddy_test.go",
"chars": 2198,
"preview": "// Copyright 2015 Matthew Holt and The Caddy Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\""
},
{
"path": "caddyconfig/caddyfile/adapter.go",
"chars": 4853,
"preview": "// Copyright 2015 Matthew Holt and The Caddy Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\""
},
{
"path": "caddyconfig/caddyfile/dispenser.go",
"chars": 15636,
"preview": "// Copyright 2015 Matthew Holt and The Caddy Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\""
},
{
"path": "caddyconfig/caddyfile/dispenser_test.go",
"chars": 10267,
"preview": "// Copyright 2015 Matthew Holt and The Caddy Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\""
},
{
"path": "caddyconfig/caddyfile/formatter.go",
"chars": 7378,
"preview": "// Copyright 2015 Matthew Holt and The Caddy Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\""
},
{
"path": "caddyconfig/caddyfile/formatter_fuzz.go",
"chars": 804,
"preview": "// Copyright 2015 Matthew Holt and The Caddy Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\""
},
{
"path": "caddyconfig/caddyfile/formatter_test.go",
"chars": 6740,
"preview": "// Copyright 2015 Matthew Holt and The Caddy Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\""
},
{
"path": "caddyconfig/caddyfile/importargs.go",
"chars": 5243,
"preview": "// Copyright 2015 Matthew Holt and The Caddy Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\""
},
{
"path": "caddyconfig/caddyfile/importgraph.go",
"chars": 2579,
"preview": "// Copyright 2015 Matthew Holt and The Caddy Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\""
},
{
"path": "caddyconfig/caddyfile/lexer.go",
"chars": 10985,
"preview": "// Copyright 2015 Matthew Holt and The Caddy Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\""
},
{
"path": "caddyconfig/caddyfile/lexer_fuzz.go",
"chars": 814,
"preview": "// Copyright 2015 Matthew Holt and The Caddy Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\""
},
{
"path": "caddyconfig/caddyfile/lexer_test.go",
"chars": 12987,
"preview": "// Copyright 2015 Matthew Holt and The Caddy Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\""
},
{
"path": "caddyconfig/caddyfile/parse.go",
"chars": 23539,
"preview": "// Copyright 2015 Matthew Holt and The Caddy Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\""
},
{
"path": "caddyconfig/caddyfile/parse_test.go",
"chars": 21069,
"preview": "// Copyright 2015 Matthew Holt and The Caddy Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\""
},
{
"path": "caddyconfig/caddyfile/testdata/empty.txt",
"chars": 0,
"preview": ""
},
{
"path": "caddyconfig/caddyfile/testdata/glob/.dotfile.txt",
"chars": 27,
"preview": "host1 {\n\tdir1\n\tdir2 arg1\n}\n"
},
{
"path": "caddyconfig/caddyfile/testdata/glob/import_test1.txt",
"chars": 19,
"preview": "dir2 arg1 arg2\ndir3"
},
{
"path": "caddyconfig/caddyfile/testdata/import_args0.txt",
"chars": 9,
"preview": "{args[0]}"
},
{
"path": "caddyconfig/caddyfile/testdata/import_args1.txt",
"chars": 19,
"preview": "{args[0]} {args[1]}"
},
{
"path": "caddyconfig/caddyfile/testdata/import_glob0.txt",
"chars": 44,
"preview": "glob0.host0 {\n\tdir2 arg1\n}\n\nglob0.host1 {\n}\n"
},
{
"path": "caddyconfig/caddyfile/testdata/import_glob1.txt",
"chars": 33,
"preview": "glob1.host0 {\n\tdir1\n\tdir2 arg1\n}\n"
},
{
"path": "caddyconfig/caddyfile/testdata/import_glob2.txt",
"chars": 27,
"preview": "glob2.host0 {\n\tdir2 arg1\n}\n"
},
{
"path": "caddyconfig/caddyfile/testdata/import_recursive0.txt",
"chars": 28,
"preview": "import import_recursive0.txt"
},
{
"path": "caddyconfig/caddyfile/testdata/import_recursive1.txt",
"chars": 28,
"preview": "import import_recursive2.txt"
},
{
"path": "caddyconfig/caddyfile/testdata/import_recursive2.txt",
"chars": 28,
"preview": "import import_recursive3.txt"
},
{
"path": "caddyconfig/caddyfile/testdata/import_recursive3.txt",
"chars": 28,
"preview": "import import_recursive1.txt"
},
{
"path": "caddyconfig/caddyfile/testdata/import_test1.txt",
"chars": 19,
"preview": "dir2 arg1 arg2\ndir3"
},
{
"path": "caddyconfig/caddyfile/testdata/import_test2.txt",
"chars": 26,
"preview": "host1 {\n\tdir1\n\tdir2 arg1\n}"
},
{
"path": "caddyconfig/caddyfile/testdata/only_white_space.txt",
"chars": 11,
"preview": "\n\n \n \n \n\n\n"
},
{
"path": "caddyconfig/configadapters.go",
"chars": 4529,
"preview": "// Copyright 2015 Matthew Holt and The Caddy Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\""
},
{
"path": "caddyconfig/httpcaddyfile/addresses.go",
"chars": 17240,
"preview": "// Copyright 2015 Matthew Holt and The Caddy Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\""
},
{
"path": "caddyconfig/httpcaddyfile/addresses_fuzz.go",
"chars": 822,
"preview": "// Copyright 2015 Matthew Holt and The Caddy Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\""
},
{
"path": "caddyconfig/httpcaddyfile/addresses_test.go",
"chars": 7466,
"preview": "package httpcaddyfile\n\nimport (\n\t\"testing\"\n)\n\nfunc TestParseAddress(t *testing.T) {\n\tfor i, test := range []struct {\n\t\ti"
},
{
"path": "caddyconfig/httpcaddyfile/builtins.go",
"chars": 35619,
"preview": "// Copyright 2015 Matthew Holt and The Caddy Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\""
},
{
"path": "caddyconfig/httpcaddyfile/builtins_test.go",
"chars": 7282,
"preview": "package httpcaddyfile\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile\"\n\t_ \"gith"
},
{
"path": "caddyconfig/httpcaddyfile/directives.go",
"chars": 20744,
"preview": "// Copyright 2015 Matthew Holt and The Caddy Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\""
},
{
"path": "caddyconfig/httpcaddyfile/directives_test.go",
"chars": 2009,
"preview": "package httpcaddyfile\n\nimport (\n\t\"reflect\"\n\t\"sort\"\n\t\"testing\"\n)\n\nfunc TestHostsFromKeys(t *testing.T) {\n\tfor i, tc := ra"
},
{
"path": "caddyconfig/httpcaddyfile/httptype.go",
"chars": 61091,
"preview": "// Copyright 2015 Matthew Holt and The Caddy Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\""
},
{
"path": "caddyconfig/httpcaddyfile/httptype_test.go",
"chars": 4377,
"preview": "package httpcaddyfile\n\nimport (\n\t\"encoding/json\"\n\t\"testing\"\n\n\t\"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile\"\n\t\""
},
{
"path": "caddyconfig/httpcaddyfile/options.go",
"chars": 17374,
"preview": "// Copyright 2015 Matthew Holt and The Caddy Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\""
},
{
"path": "caddyconfig/httpcaddyfile/options_test.go",
"chars": 3590,
"preview": "package httpcaddyfile\n\nimport (\n\t\"encoding/json\"\n\t\"testing\"\n\n\t\"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile\"\n\t\""
},
{
"path": "caddyconfig/httpcaddyfile/pkiapp.go",
"chars": 6762,
"preview": "// Copyright 2015 Matthew Holt and The Caddy Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\""
},
{
"path": "caddyconfig/httpcaddyfile/pkiapp_test.go",
"chars": 2308,
"preview": "// Copyright 2015 Matthew Holt and The Caddy Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\""
},
{
"path": "caddyconfig/httpcaddyfile/serveroptions.go",
"chars": 12310,
"preview": "// Copyright 2015 Matthew Holt and The Caddy Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\""
},
{
"path": "caddyconfig/httpcaddyfile/shorthands.go",
"chars": 4217,
"preview": "package httpcaddyfile\n\nimport (\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile\"\n)\n\ntype C"
},
{
"path": "caddyconfig/httpcaddyfile/testdata/import_variadic.txt",
"chars": 86,
"preview": "(t2) {\n respond 200 {\n body {args[:]}\n }\n}\n\n:8082 {\n import t2 false\n}"
},
{
"path": "caddyconfig/httpcaddyfile/testdata/import_variadic_snippet.txt",
"chars": 86,
"preview": "(t1) {\n respond 200 {\n body {args[:]}\n }\n}\n\n:8081 {\n import t1 false\n}"
},
{
"path": "caddyconfig/httpcaddyfile/testdata/import_variadic_with_import.txt",
"chars": 144,
"preview": "(t1) {\n respond 200 {\n body {args[:]}\n }\n}\n\n:8081 {\n import t1 false\n}\n\nimport import_variadic.txt\n\n:808"
},
{
"path": "caddyconfig/httpcaddyfile/tlsapp.go",
"chars": 33567,
"preview": "// Copyright 2015 Matthew Holt and The Caddy Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\""
},
{
"path": "caddyconfig/httpcaddyfile/tlsapp_test.go",
"chars": 1241,
"preview": "package httpcaddyfile\n\nimport (\n\t\"testing\"\n\n\t\"github.com/caddyserver/caddy/v2/modules/caddytls\"\n)\n\nfunc TestAutomationPo"
},
{
"path": "caddyconfig/httploader.go",
"chars": 6408,
"preview": "// Copyright 2015 Matthew Holt and The Caddy Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\""
},
{
"path": "caddyconfig/load.go",
"chars": 6186,
"preview": "// Copyright 2015 Matthew Holt and The Caddy Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\""
},
{
"path": "caddytest/a.caddy.localhost.crt",
"chars": 1415,
"preview": "-----BEGIN CERTIFICATE-----\nMIID5zCCAs8CFG4+w/pqR5AZQ+aVB330uRRRKMF0MA0GCSqGSIb3DQEBCwUAMIGv\nMQswCQYDVQQGEwJVUzELMAkGA1U"
},
{
"path": "caddytest/a.caddy.localhost.key",
"chars": 1679,
"preview": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAx32kL3AXuPTjn0Wd0+wN653+urjWMRkWxU5W2NCCNLUDly3o\n194yZKWryLUXu5pMw9mNs4f"
},
{
"path": "caddytest/caddy.ca.cer",
"chars": 1203,
"preview": "-----BEGIN CERTIFICATE-----\nMIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQEL\nBQAwFjEUMBIGA1UEAwwLRWFzeS1"
},
{
"path": "caddytest/caddy.localhost.crt",
"chars": 1415,
"preview": "-----BEGIN CERTIFICATE-----\nMIID5zCCAs8CFFmAAFKV79uhzxc5qXbUw3oBNsYXMA0GCSqGSIb3DQEBCwUAMIGv\nMQswCQYDVQQGEwJVUzELMAkGA1U"
},
{
"path": "caddytest/caddy.localhost.key",
"chars": 1675,
"preview": "-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEAmeB96KtCRZpTyKGAgLlIqlFBf/eShGOWMMr+gJuEuz7CDn3a\nQjBW/TqXv6afs1Jo0GWhpuO"
},
{
"path": "caddytest/caddytest.go",
"chars": 17556,
"preview": "package caddytest\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/fs\"\n\t\"log\"\n\t\""
},
{
"path": "caddytest/caddytest_test.go",
"chars": 6180,
"preview": "package caddytest\n\nimport (\n\t\"bytes\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestReplaceCertificatePaths(t *testing.T)"
},
{
"path": "caddytest/integration/acme_test.go",
"chars": 5811,
"preview": "package integration\n\nimport (\n\t\"context\"\n\t\"crypto/ecdsa\"\n\t\"crypto/elliptic\"\n\t\"crypto/rand\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net\"\n\t\"n"
},
{
"path": "caddytest/integration/acmeserver_test.go",
"chars": 5161,
"preview": "package integration\n\nimport (\n\t\"context\"\n\t\"crypto/ecdsa\"\n\t\"crypto/elliptic\"\n\t\"crypto/rand\"\n\t\"log/slog\"\n\t\"strings\"\n\t\"test"
},
{
"path": "caddytest/integration/autohttps_test.go",
"chars": 3907,
"preview": "package integration\n\nimport (\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/caddyserver/caddy/v2/caddytest\"\n)\n\nfunc TestAutoHTTPt"
},
{
"path": "caddytest/integration/caddyfile_adapt/acme_dns_configured.caddyfiletest",
"chars": 935,
"preview": "{\n\tacme_dns mock foo\n}\n\nexample.com {\n\trespond \"Hello World\"\n}\n----------\n{\n\t\"apps\": {\n\t\t\"http\": {\n\t\t\t\"servers\": {\n\t\t\t\t\""
},
{
"path": "caddytest/integration/caddyfile_adapt/acme_dns_naked_use_dns_defaults.caddyfiletest",
"chars": 570,
"preview": "{\n\tdns mock\n\tacme_dns\n}\n\nexample.com {\n\n}\n----------\n{\n\t\"apps\": {\n\t\t\"http\": {\n\t\t\t\"servers\": {\n\t\t\t\t\"srv0\": {\n\t\t\t\t\t\"listen"
},
{
"path": "caddytest/integration/caddyfile_adapt/acme_dns_naked_without_dns.caddyfiletest",
"chars": 163,
"preview": "{\n\tacme_dns\n}\n\nexample.com {\n\trespond \"Hello World\"\n}\n----------\nacme_dns specified without DNS provider config, but no "
},
{
"path": "caddytest/integration/caddyfile_adapt/acme_server_custom_challenges.caddyfiletest",
"chars": 867,
"preview": "{\n\tpki {\n\t\tca custom-ca {\n\t\t\tname \"Custom CA\"\n\t\t}\n\t}\n}\n\nacme.example.com {\n\tacme_server {\n\t\tca custom-ca\n\t\tchallenges dn"
},
{
"path": "caddytest/integration/caddyfile_adapt/acme_server_default_challenges.caddyfiletest",
"chars": 792,
"preview": "{\n\tpki {\n\t\tca custom-ca {\n\t\t\tname \"Custom CA\"\n\t\t}\n\t}\n}\n\nacme.example.com {\n\tacme_server {\n\t\tca custom-ca\n\t\tchallenges\n\t}"
},
{
"path": "caddytest/integration/caddyfile_adapt/acme_server_lifetime.caddyfiletest",
"chars": 1865,
"preview": "{\n\tpki {\n\t\tca internal {\n\t\t\tname \"Internal\"\n\t\t\troot_cn \"Internal Root Cert\"\n\t\t\tintermediate_cn \"Internal Intermediate Ce"
},
{
"path": "caddytest/integration/caddyfile_adapt/acme_server_multi_custom_challenges.caddyfiletest",
"chars": 900,
"preview": "{\n\tpki {\n\t\tca custom-ca {\n\t\t\tname \"Custom CA\"\n\t\t}\n\t}\n}\n\nacme.example.com {\n\tacme_server {\n\t\tca custom-ca\n\t\tchallenges dn"
},
{
"path": "caddytest/integration/caddyfile_adapt/acme_server_policy-allow.caddyfiletest",
"chars": 1080,
"preview": "{\n\tpki {\n\t\tca custom-ca {\n\t\t\tname \"Custom CA\"\n\t\t}\n\t}\n}\n\nacme.example.com {\n\tacme_server {\n\t\tca custom-ca\n\t\tallow {\n\t\t\tdo"
},
{
"path": "caddytest/integration/caddyfile_adapt/acme_server_policy-both.caddyfiletest",
"chars": 1256,
"preview": "{\n\tpki {\n\t\tca custom-ca {\n\t\t\tname \"Custom CA\"\n\t\t}\n\t}\n}\n\nacme.example.com {\n\tacme_server {\n\t\tca custom-ca\n\t\tallow {\n\t\t\tdo"
},
{
"path": "caddytest/integration/caddyfile_adapt/acme_server_policy-deny.caddyfiletest",
"chars": 995,
"preview": "{\n\tpki {\n\t\tca custom-ca {\n\t\t\tname \"Custom CA\"\n\t\t}\n\t}\n}\n\nacme.example.com {\n\tacme_server {\n\t\tca custom-ca\n\t\tdeny {\n\t\t\tdom"
},
{
"path": "caddytest/integration/caddyfile_adapt/acme_server_sign_with_root.caddyfiletest",
"chars": 1017,
"preview": "{\n\tpki {\n\t\tca internal {\n\t\t\tname \"Internal\"\n\t\t\troot_cn \"Internal Root Cert\"\n\t\t\tintermediate_cn \"Internal Intermediate Ce"
},
{
"path": "caddytest/integration/caddyfile_adapt/ambiguous_site_definition.caddyfiletest",
"chars": 258,
"preview": "example.com\nhandle {\n\trespond \"one\"\n}\n\nexample.com\nhandle {\n\trespond \"two\"\n}\n----------\nCaddyfile:6: unrecognized direct"
},
{
"path": "caddytest/integration/caddyfile_adapt/ambiguous_site_definition_duplicate_key.caddyfiletest",
"chars": 94,
"preview": ":8080 {\n\trespond \"one\"\n}\n\n:8080 {\n\trespond \"two\"\n}\n----------\nambiguous site definition: :8080"
},
{
"path": "caddytest/integration/caddyfile_adapt/auto_https_disable_redirects.caddyfiletest",
"chars": 398,
"preview": "{\n\tauto_https disable_redirects\n}\n\nlocalhost\n----------\n{\n\t\"apps\": {\n\t\t\"http\": {\n\t\t\t\"servers\": {\n\t\t\t\t\"srv0\": {\n\t\t\t\t\t\"lis"
},
{
"path": "caddytest/integration/caddyfile_adapt/auto_https_ignore_loaded_certs.caddyfiletest",
"chars": 409,
"preview": "{\n\tauto_https ignore_loaded_certs\n}\n\nlocalhost\n----------\n{\n\t\"apps\": {\n\t\t\"http\": {\n\t\t\t\"servers\": {\n\t\t\t\t\"srv0\": {\n\t\t\t\t\t\"l"
},
{
"path": "caddytest/integration/caddyfile_adapt/auto_https_off.caddyfiletest",
"chars": 425,
"preview": "{\n\tauto_https off\n}\n\nlocalhost\n----------\n{\n\t\"apps\": {\n\t\t\"http\": {\n\t\t\t\"servers\": {\n\t\t\t\t\"srv0\": {\n\t\t\t\t\t\"listen\": [\n\t\t\t\t\t\t"
},
{
"path": "caddytest/integration/caddyfile_adapt/bind_fd_fdgram_h123.caddyfiletest",
"chars": 2110,
"preview": "{\n\tauto_https disable_redirects\n\tadmin off\n}\n\nhttp://localhost {\n\tbind fd/{env.CADDY_HTTP_FD} {\n\t\tprotocols h1\n\t}\n\tlog\n\t"
},
{
"path": "caddytest/integration/caddyfile_adapt/bind_ipv6.caddyfiletest",
"chars": 330,
"preview": "example.com {\n\tbind tcp6/[::]\n}\n----------\n{\n\t\"apps\": {\n\t\t\"http\": {\n\t\t\t\"servers\": {\n\t\t\t\t\"srv0\": {\n\t\t\t\t\t\"listen\": [\n\t\t\t\t\t"
},
{
"path": "caddytest/integration/caddyfile_adapt/directive_as_site_address.caddyfiletest",
"chars": 160,
"preview": "handle\n\nrespond \"should not work\"\n----------\nCaddyfile:1: parsed 'handle' as a site address, but it is a known directive"
},
{
"path": "caddytest/integration/caddyfile_adapt/duplicate_listener_address_global.caddyfiletest",
"chars": 177,
"preview": "{\n\tservers {\n\t\tsrv0 {\n\t\t\tlisten :8080\n\t\t}\n\t\tsrv1 {\n\t\t\tlisten :8080\n\t\t}\n\t}\n}\n----------\nparsing caddyfile tokens for 'ser"
},
{
"path": "caddytest/integration/caddyfile_adapt/enable_tls_for_catch_all_site.caddyfiletest",
"chars": 394,
"preview": ":8443 {\n\ttls internal {\n\t\ton_demand\n\t}\n}\n----------\n{\n\t\"apps\": {\n\t\t\"http\": {\n\t\t\t\"servers\": {\n\t\t\t\t\"srv0\": {\n\t\t\t\t\t\"listen\""
},
{
"path": "caddytest/integration/caddyfile_adapt/encode_options.caddyfiletest",
"chars": 1802,
"preview": ":80\n\n# All the options\nencode gzip zstd {\n\tminimum_length 256\n\tmatch {\n\t\tstatus 2xx 4xx 500\n\t\theader Content-Type text/*"
},
{
"path": "caddytest/integration/caddyfile_adapt/error_example.caddyfiletest",
"chars": 2642,
"preview": "example.com {\n\troot * /srv\n\n\t# Trigger errors for certain paths\n\terror /private* \"Unauthorized\" 403\n\terror /hidden* \"Not"
},
{
"path": "caddytest/integration/caddyfile_adapt/error_multi_site_blocks.caddyfiletest",
"chars": 5596,
"preview": "foo.localhost {\n\troot * /srv\n\terror /private* \"Unauthorized\" 410\n\terror /fivehundred* \"Internal Server Error\" 500\n\n\thand"
},
{
"path": "caddytest/integration/caddyfile_adapt/error_range_codes.caddyfiletest",
"chars": 2302,
"preview": "{\n\thttp_port 3010\n}\nlocalhost:3010 {\n\troot * /srv\n\terror /private* \"Unauthorized\" 410\n\terror /hidden* \"Not found\" 404\n\n\t"
},
{
"path": "caddytest/integration/caddyfile_adapt/error_range_simple_codes.caddyfiletest",
"chars": 3450,
"preview": "{\n\thttp_port 2099\n}\nlocalhost:2099 {\n\troot * /srv\n\terror /private* \"Unauthorized\" 410\n\terror /threehundred* \"Moved Perma"
},
{
"path": "caddytest/integration/caddyfile_adapt/error_simple_codes.caddyfiletest",
"chars": 2228,
"preview": "{\n\thttp_port 3010\n}\nlocalhost:3010 {\n\troot * /srv\n\terror /private* \"Unauthorized\" 410\n\terror /hidden* \"Not found\" 404\n\n\t"
},
{
"path": "caddytest/integration/caddyfile_adapt/error_sort.caddyfiletest",
"chars": 3184,
"preview": "{\n\thttp_port 2099\n}\nlocalhost:2099 {\n\troot * /srv\n\terror /private* \"Unauthorized\" 410\n\terror /hidden* \"Not found\" 404\n\te"
},
{
"path": "caddytest/integration/caddyfile_adapt/error_subhandlers.caddyfiletest",
"chars": 5493,
"preview": "{\n\thttp_port 2099\n}\nlocalhost:2099 {\n\troot * /var/www/\n\tfile_server\n\n\thandle_errors 404 {\n\t\thandle /en/* {\n\t\t\trespond \"n"
},
{
"path": "caddytest/integration/caddyfile_adapt/expression_quotes.caddyfiletest",
"chars": 3084,
"preview": "(snippet) {\n\t@g `{http.error.status_code} == 404`\n}\n\nexample.com\n\n@a expression {http.error.status_code} == 400\nabort @a"
},
{
"path": "caddytest/integration/caddyfile_adapt/file_server_disable_canonical_uris.caddyfiletest",
"chars": 379,
"preview": ":80\n\nfile_server {\n\tdisable_canonical_uris\n}\n----------\n{\n\t\"apps\": {\n\t\t\"http\": {\n\t\t\t\"servers\": {\n\t\t\t\t\"srv0\": {\n\t\t\t\t\t\"lis"
},
{
"path": "caddytest/integration/caddyfile_adapt/file_server_etag_file_extensions.caddyfiletest",
"chars": 536,
"preview": ":8080 {\n\troot * ./\n\tfile_server {\n\t\tetag_file_extensions .b3sum .sha256\n\t}\n}\n----------\n{\n\t\"apps\": {\n\t\t\"http\": {\n\t\t\t\"ser"
},
{
"path": "caddytest/integration/caddyfile_adapt/file_server_file_limit.caddyfiletest",
"chars": 413,
"preview": ":80\n\nfile_server {\n\tbrowse {\n\t\tfile_limit 4000\n\t}\n}\n----------\n{\n\t\"apps\": {\n\t\t\"http\": {\n\t\t\t\"servers\": {\n\t\t\t\t\"srv0\": {\n\t\t"
},
{
"path": "caddytest/integration/caddyfile_adapt/file_server_pass_thru.caddyfiletest",
"chars": 360,
"preview": ":80\n\nfile_server {\n\tpass_thru\n}\n----------\n{\n\t\"apps\": {\n\t\t\"http\": {\n\t\t\t\"servers\": {\n\t\t\t\t\"srv0\": {\n\t\t\t\t\t\"listen\": [\n\t\t\t\t\t"
},
{
"path": "caddytest/integration/caddyfile_adapt/file_server_precompressed.caddyfiletest",
"chars": 891,
"preview": ":80\n\nfile_server {\n\tprecompressed zstd br gzip\n}\n\nfile_server {\n\tprecompressed\n}\n----------\n{\n\t\"apps\": {\n\t\t\"http\": {\n\t\t\t"
},
{
"path": "caddytest/integration/caddyfile_adapt/file_server_sort.caddyfiletest",
"chars": 452,
"preview": ":80\n\nfile_server {\n\tbrowse {\n\t\tsort size desc\n\t}\n}\n----------\n{\n\t\"apps\": {\n\t\t\"http\": {\n\t\t\t\"servers\": {\n\t\t\t\t\"srv0\": {\n\t\t\t"
},
{
"path": "caddytest/integration/caddyfile_adapt/file_server_status.caddyfiletest",
"chars": 1903,
"preview": "localhost\n\nroot * /srv\n\nhandle /nope* {\n\tfile_server {\n\t\tstatus 403\n\t}\n}\n\nhandle /custom-status* {\n\tfile_server {\n\t\tstat"
},
{
"path": "caddytest/integration/caddyfile_adapt/forward_auth_authelia.caddyfiletest",
"chars": 6083,
"preview": "app.example.com {\n\tforward_auth authelia:9091 {\n\t\turi /api/authz/forward-auth\n\t\tcopy_headers Remote-User Remote-Groups R"
},
{
"path": "caddytest/integration/caddyfile_adapt/forward_auth_copy_headers_strip.caddyfiletest",
"chars": 2898,
"preview": ":8080\n\nforward_auth 127.0.0.1:9091 {\n\turi /\n\tcopy_headers X-User-Id X-User-Role\n}\n----------\n{\n\t\"apps\": {\n\t\t\"http\": {\n\t\t"
},
{
"path": "caddytest/integration/caddyfile_adapt/forward_auth_rename_headers.caddyfiletest",
"chars": 5415,
"preview": ":8881\n\nforward_auth localhost:9000 {\n\turi /auth\n\tcopy_headers A>1 B C>3 {\n\t\tD\n\t\tE>5\n\t}\n}\n----------\n{\n\t\"apps\": {\n\t\t\"http"
},
{
"path": "caddytest/integration/caddyfile_adapt/global_options.caddyfiletest",
"chars": 1248,
"preview": "{\n\tdebug\n\thttp_port 8080\n\thttps_port 8443\n\tgrace_period 5s\n\tshutdown_delay 10s\n\tdefault_sni localhost\n\torder root first\n"
},
{
"path": "caddytest/integration/caddyfile_adapt/global_options_acme.caddyfiletest",
"chars": 1765,
"preview": "{\n\tdebug\n\thttp_port 8080\n\thttps_port 8443\n\tdefault_sni localhost\n\torder root first\n\tstorage file_system {\n\t\troot /data\n\t"
},
{
"path": "caddytest/integration/caddyfile_adapt/global_options_admin.caddyfiletest",
"chars": 1117,
"preview": "{\n\tdebug\n\thttp_port 8080\n\thttps_port 8443\n\tdefault_sni localhost\n\torder root first\n\tstorage file_system {\n\t\troot /data\n\t"
},
{
"path": "caddytest/integration/caddyfile_adapt/global_options_admin_with_persist_config_off.caddyfiletest",
"chars": 450,
"preview": "{\n\thttp_port 8080\n\tpersist_config off\n\tadmin {\n\t\torigins localhost:2019 [::1]:2019 127.0.0.1:2019 192.168.10.128\n\t}\n}\n\n:"
},
{
"path": "caddytest/integration/caddyfile_adapt/global_options_debug_with_access_log.caddyfiletest",
"chars": 500,
"preview": "{\n\tdebug\n}\n\n:8881 {\n\tlog {\n\t\tformat console\n\t}\n}\n----------\n{\n\t\"logging\": {\n\t\t\"logs\": {\n\t\t\t\"default\": {\n\t\t\t\t\"level\": \"DE"
},
{
"path": "caddytest/integration/caddyfile_adapt/global_options_default_bind.caddyfiletest",
"chars": 669,
"preview": "{\n\tdefault_bind tcp4/0.0.0.0 tcp6/[::]\n}\n\nexample.com {\n}\n\nexample.org:12345 {\n}\n----------\n{\n\t\"apps\": {\n\t\t\"http\": {\n\t\t\t"
},
{
"path": "caddytest/integration/caddyfile_adapt/global_options_log_and_site.caddyfiletest",
"chars": 1082,
"preview": "{\n\tlog {\n\t\toutput file caddy.log\n\t\tinclude some-log-source\n\t\texclude admin.api admin2.api\n\t}\n\tlog custom-logger {\n\t\toutp"
},
{
"path": "caddytest/integration/caddyfile_adapt/global_options_log_basic.caddyfiletest",
"chars": 177,
"preview": "{\n\tlog {\n\t\toutput file foo.log\n\t}\n}\n----------\n{\n\t\"logging\": {\n\t\t\"logs\": {\n\t\t\t\"default\": {\n\t\t\t\t\"writer\": {\n\t\t\t\t\t\"filenam"
},
{
"path": "caddytest/integration/caddyfile_adapt/global_options_log_custom.caddyfiletest",
"chars": 460,
"preview": "{\n\tlog custom-logger {\n\t\tformat filter {\n\t\t\twrap console\n\t\t\tfields {\n\t\t\t\trequest>remote_ip ip_mask {\n\t\t\t\t\tipv4 24\n\t\t\t\t\ti"
},
{
"path": "caddytest/integration/caddyfile_adapt/global_options_log_multi.caddyfiletest",
"chars": 278,
"preview": "{\n\tlog first {\n\t\toutput file foo.log\n\t}\n\tlog second {\n\t\tformat json\n\t}\n}\n----------\n{\n\t\"logging\": {\n\t\t\"logs\": {\n\t\t\t\"firs"
},
{
"path": "caddytest/integration/caddyfile_adapt/global_options_log_sampling.caddyfiletest",
"chars": 230,
"preview": "{\n\tlog {\n\t\tsampling {\n\t\t\tinterval 300\n\t\t\tfirst 50\n\t\t\tthereafter 40\n\t\t}\n\t}\n}\n----------\n{\n\t\"logging\": {\n\t\t\"logs\": {\n\t\t\t\"d"
},
{
"path": "caddytest/integration/caddyfile_adapt/global_options_persist_config.caddyfiletest",
"chars": 213,
"preview": "{\n\tpersist_config off\n}\n\n:8881 {\n}\n----------\n{\n\t\"admin\": {\n\t\t\"config\": {\n\t\t\t\"persist\": false\n\t\t}\n\t},\n\t\"apps\": {\n\t\t\"http"
},
{
"path": "caddytest/integration/caddyfile_adapt/global_options_preferred_chains.caddyfiletest",
"chars": 549,
"preview": "{\n\tpreferred_chains smallest\n}\n\nexample.com\n----------\n{\n\t\"apps\": {\n\t\t\"http\": {\n\t\t\t\"servers\": {\n\t\t\t\t\"srv0\": {\n\t\t\t\t\t\"list"
},
{
"path": "caddytest/integration/caddyfile_adapt/global_options_resolvers.caddyfiletest",
"chars": 1080,
"preview": "{\n\temail test@example.com\n\tdns mock\n\ttls_resolvers 1.1.1.1 8.8.8.8\n\tacme_dns\n}\n\nexample.com {\n}\n----------\n{\n\t\"apps\": {\n"
},
{
"path": "caddytest/integration/caddyfile_adapt/global_options_resolvers_http_challenge.caddyfiletest",
"chars": 410,
"preview": "{\n\ttls_resolvers 1.1.1.1 8.8.8.8\n}\n\nexample.com {\n}\n----------\n{\n\t\"apps\": {\n\t\t\"http\": {\n\t\t\t\"servers\": {\n\t\t\t\t\"srv0\": {\n\t\t"
},
{
"path": "caddytest/integration/caddyfile_adapt/global_options_resolvers_local_dns_inherit.caddyfiletest",
"chars": 929,
"preview": "{\n\temail test@example.com\n\tdns mock\n\ttls_resolvers 1.1.1.1 8.8.8.8\n}\n\nexample.com {\n\ttls {\n\t\tdns mock\n\t}\n}\n----------\n{\n"
},
{
"path": "caddytest/integration/caddyfile_adapt/global_options_resolvers_local_override.caddyfiletest",
"chars": 1404,
"preview": "{\n\temail test@example.com\n\tdns mock\n\ttls_resolvers 1.1.1.1 8.8.8.8\n\tacme_dns\n}\n\nexample.com {\n\ttls {\n\t\tresolvers 9.9.9.9"
},
{
"path": "caddytest/integration/caddyfile_adapt/global_options_resolvers_mixed.caddyfiletest",
"chars": 1624,
"preview": "{\n\temail test@example.com\n\tdns mock\n\ttls_resolvers 1.1.1.1 8.8.8.8\n\tacme_dns\n}\n\nsite1.example.com {\n}\n\nsite2.example.com"
},
{
"path": "caddytest/integration/caddyfile_adapt/global_options_skip_install_trust.caddyfiletest",
"chars": 2751,
"preview": "{\n\tskip_install_trust\n\tpki {\n\t\tca {\n\t\t\tname \"Local\"\n\t\t\troot_cn \"Custom Local Root Name\"\n\t\t\tintermediate_cn \"Custom Local"
},
{
"path": "caddytest/integration/caddyfile_adapt/global_server_options_multi.caddyfiletest",
"chars": 964,
"preview": "{\n\tservers {\n\t\ttimeouts {\n\t\t\tidle 90s\n\t\t}\n\t\tstrict_sni_host insecure_off\n\t}\n\tservers :80 {\n\t\ttimeouts {\n\t\t\tidle 60s\n\t\t}\n"
},
{
"path": "caddytest/integration/caddyfile_adapt/global_server_options_single.caddyfiletest",
"chars": 1718,
"preview": "{\n\tservers {\n\t\tlistener_wrappers {\n\t\t\thttp_redirect\n\t\t\ttls\n\t\t}\n\t\ttimeouts {\n\t\t\tread_body 30s\n\t\t\tread_header 30s\n\t\t\twrite"
},
{
"path": "caddytest/integration/caddyfile_adapt/handle_nested_in_route.caddyfiletest",
"chars": 1263,
"preview": ":8881 {\n\troute {\n\t\thandle /foo/* {\n\t\t\trespond \"Foo\"\n\t\t}\n\t\thandle {\n\t\t\trespond \"Bar\"\n\t\t}\n\t}\n}\n----------\n{\n\t\"apps\": {\n\t\t\""
},
{
"path": "caddytest/integration/caddyfile_adapt/handle_path.caddyfiletest",
"chars": 751,
"preview": ":80\nhandle_path /api/v1/* {\n\trespond \"API v1\"\n}\n----------\n{\n\t\"apps\": {\n\t\t\"http\": {\n\t\t\t\"servers\": {\n\t\t\t\t\"srv0\": {\n\t\t\t\t\t\""
},
{
"path": "caddytest/integration/caddyfile_adapt/handle_path_sorting.caddyfiletest",
"chars": 1580,
"preview": ":80 {\n\thandle /api/* {\n\t\trespond \"api\"\n\t}\n\n\thandle_path /static/* {\n\t\trespond \"static\"\n\t}\n\n\thandle {\n\t\trespond \"handle\"\n"
},
{
"path": "caddytest/integration/caddyfile_adapt/header.caddyfiletest",
"chars": 3282,
"preview": ":80 {\n\theader Denis \"Ritchie\"\n\theader +Edsger \"Dijkstra\"\n\theader ?John \"von Neumann\"\n\theader -Wolfram\n\theader {\n\t\tGrace:"
},
{
"path": "caddytest/integration/caddyfile_adapt/header_placeholder_search.caddyfiletest",
"chars": 1268,
"preview": ":80 {\n\theader Test-Static \":443\" \"STATIC-WORKS\"\n\theader Test-Dynamic \":{http.request.local.port}\" \"DYNAMIC-WORKS\"\n\theade"
},
{
"path": "caddytest/integration/caddyfile_adapt/heredoc.caddyfiletest",
"chars": 844,
"preview": "example.com {\n\trespond <<EOF\n <html>\n <head><title>Foo</title>\n <body>Foo</body>\n </html>\n EOF 200\n}\n"
},
{
"path": "caddytest/integration/caddyfile_adapt/heredoc_extra_indentation.caddyfiletest",
"chars": 519,
"preview": ":80\n\nhandle {\n\trespond <<END\n line1\n line2\n END\n}\n----------\n{\n\t\"apps\": {\n\t\t\"http\": {\n\t\t\t\"servers\": {\n\t\t\t"
},
{
"path": "caddytest/integration/caddyfile_adapt/heredoc_incomplete.caddyfiletest",
"chars": 207,
"preview": ":80\n\nhandle {\n respond <<EOF\n Hello\n# missing EOF marker\n}\n----------\nmismatched leading whitespace in heredoc <<E"
},
{
"path": "caddytest/integration/caddyfile_adapt/heredoc_invalid_marker.caddyfiletest",
"chars": 169,
"preview": ":80\n\nhandle {\n respond <<END!\n Hello\n END!\n}\n----------\nheredoc marker on line #4 must contain only alpha-numer"
},
{
"path": "caddytest/integration/caddyfile_adapt/heredoc_mismatched_whitespace.caddyfiletest",
"chars": 182,
"preview": ":80\n\nhandle {\n\trespond <<END\n\tline1\n\tline2\n END\n}\n----------\nmismatched leading whitespace in heredoc <<END on line #5 "
},
{
"path": "caddytest/integration/caddyfile_adapt/heredoc_missing_marker.caddyfiletest",
"chars": 218,
"preview": ":80\n\nhandle {\n respond << \n Hello\n END\n}\n----------\nparsing caddyfile tokens for 'handle': unrecognized directi"
},
{
"path": "caddytest/integration/caddyfile_adapt/heredoc_too_many_angle_brackets.caddyfiletest",
"chars": 132,
"preview": ":80\n\nhandle {\n respond <<<END\n Hello\n END\n}\n----------\ntoo many '<' for heredoc on line #4; only use two, for e"
},
{
"path": "caddytest/integration/caddyfile_adapt/http_only_hostnames.caddyfiletest",
"chars": 648,
"preview": "# https://github.com/caddyserver/caddy/issues/3977\nhttp://* {\n\trespond \"Hello, world!\"\n}\n----------\n{\n\t\"apps\": {\n\t\t\"http"
},
{
"path": "caddytest/integration/caddyfile_adapt/http_only_on_any_address.caddyfiletest",
"chars": 482,
"preview": ":80 {\n\trespond /version 200 {\n\t\tbody \"hello from localhost\"\n\t}\n}\n----------\n{\n\t\"apps\": {\n\t\t\"http\": {\n\t\t\t\"servers\": {\n\t\t\t"
},
{
"path": "caddytest/integration/caddyfile_adapt/http_only_on_domain.caddyfiletest",
"chars": 828,
"preview": "http://a.caddy.localhost {\n\trespond /version 200 {\n\t\tbody \"hello from localhost\"\n\t}\n}\n----------\n{\n\t\"apps\": {\n\t\t\"http\": "
},
{
"path": "caddytest/integration/caddyfile_adapt/http_only_on_hostless_block.caddyfiletest",
"chars": 329,
"preview": "# Issue #4113\n:80, http://example.com {\n\trespond \"foo\"\n}\n----------\n{\n\t\"apps\": {\n\t\t\"http\": {\n\t\t\t\"servers\": {\n\t\t\t\t\"srv0\":"
},
{
"path": "caddytest/integration/caddyfile_adapt/http_only_on_localhost.caddyfiletest",
"chars": 808,
"preview": "localhost:80 {\n\trespond /version 200 {\n\t\tbody \"hello from localhost\"\n\t}\n}\n----------\n{\n\t\"apps\": {\n\t\t\"http\": {\n\t\t\t\"server"
},
{
"path": "caddytest/integration/caddyfile_adapt/http_only_on_non_standard_port.caddyfiletest",
"chars": 916,
"preview": "http://a.caddy.localhost:81 {\n\trespond /version 200 {\n\t\tbody \"hello from localhost\"\n\t}\n}\n----------\n{\n\t\"apps\": {\n\t\t\"http"
},
{
"path": "caddytest/integration/caddyfile_adapt/http_valid_directive_like_site_address.caddyfiletest",
"chars": 620,
"preview": "http://handle {\n\tfile_server\n}\n----------\n{\n\t\"apps\": {\n\t\t\"http\": {\n\t\t\t\"servers\": {\n\t\t\t\t\"srv0\": {\n\t\t\t\t\t\"listen\": [\n\t\t\t\t\t\t"
},
{
"path": "caddytest/integration/caddyfile_adapt/https_on_domain.caddyfiletest",
"chars": 822,
"preview": "a.caddy.localhost {\n\trespond /version 200 {\n\t\tbody \"hello from localhost\"\n\t}\n}\n----------\n{\n\t\"apps\": {\n\t\t\"http\": {\n\t\t\t\"s"
},
{
"path": "caddytest/integration/caddyfile_adapt/import_args_file.caddyfiletest",
"chars": 832,
"preview": "example.com\n\nimport testdata/import_respond.txt Groot Rocket\nimport testdata/import_respond.txt you \"the confused man\"\n-"
},
{
"path": "caddytest/integration/caddyfile_adapt/import_args_snippet.caddyfiletest",
"chars": 1249,
"preview": "(logging) {\n\tlog {\n\t\toutput file /var/log/caddy/{args[0]}.access.log\n\t}\n}\n\na.example.com {\n\timport logging a.example.com"
},
{
"path": "caddytest/integration/caddyfile_adapt/import_args_snippet_env_placeholder.caddyfiletest",
"chars": 328,
"preview": "(foo) {\n\trespond {env.FOO}\n}\n\n:80 {\n\timport foo\n}\n----------\n{\n\t\"apps\": {\n\t\t\"http\": {\n\t\t\t\"servers\": {\n\t\t\t\t\"srv0\": {\n\t\t\t\t"
},
{
"path": "caddytest/integration/caddyfile_adapt/import_block_anonymous.caddyfiletest",
"chars": 232,
"preview": "(site) {\n http://{args[0]} https://{args[0]} {\n {block}\n }\n}\nimport site test.domain {\n { \n heade"
},
{
"path": "caddytest/integration/caddyfile_adapt/import_block_snippet.caddyfiletest",
"chars": 755,
"preview": "(snippet) {\n\theader {\n\t\t{block}\n\t}\n}\n\nexample.com {\n\timport snippet {\n\t\tfoo bar\n\t}\n}\n----------\n{\n\t\"apps\": {\n\t\t\"http\": {"
},
{
"path": "caddytest/integration/caddyfile_adapt/import_block_snippet_args.caddyfiletest",
"chars": 748,
"preview": "(snippet) {\n\t{block}\n}\n\nexample.com {\n\timport snippet {\n\t\theader foo bar\n\t}\n}\n----------\n{\n\t\"apps\": {\n\t\t\"http\": {\n\t\t\t\"se"
},
{
"path": "caddytest/integration/caddyfile_adapt/import_block_snippet_non_replaced_block.caddyfiletest",
"chars": 792,
"preview": "(snippet) {\n\theader {\n\t\treverse_proxy localhost:3000\n\t\t{block}\n\t}\n}\n\nexample.com {\n\timport snippet\n}\n---------- \n{\n\t\"app"
},
{
"path": "caddytest/integration/caddyfile_adapt/import_block_snippet_non_replaced_block_from_separate_file.caddyfiletest",
"chars": 783,
"preview": "import testdata/issue_7518_unused_block_panic_snippets.conf\n\nexample.com {\n\timport snippet\n}\n----------\n{\n\t\"apps\": {\n\t\t\""
},
{
"path": "caddytest/integration/caddyfile_adapt/import_block_snippet_non_replaced_key_block.caddyfiletest",
"chars": 806,
"preview": "(snippet) {\n\theader {\n\t\treverse_proxy localhost:3000\n\t\t{blocks.content_type}\n\t}\n}\n\nexample.com {\n\timport snippet\n}\n-----"
},
{
"path": "caddytest/integration/caddyfile_adapt/import_block_with_site_block.caddyfiletest",
"chars": 1000,
"preview": "(site) {\n\thttps://{args[0]} {\n\t\t{block}\n\t}\n}\n\nimport site test.domain {\n\treverse_proxy http://192.168.1.1:8080 {\n\t\theade"
},
{
"path": "caddytest/integration/caddyfile_adapt/import_blocks_snippet.caddyfiletest",
"chars": 1024,
"preview": "(snippet) {\n\theader {\n\t\t{blocks.foo}\n\t}\n\theader {\n\t\t{blocks.bar}\n\t}\n}\n\nexample.com {\n\timport snippet {\n\t\tfoo {\n\t\t\tfoo a\n"
},
{
"path": "caddytest/integration/caddyfile_adapt/import_blocks_snippet_nested.caddyfiletest",
"chars": 1094,
"preview": "(snippet) {\n\theader {\n\t\t{blocks.bar}\n\t}\n\timport sub_snippet {\n\t\tbar {\n\t\t\t{blocks.foo}\n\t\t}\n\t}\n}\n(sub_snippet) {\n\theader {"
},
{
"path": "caddytest/integration/caddyfile_adapt/import_cycle.caddyfiletest",
"chars": 162,
"preview": "(import1) {\n\timport import2\n}\n\n(import2) {\n\timport import1\n}\n\nimport import1\n\n----------\na cycle of imports exists betwe"
},
{
"path": "caddytest/integration/caddyfile_adapt/intercept_response.caddyfiletest",
"chars": 4745,
"preview": "localhost\n\nrespond \"To intercept\"\n\nintercept {\n\t@500 status 500\n\treplace_status @500 400\n\n\t@all status 2xx 3xx 4xx 5xx\n\t"
},
{
"path": "caddytest/integration/caddyfile_adapt/invoke_named_routes.caddyfiletest",
"chars": 2221,
"preview": "&(first) {\n\t@first path /first\n\tvars @first first 1\n\trespond \"first\"\n}\n\n&(second) {\n\trespond \"second\"\n}\n\n:8881 {\n\tinvoke"
},
{
"path": "caddytest/integration/caddyfile_adapt/invoke_undefined_named_route.caddyfiletest",
"chars": 93,
"preview": "example.com {\n\tinvoke foo\n}\n----------\ncannot invoke named route 'foo', which was not defined"
},
{
"path": "caddytest/integration/caddyfile_adapt/log_add.caddyfiletest",
"chars": 1069,
"preview": ":80 {\n\tlog\n\n\tvars foo foo\n\n\tlog_append const bar\n\tlog_append vars foo\n\tlog_append placeholder {path}\n\n\tlog_append /only-"
},
{
"path": "caddytest/integration/caddyfile_adapt/log_append_encoder.caddyfiletest",
"chars": 777,
"preview": "{\n\tlog {\n\t\tformat append {\n\t\t\twrap json\n\t\t\tfields {\n\t\t\t\twrap \"foo\"\n\t\t\t}\n\t\t\tenv {env.EXAMPLE}\n\t\t\tint 1\n\t\t\tfloat 1.1\n\t\t\tbo"
},
{
"path": "caddytest/integration/caddyfile_adapt/log_except_catchall_blocks.caddyfiletest",
"chars": 1778,
"preview": "http://localhost:2020 {\n\tlog\n\tlog_skip /first-hidden*\n\tlog_skip /second-hidden*\n\trespond 200\n}\n\n:2020 {\n\trespond 418\n}\n-"
},
{
"path": "caddytest/integration/caddyfile_adapt/log_filter_no_wrap.caddyfiletest",
"chars": 651,
"preview": ":80\n\nlog {\n\toutput stdout\n\tformat filter {\n\t\tfields {\n\t\t\trequest>headers>Server delete\n\t\t}\n\t}\n}\n----------\n{\n\t\"logging\":"
},
{
"path": "caddytest/integration/caddyfile_adapt/log_filter_with_header.txt",
"chars": 2841,
"preview": "localhost {\n\tlog {\n\t\toutput file ./caddy.access.log\n\t}\n\tlog health_check_log {\n\t\toutput file ./caddy.access.health.log\n\t"
},
{
"path": "caddytest/integration/caddyfile_adapt/log_filters.caddyfiletest",
"chars": 2408,
"preview": ":80\n\nlog {\n\toutput stdout\n\tformat filter {\n\t\twrap console\n\n\t\t# long form, with \"fields\" wrapper\n\t\tfields {\n\t\t\turi query "
},
{
"path": "caddytest/integration/caddyfile_adapt/log_multi_logger_name.caddyfiletest",
"chars": 2196,
"preview": "(log-both) {\n\tlog {args[0]}-json {\n\t\thostnames {args[0]}\n\t\toutput file /var/log/{args[0]}.log\n\t\tformat json\n\t}\n\tlog {arg"
},
{
"path": "caddytest/integration/caddyfile_adapt/log_multiple_regexp_filters.caddyfiletest",
"chars": 1997,
"preview": ":80\n\nlog {\n\toutput stdout\n\tformat filter {\n\t\twrap console\n\t\t\n\t\t# Multiple regexp filters for the same field - this shoul"
},
{
"path": "caddytest/integration/caddyfile_adapt/log_override_hostname.caddyfiletest",
"chars": 1650,
"preview": "*.example.com {\n\tlog {\n\t\thostnames foo.example.com bar.example.com\n\t\toutput file /foo-bar.txt\n\t}\n\tlog {\n\t\thostnames baz."
},
{
"path": "caddytest/integration/caddyfile_adapt/log_override_name_multiaccess.caddyfiletest",
"chars": 1243,
"preview": "{\n\tlog access-console {\n\t\tinclude http.log.access.foo\n\t\toutput file access-localhost.log\n\t\tformat console\n\t}\n\n\tlog acces"
},
{
"path": "caddytest/integration/caddyfile_adapt/log_override_name_multiaccess_debug.caddyfiletest",
"chars": 1317,
"preview": "{\n\tdebug\n\n\tlog access-console {\n\t\tinclude http.log.access.foo\n\t\toutput file access-localhost.log\n\t\tformat console\n\t}\n\n\tl"
},
{
"path": "caddytest/integration/caddyfile_adapt/log_roll_days.caddyfiletest",
"chars": 1618,
"preview": ":80\n\nlog one {\n\toutput file /var/log/access.log {\n\t\tmode 0644\n\t\tdir_mode 0755\n\t\troll_size 1gb\n\t\troll_uncompressed\n\t\troll"
},
{
"path": "caddytest/integration/caddyfile_adapt/log_sampling.caddyfiletest",
"chars": 523,
"preview": ":80 {\n\tlog {\n\t\tsampling {\n\t\t\tinterval 300\n\t\t\tfirst 50\n\t\t\tthereafter 40\n\t\t}\n\t}\n}\n----------\n{\n\t\"logging\": {\n\t\t\"logs\": {\n\t"
}
]
// ... and 388 more files (download for full content)
About this extraction
This page contains the full source code of the caddyserver/caddy GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 588 files (3.1 MB), approximately 855.4k tokens, and a symbol index with 3053 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.