Compare commits

..

277 Commits

Author SHA1 Message Date
Matthew Holt 11ae1aa6b8 Prepare v1.0.5 release 2020-01-15 09:57:41 -07:00
Matthew Holt 3c8837163d errors: Fix test 2020-01-15 09:57:23 -07:00
Matt Holt b6ca782c99 Don't use default log settings on custom logs (#2973) (#2976)
Attempt partial fix/rollback of #2781.
2020-01-15 09:40:32 -07:00
Dmitri Goutnik a976629174 go.mod: Add support for freebsd/arm64 (#2956) 2020-01-12 14:01:25 -07:00
Dozer cd66b62083 gzip: Avoid duplicated Vary header (#2939) 2020-01-12 14:00:18 -07:00
Maxime Soulé 4b68de8418 FreeBSD init: PID file should not be executable (#2945) 2019-12-28 11:54:28 -07:00
MisterDuval 008415f206 fastcgi: Trim dot and space suffix for Windows sake (#2917) 2019-12-14 11:21:27 -07:00
Vanessa Nicole Naff f0eae39cb2 readme: Update sponsor link 2019-12-13 09:42:46 -07:00
Gábor Lipták 7fa90f08ae Correct golint warning (#2915) 2019-12-04 16:35:56 -07:00
evtr 5ec503386c proxy: ability to use client certs in reverse proxy (#2914)
* ability to use client certs in reverse proxy

* changed to http3.RoundTripper after review
2019-12-04 15:35:36 -07:00
Matthew Holt 6f9a39525a github: Remove issue and PR templates; update contributing guidelines 2019-12-04 15:13:29 -07:00
Rustin cf611796c6 update readme to remove GO111MODULE env (#2886) 2019-11-18 12:02:08 -07:00
Matthew Holt aadda6e34e Prepare v1.0.4 tag 2019-11-15 13:11:32 -07:00
Matthew Holt 6c4cb5006a go.mod: lego v3.2.0, certmagic v0.8.3, and quic-go v0.13.1 2019-11-15 13:08:28 -07:00
Yuqian Ma 12107f035c dist: update caddy.service on Ubuntu 18 (#2866)
move `StartLimitIntervalSec` and `StartLimitBurst` from [Service] to [Unit]
ref: https://lists.freedesktop.org/archives/systemd-devel/2017-July/039255.html
2019-11-15 12:59:03 -07:00
MisterDuval 832df649c1 fastcgi: Case-insensitive extension comparison 2019-11-15 12:54:39 -07:00
Julian Kornberger cc63eca0c8 Add flag to disable timestamps for process log (#2781)
* Add flag to enable/disable timstamps for process log.

solves #2615

* Remove timestamp argument from log.Printf()

* Add log-timestamps=false to systemd unit

* Copy log flags

* Fix argument list
2019-11-06 13:06:34 -07:00
Marten Seemann aa94f2b802 update quic-go to v0.13.0 (#2862)
This version supports IETF QUIC draft-23.
2019-11-06 00:30:29 -07:00
Matt Holt 3f9a431100 Create FUNDING.yml 2019-10-30 09:56:43 -06:00
Adam Daniels 8c860641b9 dist: Update FreeBSD Init script to support custom flags (#2796)
Clear rc_flags in start precmd. If these flags are still present during
start command, they are passed to daemon(8) instead of caddy(8).

Extract all options into $caddy_options environment variable.
2019-10-15 15:38:20 -06:00
Jonathan Rudenberg 99914d2204 Move certmagic import out of caddy package (#2807) 2019-10-14 10:07:36 -06:00
Jacob Hoffman-Andrews 24b2e02ee5 init/systemd: Re-add ReadWriteDirectories. (#2798)
In systemd 231
(https://github.com/systemd/systemd/blob/4f10b80786e8baa1399b6de6111d5b3a16bf99ba/NEWS#L3558-L3565),
ReadWriteDirectories was renamed ReadWritePaths.

In https://github.com/caddyserver/caddy/pull/2620/files, @aspeteRakete
renamed the directive in Caddy's example systemd unit.

However, this means that if anyone runs this sytemd unit on a version of
systemd older than 231, Caddy will go into a crash loop that hammers
Let's Encrypt's servers. That's because the ProtectSystem=full directive
prevents writes to all paths that aren't explicitly permitted, and older
systemd doesn't see any paths being permitted.

To maximize compatibility, I re-add the original ReadWriteDirectories
directive. Older systemd will read that; newer systemd will read the
newer directive. Both should ignore the directive they do not recognize.

Another approach to solve this problem would be to remove
ProtectSystem=true, originally introduced in da8ae9e5. That would reduce
the risk of similar breakages in the future. It would make for a slightly
less "exemplary" systemd unit, but I think it would still be adequate,
given that this unit runs caddy as "www-data", a user the presumably has
low privileges.
2019-10-09 19:04:28 -06:00
Adil H be2fdb6af6 httpserver: no SetKeepAlivePeriod in openbsd (#2787)
* no SetKeepAlivePeriod on openbsd

* fix tcpKeepAliveListener.Accept signature
2019-10-08 09:49:40 -06:00
Matt Holt 16b296c97e systemd: Prevent excessive restarts in tight loop 2019-10-07 11:28:21 -06:00
Jannis Andrija Schnitzer 11eee95222 staticfiles: Signal that redirection headers have been written (#2792)
The Handler interface expects a first return value of 0 if headers have
already been written.
(cf. https://godoc.org/github.com/caddyserver/caddy/caddyhttp/httpserver#Handler)

When using http.Redirect, this is the case as http.Redirect does write
headers. When using Caddy with nested handlers, returning
http.StatusMovedPermanently could cause a wrong (200) response on a
non-slashy request on a directory name. Returning 0 ensures the
redirection will always take place.
2019-10-07 10:22:53 -06:00
Tobias Mühl 1de4a99ec3 Fix typo (#2763) 2019-09-23 22:01:44 -06:00
linquize 96579b97f6 gitattributes: go.mod, go.sum always eol=lf (#2753) 2019-09-22 14:17:16 -06:00
Ashish Acharya 8cc2f770fa Change mholt references to caddyserver (#2751)
mholt/caddy.git references are changed to caddyserver/caddy.git in the link titles
2019-09-15 11:41:20 -06:00
Jack Brown a23f707268 Update build docs to clarify usage of .go extension (#2726)
Issue #2716 was caused by a misunderstanding regarding the filename to use when creating a custom build from source. These changes aim to make the instructions more explicit.
2019-08-26 19:07:07 +01:00
Alexander Danilov ed4c2775e4 main: log caddy version on start (#2717) 2019-08-21 11:13:34 -06:00
Matthew Holt bff2469d9d Version 1.0.3 notes 2019-08-14 13:45:42 -06:00
Matthew Holt a08ab0c007 Fix slice bounds when getting key of address (fixes #2706) 2019-08-14 10:41:25 -06:00
Matthew Holt 28e1f7c562 Version 1.0.2 2019-08-13 14:37:58 -06:00
Matthew Holt 914f39d784 Adjust address parsing for Go 1.12.8's breaking changes
See https://github.com/golang/go/commit/3226f2d492963d361af9dfc6714ef141ba606713
and https://github.com/golang/go/issues/29098
2019-08-13 14:37:45 -06:00
linquize 0ba427a6f4 websocket: Enhancements, message types, and tests (#2359)
* websocket: Should reset respawn parameter when processing next config entry

* websocket: add message types: lines, text, binary

* websocket: Add unit test

* Add websocket sample files
2019-07-19 13:29:49 -06:00
Matthew Holt 7fab1b15c8 readme: Fix tests badge 2019-07-19 11:28:59 -06:00
Christian Muehlhaeuser 3856ad03b0 Used goimports to fix import order (#2682)
Keeps the list sorted and prevents future merge conflicts.
2019-07-18 22:05:49 -06:00
George Hartzell d411b7d087 Add doc re running as non-root user on FreeBSD (#2655)
Add a README.md in `dist/init/freebsd` that describes how to configure
the system so that `caddy` can be run without root privileges.
2019-07-18 15:19:21 -06:00
George Hartzell 580f7677ad Use syslog to manage caddy std{out,err} on FreeBSD (#2652)
* Use syslog to manage caddy std{out,err} on FreeBSD

There is no good way to rotate the logfile created by the previous
FreeBSD rc.d script (it's the result of redirecting std{out,err} and
is held open by the shell).

This solves the problem by sending caddy's std{out,err} stream to
syslog, using the daemon command's builtin functionality.

It replaces the old `caddy_logfile` rc.conf variable with
`caddy_syslog_facility` (which defaults to 'local7') and
`caddy_syslog_level` (which defaults to 'notice').

By default, these messages will end up in /var/log/messages but can
be redirected as documented in the script's comments.

* Add info about rotating log with newsyslog

If you create a caddy specific logfile in `/var/log`, you should
rotate it.

This adds a bit of info to the dist/init/freebsd/README.md about
rotating that log file with newsyslog.
2019-07-18 13:58:40 -06:00
rouzier 120811e7f7 staticfiles: Support pre-compressed zstd, make etag content-encoding-aware (#2626)
* Add support for precompressed zstd files (rfc8478)

* Avoid the hash lookup for the file extension.

* Only calculate Etag once
2019-07-18 13:50:01 -06:00
aspeteRakete 43458bda46 Updated systemd caddy.service (#2620)
According to https://github.com/systemd/systemd/blob/v241/NEWS#L2799
The Directive ReadWriteDirectories= has been renamed to ReadWritePaths=
in 241.
2019-07-18 13:46:00 -06:00
William Wang a9ccaa1ae5 add recaptcha plugin (#2664) 2019-07-11 13:37:27 -06:00
Matthew Holt f6ee100bae Update notes for v1.0.1 2019-07-02 13:08:31 -06:00
Matthew Holt f5720fecd6 Change all import paths: mholt/caddy -> caddyserver/caddy
Includes updating go.mod to use new module path
2019-07-02 12:49:20 -06:00
Matthew Holt 0b2e054839 tls: Deprecate 'max_certs' in favor of 'ask'; use latest CertMagic 2019-07-01 11:43:27 -06:00
Damir Vandic 6f01928512 Fix graceful shutdown (#2618)
Currently, the instance waitgroup is decremented twice in `startServers()`: once when `Serve()` is finished and once when `ServePacket()` is finished. However, with a graceful shutdown, `Serve()` returns before the server has actually finished shutting down all active connections. This patch increases the wait group by one when the server is shut down so that the program only exits when all the server instances have finished serving their connections.
2019-06-23 16:24:13 -06:00
Matthew Holt 6115a462c7 mod: Use CertMagic v0.6.1 2019-06-21 08:03:17 -06:00
Matthew Holt 5f9cba0f19 caddyfile: Move metrics into caddy package 2019-06-21 08:02:53 -06:00
Matthew Holt 05b3938556 Minor fixes to tests 2019-06-19 17:02:34 -06:00
Matthew Holt 62b4553f7d tls: Disable on-demand TLS when random config is chosen
A random config is intended to be used only for solving TLS-ALPN
challenges; so we have to be sure to disable on-demand TLS so that
arbitrary names can't request certificates with another name's
on-demand config.
2019-06-19 16:57:45 -06:00
Matthew Holt ad20323b52 Refactor clustering setup code 2019-06-19 16:57:45 -06:00
Matthew Holt 721c100bb0 Use CertMagic's HTTP and HTTPS port variable
Slightly inconvenient because it uses int type and we use string, but
oh well. This fixes a bug related to setting -http-port and -https-port
flags which weren't being used by CertMagic in some cases.
2019-06-19 16:57:45 -06:00
Matthew Holt 6720bdfb55 Clean up certmagic locks on signaled process exit
This should help prevent hanging in some cases when the process is
restarted and tries to obtain or renew a certificate, for example, but
the lock remains from the previous shutdown (which was during the same
operation). Only works if the process is cleanly shut down with a signal
it can capture.
2019-06-19 16:57:45 -06:00
shouya 0c626fbc2e tls: Allow client auth configs if CA filenames match (#2648)
* verify client certs

* move client cert compatible checker to an independent function

* unexport client cert compatible checker

* rename functions and add comment

* gofmt code

* add test

* add back the comment
2019-06-19 11:25:56 -06:00
Daniel af82141808 caddyhttp: Add 'permission' plugin directive (#2639) 2019-06-12 10:15:17 -06:00
Jared Ririe d11b648137 caddytls: Fix goroutine leak when restarting Caddy (#2644)
Each time the Caddyfile reloads and Caddy is restarted,
caddytls.NewConfig starts a goroutine for cleaning the
certificate storage. This goroutine ranges over a time.Ticker
channel; although Stop is called on this ticker, Stop does
not close the underlying channel so the goroutine never exits.

This change adds an additional channel that is listened to
in the certificate cleaning goroutine so it can exit
on restarts.
2019-06-11 15:24:35 -06:00
Matthew Holt 14a8ffedd8 Fix panic serving index file if HTTP request is malformed 2019-05-27 08:12:19 -06:00
Matthew Holt b5906135c7 Move PR template in attempt to fix (sigh)
https://github.community/t5/How-to-use-Git-and-GitHub/Our-pull-request-templates-aren-t-showing-up-for-any-PRs/m-p/22958
2019-05-27 08:11:33 -06:00
AndreKR 4bad5c79be Simple rewrite regex captures (#2592)
* More informative rewrite test output

When running rewrite tests, the output in case of a test failure now
includes not only the rewritten URLs but also the from URL.

* Move re-escaping to regexpMatches

This commit moves the code to post-process the match replacements from
ComplexRule to regexpMatches, so it can later be re-used for SimpleRule.

Also changes the comment in an attempt to better explain the reasoning
behind that code.

The required strings.Replacer is now built only once.

* Support regex captures in simple rewrite rules

Closes #2586
2019-05-24 12:00:27 -06:00
Łukasz Nowak 81430e4aff gzip: Add .wasm (WebAssembly files) (#2624) 2019-05-24 09:59:06 -06:00
Anthony Plunkett c238b72d5d readme: clarify about Go file to build Caddy (#2611) 2019-05-13 22:17:36 -06:00
Kurt Jung a2ed91bc45 httpserver: Add pubsub plugin (#2589) 2019-04-26 12:32:43 -06:00
Matthew Holt 15fecbc161 1.0 release 2019-04-24 11:24:40 -06:00
Taufiq Rahman c32a0f5f71 fix lint warnings (issue 2541) (#2551)
* Lint: fix some errcheck #2541

* Lint: fix passing structcheck #2541

* Lint: update fix structcheck #2541

* Lint: fix errcheck for basicauth, browse, fastcgi_test #2541

* Lint: fix errcheck for browse, fastcgi_test, fcgiclient, fcgiclient_test #2541

* Lint: fix errcheck for responsefilter_test, fcgilient_test #2541

* Lint: fix errcheck for header_test #2541

* Lint: update errcheck for fcgiclient_test #2541

* Lint: fix errcheck for server, header_test, fastcgi_test, https_test, recorder_test #2541

* Lint: fix errcheck for tplcontext, vhosttrie_test, internal_test, handler_test #2541

* Lint: fix errcheck for log_test, markdown mholt#2541

* Lint: fix errcheck for policy, body_test, proxy_test #2541

* Lint: fix errcheck for on multiple packages #2541

- reverseproxy
- reverseproxy_test
- upstream
- upstream_test
- body_test

* Lint: fix errcheck in multiple packages mholt#2541
- handler_test
- redirect_test
- requestid_test
- rewrite_test
- fileserver_test

* Lint: fix errcheck in multiple packages mholt#2541

- websocket
- setup
- collection
- redirect_test
- templates_test

* Lint: fix errcheck in logger test #2541

run goimports against #2551
- lexer_test
- log_test
- markdown

* Update caddyhttp/httpserver/logger_test.go

Co-Authored-By: Inconnu08 <taufiqrx8@gmail.com>

* Update log_test.go

* Lint: fix scope in logger_test #2541

* remove redundant err check in logger_test #2541

* fix alias in logger_test #2541

* fix import for format #2541

* refactor variable names and error check #2541
2019-04-22 10:20:37 -06:00
Matthew Holt 0c3d90ed21 I just learned about go mod tidy 2019-04-20 17:34:10 -06:00
Matthew Holt fb31669261 Release beta2 2019-04-20 13:13:42 -06:00
Matt Holt 917d9bc9da tls: Update to match CertMagic refactor (#2571)
* Update to match CertMagic's refactoring

* mod: CertMagic v0.5.0
2019-04-20 12:11:27 -06:00
Matthew Holt fd6e4516dc Remove now-unnecessary build.go
Caddy can be built, even with plugins, without modifying the source
code and without special build scripts, thanks to Go modules. See
the README or wiki.
2019-04-20 11:40:22 -06:00
Abiola Ibrahim 86205efcfe Merge pull request #2570 from whalehub/patch-1
Fix instructions for building Caddy from source
2019-04-20 13:26:12 +01:00
Aaron 701e77514f Fix instructions for building Caddy from source 2019-04-20 13:42:23 +02:00
Matthew Holt 018105eec9 readme: Update build-from-source instructions for Go modules 2019-04-20 01:19:44 -06:00
Matthew Holt bf6ec2bbfd main: Use embedded version, debug.BuildInfo 2019-04-20 00:52:53 -06:00
Matthew Holt 13d0454f71 Remove now-unused gitcookie 2019-04-20 00:52:23 -06:00
Marten Seemann db2741c6e0 mod: import the right version of quic-go and tidy (#2562)
* run go mod tidy

* import the right version of quic-go
2019-04-17 12:13:44 -06:00
elcore 605787f671 readme: Update build instructions (#2565)
Fix #2560
2019-04-12 14:31:38 -06:00
Matthew Holt 657780bcdf Prepare 1.0beta1 release 2019-04-09 11:29:06 -06:00
Toby Allen 9d767e768a readme: Add link to Good First Issue (#2481)
* Add link to Good First Issue

 in Contributing Section

* Fix heading
2019-04-09 11:11:26 -06:00
johncming 15268e8cdb caddytls: sort import statement (#2552) 2019-04-07 10:39:52 -06:00
Matthew Holt 04789a2446 ci: Enable use of Go modules
Temporary until Go 1.13
2019-04-06 16:31:06 -06:00
johncming e28ee90c2a httpserver: remove unused listener field (#2527) 2019-03-29 22:29:17 -06:00
Michael Grosser 3841517ce1 Migrate to Go modules - remove vendor folder (#2504)
* Migrate to gomods

* Fix conflict

* gomod: Switch xenolf/lego to go-acme/lego
2019-03-29 20:30:48 -06:00
Matt Holt bea48b80ce readme: Replace CI badges 2019-03-29 19:16:42 -06:00
Matthew Holt 9f525af210 ci: Remove Travis and AppVeyor manifests 2019-03-29 19:04:56 -06:00
Matt Holt c00b3a520c ci: Azure Pipelines CI system (#2540)
* Set up CI with Azure Pipelines [skip ci]

* Add other platforms [skip ci]

* Oops, add vmImage back in [skip ci]

* Fix goVersion [skip ci]

* Use sudo to install Go [skip ci]

* Attempt platform-specific Go isntalls [skip ci]

* Try sharing the variable a different way [skip ci]

* Trying this again... [skip ci]

* Fix the PATH [skip ci]

* Fix GOROOT, hopefully [skip ci]

* More fixing [skip ci]

* Still fixing [skip ci]

* Add golang-ci [skip ci]

* asdfasdfasf [skip ci]

* Sigh, work around broken golangci-lint installer [skip ci]

* Try again [skip ci]

* ahhhhhh [skip ci]

* sooooo close! cleanup [skip ci]

* oh c'mon [skip ci]

* thought I had it for a sec [skip ci]

* yaaaayyyy [skip ci]

ship it
2019-03-29 18:57:02 -06:00
johncming f6e6a6be04 caddy: improve error naming (#2526) 2019-03-16 23:33:11 -06:00
johncming bc5df3b383 onevent: add missing return error (#2525) 2019-03-15 22:43:28 -06:00
Linkonoid 1a0292b830 httpserver: Register dyndns directive (#2521) 2019-03-15 17:43:01 -06:00
M. Ángel Jimeno e6a3e5e1f3 cmd: rename -env to -envfile and use -env to print the environment (#2517)
* caddy: Rename env flag to envfile

* caddy: Add env flag to print environment variables
2019-03-11 17:50:04 -06:00
Aka.Why 397d67876c caddy: Start all servers only after all listeners successful (#2508) 2019-03-10 21:01:56 -06:00
comp500 47b78714b8 proxy: Change headers using regex (#2144)
* Add upstream header replacements (TODO: tests, docs)

* Add tests, fix a few bugs

* Add more tests and comments

* Refactor header_upstream to use a fallthrough; return regex errors
2019-03-06 14:35:07 -07:00
Toby Allen fda7350a43 github: Change name to ensure PR template is seen (#2501)
as per https://help.github.com/en/articles/creating-a-pull-request-template-for-your-repository
2019-03-04 19:14:18 -07:00
Matthew Holt 80dfb8b2a7 vendor: Update lego; notes for v0.11.5 2019-03-04 12:14:25 -07:00
Toby Allen 98f160e39c httpserver: More organized startup output (#2497)
* Move SiteOutput to a seperate function sorted by port.

* Rename vars and tidy up

* Move loopback note to output loop

* Fix Typo

* Remove unneeded var

* Readability Change

* Change to other port string.

* Simplify as all sites in Server use the same port

* Ensure -quiet supresses fmt.Println calls

* Prevent double output of siteinfo to log - improve log message

* change name of log in comment

* Remove spaces

* Remove extra line output

* final tidy!

* Use caddy.LogDestination to setup log

* Ensure Log is still output if quiet.

* Correct case of functions and make function param bool

* Remove conditional check for LogDestination

* Revert output to simple blocks

* comment update
2019-03-04 12:06:04 -07:00
Toby Allen 4f8020a94c basicauth: Simplify user replacement on auth failure (#2470)
* Can simply add user to replacer before Replace call.  user is then also added to logs

* use fmt.Errorf

* fix how fmt.Errorf is called.
2019-03-04 12:02:24 -07:00
Toby Allen b295aab2d8 errors: Return parse error if more than one argument (#2472)
* Check errors directive only has 1 argument

* Added Tests
2019-03-04 12:00:28 -07:00
Matthias Schneider 448edcca8e caddytls: removed useless code in selfsigned (#2494)
removed "names": is not used
2019-03-01 08:35:05 -07:00
Wèi Cōngruì 72d0debde6 caddytls: add TLS 1.3 support and remove CBC ciphers (#2399) 2019-02-25 18:39:30 -07:00
Matt Holt 9037d3ab85 appveyor: Use Go 1.12 2019-02-25 18:31:08 -07:00
Matthew Holt 8a511989a0 vendor: Update CertMagic; fix bug related to accounts with empty emails 2019-02-24 23:28:34 -07:00
Toby Allen 44e3a97a67 Revert "Modify Startup Output (#2469)" (#2482)
This reverts commit c0190a3460.
2019-02-24 14:44:17 -07:00
Toby Allen c0190a3460 Modify Startup Output (#2469)
* Move SiteOutput to a seperate function.

* Simplify as all sites in Server use the same port

* Ensure -quiet supresses fmt.Println calls

* Prevent double output of siteinfo to log - improve log message

* Use caddy.LogDestination to setup log

* Ensure Log is still output if quiet.
2019-02-24 10:04:24 +00:00
magikstm 396d8e989f internal: add internal paths to HiddenFiles (#2133)
* Append Internal paths to Caddy config HiddenFiles

* gofmt

* Reuse a variable

* Update caddyhttp/internalsrv/setup.go

Co-Authored-By: magikstm <myskina@gmail.com>
2019-02-17 14:32:22 -07:00
Matthew Holt 33b00dc8b1 staticfiles: HEAD, not OPTIONS 2019-02-16 08:22:04 -07:00
Matthew Holt eb9857137a staticfiles: Re-allow HEAD requests 2019-02-16 07:42:44 -07:00
Matthew Holt c1d6c928e3 Merge branch 'master' of ssh://github.com/mholt/caddy 2019-02-15 12:00:02 -07:00
Matthew Holt 118f666706 Update release notes for 0.11.4 2019-02-15 11:58:56 -07:00
Matthew Fay e9641c5c7e proxy: Re-add pre-existing trailing slashes in AllowedPath (#2151)
* proxy.AllowedPath: Re-append trailing slashes to excepted path comparisons

* Substitute HasSuffix for manual check; check slash was actually removed
2019-02-15 11:53:14 -07:00
Toby Allen 495656f72b basicauth: Log error when authentication fails (#2434)
* log basicauth errors

* Fixed tests

* Fix log include

* Remove some unneeded blank lines.
2019-02-15 11:50:30 -07:00
Matthew Holt c70d4a4cf6 Update pull request and issue templates 2019-02-15 10:35:51 -07:00
Matthew Holt 39c5d6b964 caddytls: Remove repeated variable declaration 2019-02-14 17:44:58 -07:00
Matthew Holt 0c69e9ed7f Merge branch 'tlscluster' 2019-02-14 17:21:25 -07:00
Matthew Holt 0a95b5d359 caddytls: Move config of certmagic storage to NewConfig (fixes #2465)
Breaking API change for server type plugins that use caddytls package.
Now an error value is returned from NewConfig as well. Sorry about that.
2019-02-14 17:20:06 -07:00
linquize 6246d4c3ca Expose JSON <-> Caddyfile conversion via command line flags (#2374)
* Expose JSON <-> Caddyfile conversion via command line flags stdin / stdout

*  cli caddyfile-to-json and json-to-caddyfile
2019-02-13 21:30:14 +00:00
Yue Ko 4de9d64c0c core: Terminate goroutine used for logging errors (#2398)
Terminate the goroutine used for logging errors by using a
WaitGroup (stopWg) to track termination of servers.
Fixes #2358.
2019-02-13 11:51:57 -07:00
Matt Holt 1867ded14c caddytls: Change clustering to be a plugin to the caddytls package (#2459)
* caddytls: Change clustering to be a plugin to the caddytls package

Should resolve the failure in
https://github.com/coredns/coredns/pull/2541.

This change is breaking to clustering plugin developers (not Caddy
users), but logical, since only the caddytls package uses CertMagic
directly (the httpserver package also uses it, but only because it also
uses the caddytls plugin); and it is early enough that no clustering
plugins really exist yet.

This will also require a change of devportal
so that it looks for a different registration function, which has moved
to the caddytls package.

* Remove unused variable

* caddyhttp: Fix test (adjust plugin counting)

* ummmm, remove extra line break

somehow VS Code didn't fmt on save... weird.
2019-02-08 13:06:21 -07:00
Matthew Holt 22db8bcf3d ummmm, remove extra line break
somehow VS Code didn't fmt on save... weird.
2019-02-08 12:56:51 -07:00
Matthew Holt 59e7a8864a caddyhttp: Fix test (adjust plugin counting) 2019-02-08 12:43:20 -07:00
Matthew Holt 7d737427a9 Remove unused variable 2019-02-08 12:28:27 -07:00
Matthew Holt eac939e9a7 caddytls: Change clustering to be a plugin to the caddytls package
Should resolve the failure in
https://github.com/coredns/coredns/pull/2541.

This change is breaking to clustering plugin developers (not Caddy
users), but logical, since only the caddytls package uses CertMagic
directly (the httpserver package also uses it, but only because it also
uses the caddytls plugin); and it is early enough that no clustering
plugins really exist yet.

This will also require a change of devportal
so that it looks for a different registration function, which has moved
to the caddytls package.
2019-02-08 12:25:01 -07:00
Matthew Holt 2ea544e9a0 Notes for v0.11.3 2019-02-05 13:14:50 -07:00
Matthew Holt 87b645386f vendor: Update lego 2019-02-05 11:27:09 -07:00
Michael Li e3ba9ffff2 telemetry: Improve parsing of disabled-metrics flag (#2389)
* optimized parse cli's disabledMetrics flag string to initTelemetry

* add splitTrim to obtain string slice that not contain empty string

* change TestSplitTrim error output

* gofmt for run_test.go

* restore name of disabledMetrics made more sense

* optimized TestSplitTrim case

* just update splitTrim comment to force CI restart
2019-02-05 10:33:52 -07:00
Danny Navarro e0efb027da proxy: Implement own CA certificates of backends (#2454)
By using option ca_certificates in proxy block it is possible now to select
CA against which backend certificates shall be checked.

Resolves #1550

Co-authored-by: Danny Navarro <navdgo@gmail.com>
2019-02-05 10:16:08 -07:00
Matt Holt 9e4a29191c caddytls: Fix handling of IP-only TLS configs and empty-SNI handshakes (#2452)
* caddytls: Fix empty SNI handling (new -default-sni flag)

vendor: update certmagic, needed to support this

Hopefully fixes #2451, fixes #2438, and fixes #2414

* caddytls: Don't overwrite certmagic Manager (fixes #2407)

Supersedes #2447

* vendor: Update certmagic to fix nil pointer deref and TLS-ALPN cleanup

* Improve -default-sni flag help text
2019-02-05 09:30:22 -07:00
Toby Allen fa10b0275f on: Roll back additional instance_startup event from #2161 (#2453) 2019-02-04 13:59:13 -07:00
Matthew Holt 4f8ff09551 staticfiles: Require method GET
Other methods are not currently implemented in the static file server
2019-02-02 18:36:20 -07:00
Matthew Holt f2491580e0 httpserver: Fix address display and routing for IPv6 IPs/wildcards 2019-02-02 14:32:37 -07:00
Josh Soref 8369a12115 Fix spelling (#2448)
* spelling: access

* spelling: associated

* spelling: because

* spelling: characteristics

* spelling: chooses

* spelling: cleared

* spelling: clustering

* spelling: collaborators

* spelling: connection

* spelling: content

* spelling: conversion

* spelling: deferring

* spelling: detection

* spelling: displayed

* spelling: dispenser

* spelling: heuristic

* spelling: nonexistent

* spelling: oflisting

* spelling: preparses

* spelling: response

* spelling: responder

* spelling: sensitive

* spelling: specify

* spelling: simple

* spelling: spawning

* spelling: status

* spelling: unsupported

* spelling: upstream

* spelling: username

* spelling: whether
2019-01-29 10:51:10 -07:00
Matthew Holt 97e1f14dd3 httpserver: Revert misleading comment 2019-01-28 10:31:31 -07:00
Matthew Holt 930ca1cc1b main,log,errors: Option to disable log rotation ("rolling")
For log and errors directive, as well as process log.
2019-01-28 10:28:22 -07:00
Matthew Holt 23627bbf54 caddy: Improve error messages when (re)starting servers 2019-01-28 10:28:22 -07:00
Matthew Holt 2fc615b405 appveyor: Don't run checks twice with PR 2019-01-28 10:28:22 -07:00
Sebastian Hutter a36c7c7e87 Disable basic authentication for OPTIONS method (#2415)
Execute an OPTIONS call and make sure we receive a valid response
independently of the provided username or password as the
authentication step is ignored

* Do not authenticate OPTIONS calls
* Add test for OPTIONS call
2019-01-28 10:26:22 +00:00
Abiola Ibrahim fdec3c68f0 Merge pull request #2443 from maxheyer/master
Add header X-Forwarded-Port to preset transparent
2019-01-26 08:30:12 +01:00
Abiola Ibrahim 0ecc5c46bf Merge branch 'master' into master 2019-01-26 08:20:17 +01:00
Bryan Burke a947f70c56 httpserver: add extauth plugin directive (#2444) 2019-01-26 00:12:43 -07:00
Max Heyer c259381541 Add header X-Forwarded-Port with placeholder {server_port} to preset proxy preset transparent 2019-01-26 00:42:51 +01:00
Max Heyer 7f546e529e httpserver: Implement {sever_port} placeholder (#2424) 2019-01-25 20:54:33 +00:00
Matthew Holt a7aeb979be caddytls: Use IP address to find config; vendor: update certmagic
Closes #2356
2019-01-21 18:58:15 -07:00
elcore 771dcf3d40 caddy: move EmitEvent(InstanceStartupEvent, instance) (#2161)
* caddy: move EmitEvent(InstanceStartupEvent, instance)

* caddy: update SupportedEvents
2019-01-18 10:46:21 -07:00
Matthew Holt f3a4f46d78 vendor: Update certmagic; fix #2400 2019-01-18 10:39:00 -07:00
Wèi Cōngruì 78455c7cb9 caddytls: set certmagic.Config.Email when parsing config file (#2432) 2019-01-18 07:25:41 -07:00
Matthew Holt 01f2b85826 vendor: Update certmagic and lego 2019-01-17 11:12:11 -07:00
Oleg Kovalov 7fe9e13fbf caddyhttp: use strings.EqualFold (#2413) 2019-01-16 22:51:55 -07:00
Jeffrey Zhao f92a3aa0e5 gzip: avoid unnecessary allocations when not compressing (#2396) 2019-01-16 22:43:31 -07:00
Marten Seemann 917534e35e vendor: update quic-go to v0.10.1 (#2431) 2019-01-16 21:38:10 -07:00
Matthew Holt 8ab447e615 Cut release 0.11.2 2019-01-16 16:04:42 -07:00
Adam Woodbeck 0d8384a9b4 caddyfile: Support 'import' inside directives (#2428) 2019-01-14 22:08:54 -07:00
Marten Seemann e14328b71b tls: Set a GetCertificate callback in the tls.Config (#2404)
A tls.Config must have Certificates or GetCertificate set, in order to
be accepted by tls.Listen and quic.Listen.
2019-01-13 21:39:17 -07:00
Henrique Dias f5aaa471de httpserver: remove jekyll, hugo. Replace by filebrowser (#2417) 2019-01-08 06:30:18 -07:00
Matthew Holt 0b83014ff8 caddytls: Use latest certmagic package, with updated Storage interface 2018-12-19 21:53:52 -07:00
Kurtis Rader 0684cf8611 Implement {when_iso_local} placeholder (#2363)
Implement `{when_iso_local}` placeholder

This implements the `{when_iso_local}` placeholder. This is like the
`{when_iso}` placeholder but the output is in the current timezone
rather than UTC.

Resolves #2362
2018-12-18 22:42:05 +00:00
Matthew Holt 1570bc5d03 caddytls: Fix race condition in tests 2018-12-13 07:34:00 -07:00
Matthew Holt 8811853f6d caddytls: Better handle FileStorage and cleaning up locks on exit 2018-12-13 07:06:47 -07:00
Matthew Holt b7028b139f vendor: Update certmagic to include List fix 2018-12-12 14:50:25 -07:00
Matthew Holt 620f9687c8 Merge branch 'reload-ln-middleware' 2018-12-11 21:32:23 -07:00
Matthew Holt 2c43616781 readme: Add certmagic link 2018-12-11 19:46:06 -07:00
Matthew Holt d1171af679 httpserver: Don't obtain certs for unmanaged configs (fixes #2387) 2018-12-11 19:37:08 -07:00
Matthew Holt 598de9e6d9 vendor: Update certmagic 2018-12-11 19:36:46 -07:00
Matthew Holt 393bc2992e Add clustering plugin types; use latest certmagic.Storage interface 2018-12-11 12:13:48 -07:00
Matthew Holt 33f2b16a1b Merge branch 'certmagic' 2018-12-10 20:08:55 -07:00
Matthew Holt f03ad80701 Update tests after large refactor 2018-12-10 20:08:29 -07:00
Matthew Holt a68b01080c vendor: Update dependencies; add certmagic, update lego 2018-12-10 20:00:34 -07:00
Matthew Holt e0f1a02c37 Extract most of caddytls core code into external CertMagic package
All code relating to a caddytls.Config and setting it up from the
Caddyfile is still intact; only the certificate management-related
code was removed into a separate package.

I don't expect this to build in CI successfully; updating dependencies
and vendor is coming next.

I've also removed the ad-hoc, half-baked storage plugins that we need
to finish making first-class Caddy plugins (they were never documented
anyway). The new certmagic package has a much better storage interface,
and we can finally move toward making a new storage plugin type, but
it shouldn't be configurable in the Caddyfile, I think, since it doesn't
make sense for a Caddy instance to use more than one storage config...

We also have the option of eliminating DNS provider plugins and just
shipping all of lego's DNS providers by using a lego package (the
caddytls/setup.go file has a comment describing how) -- but it doubles
Caddy's binary size by 100% from about 19 MB to around 40 MB...!
2018-12-10 19:49:29 -07:00
Abiola Ibrahim 2358102c07 Merge pull request #2384 from mholt/francislavoie-patch-1
Fix `s3browser` plugin name
2018-12-08 09:10:02 +00:00
Francis Lavoie 1533652b78 Fix s3browser plugin name
Thanks to @webprofusion-chrisc for spotting this: https://github.com/mholt/caddy/pull/2383#issuecomment-445432256
2018-12-08 03:55:34 -05:00
techknowlogick c7562e46a4 httpserver: Add s3browser directive (#2383)
Fixes https://github.com/techknowlogick/caddy-s3browser/issues/2
2018-12-07 19:17:36 -07:00
Matthew Holt 8f583dcf36 vendor: Update github.com/xenolf/lego/acme to latest 2018-12-05 18:01:22 -07:00
Matt Holt 09188981c4 tls: Add support for the tls-alpn-01 challenge (#2201)
* tls: Add support for the tls-alpn-01 challenge

Also updates lego/acme to latest on master.

TODO: This implementation of the tls-alpn challenge is not yet solvable
in a distributed Caddy cluster like the http challenge is.

* build: Allow building with the race detector

* tls: Support distributed solving of the TLS-ALPN-01 challenge

* Update vendor and add a todo in MITM checker
2018-12-05 17:33:23 -07:00
linquize ae5f013a48 Fix linter warning (#2375) 2018-12-02 15:39:58 -07:00
Matthew Holt b7091650f8 Revert "bind: support multiple values (#2128)"
This reverts commit 3a810c6502.
2018-11-27 15:57:38 -07:00
zhsj 3a810c6502 bind: support multiple values (#2128)
Signed-off-by: Shengjing Zhu <i@zhsj.me>
2018-11-26 18:27:58 -07:00
Simon Legner 764c9ec956 browse: filter on document load (#2368)
When using the Browse directive, this applies the filter when using the browser history to go the
previous directory.
2018-11-25 19:35:15 +00:00
Wèi Cōngruì ce0988f48a templates: delete ETag and Last-Modified headers (#2338)
Fixes #1920
2018-11-18 14:06:54 -07:00
Christoph Blecker 1c92557c8b Fix line endings (#2351) 2018-11-18 14:04:20 -07:00
linquize 8f7a1d6a25 Prevent errors test fail if $GOPATH is something like /home/user/caddy (#2347) 2018-11-17 21:51:08 +00:00
Matthew Holt 1b085efa47 Add Relica as sponsor on readme 2018-11-17 10:22:45 -07:00
linquize d9e6e7ffa5 *.txt, *.tpl, *.htm, *.html, *.md should always use eol=lf (#2345) 2018-11-17 09:24:54 -07:00
Makeev Ivan 05d0b213a9 proxy: HTTP status 499 for 'Context canceled' errors (#2297)
* Adding {when_unix_ms} requests placeholder (unix timestamp with a milliseconds precision)

* Add an 499 HTTP status code on user's cancel request as NGINX doing (instead of 502 Bad Gateway status with 'Context canceled' message)

* 499 HTTP status code was added as constant CustomStatusContextCancelled = 499
2018-11-16 13:52:34 -07:00
Matthew Holt 6f580c6aa3 Add flags for JS/WASM builds 2018-11-12 14:46:14 -07:00
Matthew Holt 1d9a094315 Update changes/readme for v0.11.1. 2018-11-12 14:39:01 -07:00
Matt Holt f6e50890b3 caddytls: Raise TLS alert if no certificate matches SAN (closes #1303) (#2339)
* caddytls: Raise TLS alert if no certificate matches SAN (closes #1303)

I don't love this half-baked solution to the issue raised in #1303 way
more than a year after the original issue was closed (the necro comments
are about an issue separate from the original issue that started it),
but I do like TLS alerts more than wrong certificates.

* Restore test to match

* Restore another previous test
2018-11-12 14:24:07 -07:00
Jake Lucas 22dfb140d0 proxy: Add new fallback_delay sub-directive (#2309)
* Updates the existing proxy and reverse proxy tests to include a new fallback delay value

* Adds a new fallback_delay sub-directive to the proxy directive and uses it in the creation of single host reverse proxies
2018-10-30 12:02:59 -06:00
16yuki0702 15455e5a7e caddytls: Improve flaky test related to email (#2318)
Signed-off-by: Hiroyuki Sasagawa <hs19870702@gmail.com>
2018-10-30 11:59:23 -06:00
Billie Cleek f46da403d8 caddyfile: fix env var expansion after Go template (#2304) 2018-10-30 11:58:37 -06:00
Matthew Holt 4f5df39bdd caddy: Re-invoke listener middlewares on reloads and upgrades 2018-10-30 11:55:28 -06:00
Darshan Chaudhary 1f8d1df4ec caddyfile: Allow partial directive env var expansion (#2253) 2018-10-30 11:41:41 -06:00
Matthew Holt dd83687447 readme: Add AWS marketplace link 2018-10-29 21:05:19 -06:00
Ruslan Drozhdzh 3ce3f3a96a caddy: Run OnShutdown callbacks before instance Stop() calls (#2320)
- see https://github.com/coredns/coredns/issues/1666#issuecomment-380624422
2018-10-29 18:25:36 -06:00
Eugen Kleiner 86060ef9b4 caddy: Add OnRestartFailed callback (#2262)
* Add callback OnRestartFailed to caddy.Controller

* markdown: Fix 500 error (#2266)

* Addressed the comments

* Update paths for filebrowser plugins

* httpserver: update minify ordering (#2273)

* Bump required version of golang to 1.10 in README.md (#2267)

Adding TLS client cert placeholders #2217 uses features of go
v1.10.  Update README requirements accordingly.

* Update CI to use Go 1.11

* caddytls: gofmt (Go 1.11) (#2241)

* Ensure assets path exists before writing UUID file

* Adding {when_unix_ms} requests placeholder (unix timestamp with a milliseconds precision) (#2260)

* update to quic-go v0.10.0 (#2288)

quic-go now vendors all of its dependencies, so we don't need to vendor
them here.

Created by running:
gvt delete github.com/lucas-clemente/quic-go
gvt delete github.com/bifurcation/mint
gvt delete github.com/lucas-clemente/aes12
gvt delete github.com/lucas-clemente/fnv128a
gvt delete github.com/lucas-clemente/quic-go-certificates
gvt delete github.com/aead/chacha20
gvt delete github.com/hashicorp/golang-lru
gvt fetch -tag v0.10.0-no-integrationtests github.com/lucas-clemente/quic-go

* fastcgi: Add default timeouts (#2265)

Default fastcgi timeout is 60 seconds
Add tests

* Fix AppVeyor builds (#2289)

* Attempting to fix AppVeyor builds

* Trying again, 2015 image this time

* Use Appveyor's Go 1.11 stack

* Restore GOPATH\bin to PATH and delete old image config

* Add gcc to path manually

* Addressed the comments

* Fix broken link to sourcegraph in README (#2285)

* Fix deadlock, ensure instances mutex unlocked (#2296)

it's a stupid mistake

* proxy: Use DualStack=true in defaultDialer (#2305)

* ci: get golint tool from `golang.org/x/lint/golint` (#2324)

* templates: TLSVersion (#2323)

* new template action: TLS protocol version

* new template action: use caddytls.GetSupportedProtocolName

Avoids code duplication by reusing existing method to get TLS protocol
version used on connection. Also adds tests

* Don't return error on onRestartFail. Only log it.
2018-10-29 18:00:44 -06:00
Kris Kwiatkowski d3e3fc533f templates: TLSVersion (#2323)
* new template action: TLS protocol version

* new template action: use caddytls.GetSupportedProtocolName

Avoids code duplication by reusing existing method to get TLS protocol
version used on connection. Also adds tests
2018-10-19 11:51:10 -06:00
Zoe 03b10f9c8e ci: get golint tool from golang.org/x/lint/golint (#2324) 2018-10-16 11:56:41 -06:00
Jake Lucas f7757da7ed proxy: Use DualStack=true in defaultDialer (#2305) 2018-09-30 08:17:04 -06:00
yyqbuct 13f9c34d16 Fix deadlock, ensure instances mutex unlocked (#2296)
it's a stupid mistake
2018-09-18 09:00:10 -06:00
Zachary J Miller 13a54dbdda Fix broken link to sourcegraph in README (#2285) 2018-09-07 15:24:07 -06:00
Matt Holt 7ed7a95524 Fix AppVeyor builds (#2289)
* Attempting to fix AppVeyor builds

* Trying again, 2015 image this time

* Use Appveyor's Go 1.11 stack

* Restore GOPATH\bin to PATH and delete old image config

* Add gcc to path manually
2018-09-07 15:11:25 -06:00
Alexander Danilov d47b041923 fastcgi: Add default timeouts (#2265)
Default fastcgi timeout is 60 seconds
Add tests
2018-09-02 15:28:10 -06:00
Marten Seemann dfbc2e81e3 update to quic-go v0.10.0 (#2288)
quic-go now vendors all of its dependencies, so we don't need to vendor
them here.

Created by running:
gvt delete github.com/lucas-clemente/quic-go
gvt delete github.com/bifurcation/mint
gvt delete github.com/lucas-clemente/aes12
gvt delete github.com/lucas-clemente/fnv128a
gvt delete github.com/lucas-clemente/quic-go-certificates
gvt delete github.com/aead/chacha20
gvt delete github.com/hashicorp/golang-lru
gvt fetch -tag v0.10.0-no-integrationtests github.com/lucas-clemente/quic-go
2018-09-02 15:18:54 -06:00
Makeev Ivan 9edc16e4d6 Adding {when_unix_ms} requests placeholder (unix timestamp with a milliseconds precision) (#2260) 2018-08-28 11:08:55 +01:00
Matthew Holt 73273c5bf8 Ensure assets path exists before writing UUID file 2018-08-26 09:13:59 -06:00
elcore 93c5256318 caddytls: gofmt (Go 1.11) (#2241) 2018-08-24 16:43:56 -06:00
Matthew Holt 3ccad1814e Update CI to use Go 1.11 2018-08-24 16:41:15 -06:00
Brad Beveridge 35269572d7 Bump required version of golang to 1.10 in README.md (#2267)
Adding TLS client cert placeholders #2217 uses features of go
v1.10.  Update README requirements accordingly.
2018-08-24 11:09:49 -06:00
Henrique Dias a457b35750 httpserver: update minify ordering (#2273) 2018-08-22 09:19:37 -06:00
Francis Lavoie 5e5f9b0563 caddyhttp: Update paths for filebrowser plugins
Update paths for filebrowser plugins
2018-08-21 22:06:27 -04:00
cmulk 16722e4d99 Update paths for filebrowser plugins 2018-08-21 10:30:42 -05:00
Alexander Danilov 89c20f9a55 markdown: Fix 500 error (#2266) 2018-08-15 23:35:06 -06:00
Alexander Danilov d3b731e925 proxy: Fix 502 errors for requests without headers (#2188)
* Fix 502 errors for requests without headers

* Add unexported roundRobinPolicier

We have to preserve state for fallback mode of Header policy, so
it's required to save state in some variable
2018-08-07 17:01:24 -06:00
Jiri Tyr 3e0695ee31 ci: Go version bump for Windows builds (#2236) 2018-07-30 08:36:20 -06:00
Jiri Tyr 9239f3cbcc Adding TLS client cert placeholders (#2217)
* Adding TLS client cert placeholders

* Use function to get the peer certificate

* Changing SHA1 to SHA256

* Use UTC instead of GMT

* Adding tests

* Adding getters for Protocol and Cipher
2018-07-28 09:26:24 +01:00
Felix Lange b7a7fd4651 caddy/caddymain: make EnableTelemetry a variable (#2191)
This makes setting the flag easier in projects that embed package caddymain.
2018-07-26 14:16:30 -06:00
Henrique Dias 06b067b02c caddyhttp: correct import path for filemanager plugins (#2211) 2018-07-26 14:15:18 -06:00
Albert ten Napel dfb5aa6dc6 caddytls: Remove weak cipher suites from the defaults. (#2227) 2018-07-26 14:03:37 -06:00
Richard Hartmann f56696f478 rlimit_posix.go: Use backticks for shell code (#2235) 2018-07-26 13:55:42 -06:00
Silver fcbb90a9af gitignore: Don't ignore the caddyfile/ package (#2237) (#2238) 2018-07-26 13:54:10 -06:00
emersion be84b74d01 httpserver: Register 'wkd' (Web Key Directory) directive (#2239) 2018-07-26 13:50:18 -06:00
Sebastian Pipping bb5b01c911 browse: Improve table layout in Firefox (fixes #2179) (#2221)
Current Caddy code used a combination of CSS styles that
some mainstream browsers (e.g. Firefox) do not support well:
"td:first-child { width: 100%; }" together with
"td:last-child { padding-right: 5%; }".

The old approach was three columns with:
- "Name": 100% width, 5% padding left
- "Size": minimal width
- "Modified": minimal width, 5% padding right

Now the new approach is five columns with:
- <Dummy>: 5% width
- "Name": 80% width
- "Size": minimal width
- "Modified": minimal width
- <Dummy>: 5% width
2018-07-19 11:03:44 -06:00
Sebastian Pipping 3ca6bc4a66 browse: Set page background color (fixes #2214) (#2222)
Browser defaults vary and this way we prevent mix-ups
from setting only some colors.
2018-07-12 18:22:30 -06:00
Augusto Roman 053373a385 caddyfile: Fix multi-file snippets and import literals. (#2205)
* Fix a few import problems: snippets and import literals.

Two problems are fixed by this code simplification:
1. Snippets defined in one import file are strangely not available in
   another.
2. If an imported file had a directive with an argument "import", then
   the rest of the tokens on the line would be converted to absolute
   filepaths.

An example of #2 would be the following directive in an imported file:
    basicauth / import secret

In this case, the password would actually be an absolute path to the
file 'secret' (whether or not it exists) in the directory of the imported
Caddyfile.

The problem was the blind token processing to fix import paths in the
imported tokens without considering the context of the 'import' token.

My first inclination was to just add more context (detect 'import' tokens
at the beginning of lines and check the value tokens against defined
snippets), however I eventually realized that we already do all of this
in the parser, so the code was redundant. Instead we just use the current
token's File property when importing. This works fine with imported tokens
since they already have the absolute path to the imported file!

Fixes #2204

* renamed file2 -> fileName

* Fix copy/pasted comment in test.

* Change gzip example to basicauth example.

This makes it more clear how the import side effect is detrimental.
2018-06-28 10:06:52 -06:00
smlx e263566673 init: Fix configuration permissions in systemd integration. (#2130)
This fixes the permissions on /etc/caddy to match standard linux
permissions for /etc, and makes the Caddyfile read-only for the caddy
user.
2018-06-19 09:15:38 -06:00
Denis 6965075825 core: instance restart (reload) event (#2178) 2018-06-12 17:00:53 -06:00
magikstm e54dfa49c3 gzip: Add .m3u and .m3u8 (HLS playlist files) (#2182) 2018-06-07 23:38:55 -06:00
Alexander Danilov accaa378f0 Add -env-file flag (#2176)
This adds new feature to load envs from file provided from command line argument
Implement parsing of the env file for simple KEY=VALUE format
2018-05-28 09:22:21 -06:00
Abiola Ibrahim 60a0208e8d Merge pull request #2172 from SmilingNavern/add_request_scheme
Add REQUEST_SCHEME to fastcgi envs
2018-05-18 03:57:39 +01:00
Alexander Danilov 2aaaa368bb Add REQUEST_SCHEME to fastcgi envs
Fixes https://github.com/mholt/caddy/issues/2152
2018-05-18 01:20:19 +03:00
Abiola Ibrahim 4829cc6aaf Merge pull request #2158 from caldwell/script_filename-fix
fastcgi: strip PATH_INFO from SCRIPT_FILENAME (mirroring SCRIPT_NAME)
2018-05-17 20:25:37 +01:00
Abiola Ibrahim 553acf93e2 Merge branch 'master' into script_filename-fix 2018-05-17 20:06:48 +01:00
Matthew Holt f058419042 Change UUID file with CADDY_UUID_FILE environment variable 2018-05-15 19:39:15 -06:00
Matthew Holt 13268db536 Update readme with regards to telemetry 2018-05-10 11:31:31 -06:00
Matthew Holt 1f7b5abc80 Version 0.11 2018-05-10 09:45:05 -06:00
Matthew Holt c667f81866 telemetry: Use int64 constant for duration interval
Otherwise it overflows int type on 32-bit builds
2018-05-10 09:41:57 -06:00
Matthew Holt b321c00a8f telemetry: Use production endpoint 2018-05-10 09:27:03 -06:00
Matthew Holt 9160789b42 telemetry: Make http_user_agent a normalized field
This way we store a short 8-byte hash of the UA instead of the full
string; exactly the same way we store TLS ClientHello info.
2018-05-10 08:57:25 -06:00
Matthew Holt df7cdc3fae telemetry: Add memory and goroutine metrics, rename container
And fix a typo in a comment, sigh
2018-05-09 22:36:23 -06:00
Matthew Holt 86fd2f22fb telemetry: Add in_container metric
Knowing whether Caddy is running in a container is super-useful for
debugging and troubleshooting, as well as for making development-time
decisions, because Docker is one of the top contributors to our
user support burden.

Thanks to Eldin for helping to test it.
2018-05-09 17:20:38 -06:00
Matt Holt 148a6f4430 Merge pull request #2079 from mholt/telemetry
Caddy telemetry: a global, server-side perspective of the health of the Internet
2018-05-09 04:52:40 -06:00
Matthew Holt b05006663f telemetry: Add variance to retry interval, and disable keepalive 2018-05-08 22:54:12 -06:00
David Caldwell 5f1f8e4ee6 fastcgi: strip PATH_INFO from SCRIPT_FILENAME (mirroring SCRIPT_NAME) 2018-05-08 17:47:12 -07:00
Matthew Holt ef48e17e79 caddytls: Fix tests 2018-05-07 17:04:39 -06:00
Matthew Holt fe03c1aefa telemetry: Fix MITM tests 2018-05-07 16:42:35 -06:00
Matthew Holt 078770a5a6 telemetry: Record TLS ClientHellos by hash of key of structured data
Also improve handling of disabled metrics, and record TLS ClientHello
in association with User-Agent
2018-05-07 16:09:39 -06:00
Guilherme Bernal 294f6957f0 tls: Fix typo in error message, "incompabile" (#2147) 2018-05-01 13:45:23 -06:00
Wèi Cōngruì fe664c00ff proxy: initialize ReverseProxy.Transport earlier and fix TCP connection leak (#2134) 2018-04-28 08:32:20 -06:00
Matthew Holt 518edd3cd4 Corrected permissions for UUID file 2018-04-20 00:04:44 -06:00
Matthew Holt b019501b8b Merge branch 'master' into telemetry
# Conflicts:
#	caddy/caddymain/run.go
#	caddyhttp/httpserver/plugin.go
#	caddytls/client.go
2018-04-20 00:03:57 -06:00
Matthew Holt 2922d09bef Version 0.10.14 2018-04-19 18:11:50 -06:00
Matthew Holt 97487e6f0d vendor: Update lego to fix error handling bug (closes #2124) 2018-04-19 18:07:12 -06:00
Matthew Holt 694d2c9b2e Version 0.10.13 2018-04-18 17:09:54 -06:00
Matthew Holt a674c0051a vendor: Update quic and lego/acme dependencies 2018-04-18 15:48:08 -06:00
Tanmay Chaudhry 98de336a21 proxy: Enabled configurable timeout (#2070)
* Enabled configurable Timeout for the proxy directive

* Added Test for reverse for proxy timeout

* Removed Duplication in proxy constructors

* Remove indirection from multiple constructors and refactor into one

* Fix inconsistent error message and refactor dialer initialization
2018-04-17 08:09:22 -06:00
Abiola Ibrahim 9fe2ef417c rewrite: Regular expression support for simple rule (#2082)
* Regexp support for simple rewrite rule

* Add negate option for simplicity

* ascertain explicit regexp char
2018-04-14 19:40:55 -06:00
Theofanis Despoudis 88edca65d3 proxy: Fix transparent pass-thru of existing X-Forwarded-For headers
* Fixes #1960 Transparent proxy not appending
existing X-Forwarded-For header

* Fixes #1960 Formatting Code
2018-04-05 00:04:06 -06:00
Matt Holt 64c18a7c6c caddyfile: Fix errors caught by fuzzing (#2097)
* caddyfile: More robust parsing for 'import' (fixes #2096)

The fix for hanging involves limiting the number of wildcards in an
import pattern to just 1. Otherwise some patterns can expand to the
entire disk.

The other fix requires that the end string for an environment variable
expansion come after the start string.

* caddyfile: Fix more fuzzing errors
2018-04-03 11:54:32 -06:00
Matthew Holt d2fc045219 Update contributing instructions related to docs 2018-04-02 08:17:37 -06:00
Matthew Holt 917a604094 httpserver: Ignore ErrServerClosed when closing server 2018-04-02 08:17:21 -06:00
Lucas Lorentz b33b24fc9e httpserver: Add 'supervisor' directive (#2061) 2018-03-31 17:31:35 -06:00
Matt Holt 4d9ee000c8 httpserver: Prevent TLS client authentication bypass in 3 ways (#2099)
- Introduce StrictHostMatching mode for sites that require clientauth
- Error if QUIC is enabled whilst TLS clientauth is configured
  (Our QUIC implementation does not yet support TLS clientauth, but
  maybe it will in the future - fixes #2095)
- Error if one but not all TLS configs for the same hostname have a
  different ClientAuth CA pool
2018-03-30 14:40:04 -06:00
Matthew Holt 2966db7b78 httpserver: Fix test that relies on external DNS lookup
Apparently Cloudflare just caused 1.1.1.1 to resolve, so we have to
change our test IP, hopefully this is better
2018-03-30 06:39:46 -06:00
Matt Holt 38e65e28d4 tls: Fix tests on Windows (#2093) 2018-03-28 12:42:47 -06:00
Matthew Holt 73b61af58d tls: Prevent directory traversal via On-Demand TLS (fixes #2092) 2018-03-28 12:04:35 -06:00
Francis Lavoie 858e96f21c readme: Update instructions for contributing to docs (#2089) 2018-03-27 18:05:53 -06:00
Matthew Holt 8039a7127f telemetry: Remove a metric, clarify another, and fix tests 2018-03-25 21:50:07 -06:00
Matthew Holt 33aeb1cb5c telemetry: Add CLI option to selectively disable some metrics
Also fix a couple metrics that were named wrong or reported in excess.
2018-03-23 23:44:16 -06:00
Matthew Holt 8bdd13b594 telemetry: Honor the server's request to toggle certain metrics 2018-03-22 19:50:38 -06:00
Matthew Holt 52316952a5 Refactor diagnostics -> telemetry 2018-03-22 18:05:31 -06:00
Matthew Holt 7c868afd32 diagnostics: Specially handle HTTP 410 and 451 codes
An attempt to future-proof older Caddy instances so that they won't
keep trying to send telemetry to endpoints that just simply aren't
going to be available
2018-03-21 17:51:07 -06:00
Matthew Holt 4df8028bc3 diagnostics: Add/remove metrics 2018-03-21 17:01:14 -06:00
Matthew Holt 385ea53309 diagnostics: Use Retry-After header if decoding JSON fails
Improve error message and backoff as well
2018-03-18 15:49:17 -06:00
Matthew Holt a6521357e5 Fix bad merge conflict, make tests pass 2018-02-16 23:20:08 -07:00
Matthew Holt 269a8b5fce Merge branch 'master' into diagnostics
# Conflicts:
#	plugins.go
#	vendor/manifest
2018-02-16 22:42:14 -07:00
Matthew Holt 5820356cf6 diagnostics: Persist UUID in string format for convenience 2018-02-10 20:21:16 -07:00
Matthew Holt 6b3c2212a1 diagnostics: AppendUnique(), restructure sets, add metrics, fix bugs 2018-02-10 12:59:23 -07:00
elcore 703cf7bf8b vendor: delete github.com/codahale/aesnicheck in favor of cpuid (#2020) 2018-02-09 10:39:21 -07:00
Matthew Holt 3e00e18adc diagnostics: Point to staging endpoint 2018-02-08 23:37:42 -07:00
Matthew Holt 6c17e4d4c8 diagnostics: Add a few tests 2018-02-08 21:15:28 -07:00
Matthew Holt 388ff6bc0a diagnostics: Implemented collection functions and create first metrics
- Also implemented robust error handling and failovers
- Vendored klauspost/cpuid
2018-02-08 19:55:44 -07:00
Matthew Holt 8f0b44b8a4 Create diagnostics package; persist UUID 2018-02-02 19:15:28 -07:00
1198 changed files with 8092 additions and 438441 deletions
+7
View File
@@ -9,6 +9,13 @@
# go fmt will enforce this, but in case a user has not called "go fmt" allow GIT to catch this:
*.go text eol=lf core.whitespace whitespace=indent-with-non-tab,trailing-space,tabwidth=4
go.mod text eol=lf
go.sum text eol=lf
*.txt text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2
*.tpl text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2
*.htm text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2
*.html text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2
*.md text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2
*.yml text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2
.git* text eol=auto core.whitespace whitespace=trailing-space
+40 -15
View File
@@ -23,15 +23,15 @@ Other menu items:
### Contributing code
You can have a direct impact on the project by helping with its code. To contribute code to Caddy, open a [pull request](https://github.com/mholt/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/mholt/caddy/-/search).
You can have a huge impact on the project by helping with its code. To contribute code to Caddy, 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/-/search).
We hold contributions to a high standard for quality :bowtie:, so don't be surprised if we ask for revisions&mdash;even if it seems small or insignificant. Please don't take it personally. :wink: If your change is on the right track, we can guide you to make it mergable.
We hold contributions to a high standard for quality :bowtie:, so don't be surprised if we ask for revisions&mdash;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 mergable.
Here are some of the expectations we have of contributors:
- If your change is more than just a minor alteration, **open an issue to propose your change first.** This way we can avoid confusion, coordinate what everyone is working on, and ensure that changes are in-line with the project's goals and the best interests of its users. If there's already an issue about it, comment on the existing issue to claim it.
- **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.
- **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 DON'T do.](https://twitter.com/iamdevloper/status/397664295875805184)
- **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.
@@ -52,17 +52,17 @@ We often grant [collaborator status](#collaborator-instructions) to contributors
Contributing to Go projects on GitHub is fun and easy. We recommend the following workflow:
1. [Fork this repo](https://github.com/mholt/caddy). This makes a copy of the code you can write to.
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 (mholt/caddy.git) repo on your computer, get it with `go get github.com/mholt/caddy/caddy`.
2. If you don't already have this repo (caddyserver/caddy.git) repo on your computer, get it with `go get github.com/caddyserver/caddy/caddy`.
3. Tell git that it can push the mholt/caddy.git repo to your fork by adding a remote: `git remote add myfork https://github.com/you/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/you/caddy.git`
4. Make your changes in the mholt/caddy.git repo on your computer.
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/mholt/caddy/pull/new/master) to merge your changes into mholt/caddy @ master. (Click "compare across forks" and change the head fork.)
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.
@@ -71,7 +71,7 @@ This workflow is nice because you don't have to change import paths. You can get
Caddy can do more with plugins! Anyone can write a plugin. Plugins are Go libraries that get compiled into Caddy, extending its feature set. They can add directives to the Caddyfile, change how the Caddyfile is loaded, and even implement new server types (e.g. HTTP, DNS). When it's ready, you can submit your plugin to the Caddy website so others can download it.
[Learn how to write and submit a plugin](https://github.com/mholt/caddy/wiki) on the wiki. You should also share and discuss your plugin idea [on the forums](https://caddy.community) to have people test it out. We don't use the Caddy issue tracker for plugins.
[Learn how to write and submit a plugin](https://github.com/caddyserver/caddy/wiki) on the wiki. You should also share and discuss your plugin idea [on the forums](https://caddy.community) to have people test it out. We don't use the Caddy issue tracker for plugins.
### Getting help using Caddy
@@ -83,7 +83,7 @@ Many people on the forums could benefit from your experience and expertise, too.
### Reporting bugs
Like every software, Caddy has its flaws. If you find one, [search the issues](https://github.com/mholt/caddy/issues) to see if it has already been reported. If not, [open a new issue](https://github.com/mholt/caddy/issues/new) and describe the bug, and somebody will look into it! (This repository is only for Caddy, not plugins.)
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 stop bugs in their tracks!** 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.
@@ -93,17 +93,42 @@ We suggest reading [How to Report Bugs Effectively](http://www.chiark.greenend.o
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 requirements
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 undesired or surprising behavior which violates documentation or the spec.
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.
- Otherwise, the burden is on the reporter to test possible solutions. This is discouraged 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 (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/mholt/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. You don't have to follow the bug template for feature requests. 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.
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 plugins](https://github.com/mholt/caddy/wiki), though, 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.
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 plugins](https://github.com/caddyserver/caddy/wiki), 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.
### Improving documentation
Caddy's documentation is available at [https://caddyserver.com/docs](https://caddyserver.com/docs). If you would like to make a fix to the docs, feel free to contribute at the [caddyserver/website](https://github.com/caddyserver/website) repository!
Caddy's documentation is available at [https://caddyserver.com/v1/docs](https://caddyserver.com/v1/docs). If you would like to make a fix to the docs, please submit an issue here describing the change to make.
Note that plugin documentation is not hosted by the Caddy website, other than basic usage examples. They are managed by the individual plugin authors, and you will have to contact them to change their documentation.
@@ -111,7 +136,7 @@ Note that plugin documentation is not hosted by the Caddy website, other than ba
## Collaborator Instructions
Collabators 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:
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?
+12
View File
@@ -0,0 +1,12 @@
# 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']
-32
View File
@@ -1,32 +0,0 @@
<!--
Are you asking for help with using Caddy? Please use our forum instead: https://caddy.community. If you are filing a bug report, please take a few minutes to carefully answer the following questions. If your issue is not a bug report, you do not need to use this template. Thanks!)
-->
### 1. What version of Caddy are you using (`caddy -version`)?
### 2. What are you trying to do?
### 3. What is your entire Caddyfile?
```text
(paste Caddyfile here)
```
### 4. How did you run Caddy (give the full command and describe the execution environment)?
### 5. Please paste any relevant HTTP request(s) here.
<!-- Paste curl command, or full HTTP request including headers and body, here. -->
### 6. What did you expect to see?
### 7. What did you see instead (give full error messages and/or log)?
### 8. How can someone who is starting from scratch reproduce the bug as minimally as possible?
<!-- Please strip away any extra infrastructure such as containers, reverse proxies, upstream apps, caches, dependencies, etc, to prove this is a bug in Caddy and not an external misconfiguration. Your chances of getting this bug fixed go way up the easier it is to replicate. Thank you! -->
-19
View File
@@ -1,19 +0,0 @@
<!--
Thank you for contributing to Caddy! Please fill this out to help us make the most of your pull request.
-->
### 1. What does this change do, exactly?
### 2. Please link to the relevant issues.
### 3. Which documentation changes (if any) need to be made because of this PR?
### 4. Checklist
- [ ] I have written tests and verified that they fail without my change
- [ ] I have squashed any insignificant commits
- [ ] This change has comments for package types, values, functions, and non-obvious lines of code
- [ ] I am willing to help maintain this change if there are issues with it later
+2 -1
View File
@@ -13,9 +13,10 @@ access.log
/*.conf
Caddyfile
!caddyfile/
og_static/
.vscode/
*.bat
*.bat
-38
View File
@@ -1,38 +0,0 @@
language: go
addons:
hosts:
- quic.clemente.io
go:
- 1.x
- tip
matrix:
allow_failures:
- go: tip
fast_finish: true
before_install:
# Decrypts a script that installs an authenticated cookie
# for git to use when cloning from googlesource.com.
# Bypasses "bandwidth limit exceeded" errors.
# See github.com/golang/go/issues/12933
- if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then openssl aes-256-cbc -K $encrypted_3df18f9af81d_key -iv $encrypted_3df18f9af81d_iv -in dist/gitcookie.sh.enc -out dist/gitcookie.sh -d; fi
install:
- if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then bash dist/gitcookie.sh; fi
- go get -t ./...
- go get github.com/golang/lint/golint
- go get github.com/FiloSottile/vendorcheck
# Install gometalinter
- go get github.com/alecthomas/gometalinter
script:
- gometalinter --install
- gometalinter --disable-all -E vet -E gofmt -E misspell -E ineffassign -E goimports -E deadcode --tests --vendor ./...
- vendorcheck ./...
- go test -race ./...
after_script:
- golint ./...
+69 -18
View File
@@ -4,14 +4,13 @@
<h3 align="center">Every Site on HTTPS <!-- Serve Confidently --></h3>
<p align="center">Caddy is a general-purpose HTTP/2 web server that serves HTTPS by default.</p>
<p align="center">
<a href="https://travis-ci.org/mholt/caddy"><img src="https://img.shields.io/travis/mholt/caddy.svg?label=linux+build"></a>
<a href="https://ci.appveyor.com/project/mholt/caddy"><img src="https://img.shields.io/appveyor/ci/mholt/caddy.svg?label=windows+build"></a>
<a href="https://godoc.org/github.com/mholt/caddy"><img src="https://img.shields.io/badge/godoc-reference-blue.svg"></a>
<a href="https://goreportcard.com/report/mholt/caddy"><img src="https://goreportcard.com/badge/github.com/mholt/caddy"></a>
<a href="https://dev.azure.com/mholt-dev/Caddy/_build?definitionId=5"><img src="https://img.shields.io/azure-devops/build/mholt-dev/afec6074-9842-457f-98cf-69df6adbbf2e/5/master.svg?label=cross-platform%20tests"></a>
<a href="https://godoc.org/github.com/caddyserver/caddy"><img src="https://img.shields.io/badge/godoc-reference-blue.svg"></a>
<a href="https://goreportcard.com/report/caddyserver/caddy"><img src="https://goreportcard.com/badge/github.com/caddyserver/caddy"></a>
<br>
<a href="https://twitter.com/caddyserver" title="@caddyserver on Twitter"><img src="https://img.shields.io/badge/twitter-@caddyserver-55acee.svg" 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>
<a href="https://sourcegraph.com/github.com/mholt/caddy?badge" title="Caddy on Sourcegraph"><img src="https://sourcegraph.com/github.com/mholt/caddy/-/badge.svg" alt="Caddy on Sourcegraph"></a>
<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>
</p>
<p align="center">
<a href="https://caddyserver.com/download">Download</a> ·
@@ -23,7 +22,13 @@
Caddy is a **production-ready** open-source web server that is fast, easy to use, and makes you more productive.
Available for Windows, Mac, Linux, BSD, Solaris, and [Android](https://github.com/mholt/caddy/wiki/Running-Caddy-on-Android).
Available for Windows, Mac, Linux, BSD, Solaris, and [Android](https://github.com/caddyserver/caddy/wiki/Running-Caddy-on-Android).
<p align="center">
<b>Thanks to our special sponsor:</b>
<br><br>
<a href="https://relicabackup.com"><img src="https://caddyserver.com/resources/images/sponsors/relica.png" width="220" alt="Relica - Cross-platform file backup to the cloud, local disks, or other computers"></a>
</p>
## Menu
@@ -51,24 +56,66 @@ Available for Windows, Mac, Linux, BSD, Solaris, and [Android](https://github.co
Altogether, Caddy can do things other web servers simply cannot do. Its features and plugins save you time and mistakes, and will cheer you up. Your Caddy instance takes care of the details for you!
<p align="center">
<b>Powered by</b>
<br>
<a href="https://github.com/mholt/certmagic"><img src="https://user-images.githubusercontent.com/1128849/49704830-49d37200-fbd5-11e8-8385-767e0cd033c3.png" alt="CertMagic" width="250"></a>
</p>
## Install
Caddy binaries have no dependencies and are available for every platform. Get Caddy either of these ways:
Caddy binaries have no dependencies and are available for every platform. Get Caddy any of these ways:
- **[Download page](https://caddyserver.com/download)** (RECOMMENDED) allows you to customize your build in the browser
- **[Latest release](https://github.com/mholt/caddy/releases/latest)** for pre-built, vanilla binaries
- **[Latest release](https://github.com/caddyserver/caddy/releases/latest)** for pre-built, vanilla binaries
- **[AWS Marketplace](https://aws.amazon.com/marketplace/pp/B07J1WNK75?qid=1539015041932&sr=0-1&ref_=srh_res_product_title&cl_spe=C)** makes it easy to deploy directly to your cloud environment. <a href="https://aws.amazon.com/marketplace/pp/B07J1WNK75?qid=1539015041932&sr=0-1&ref_=srh_res_product_title&cl_spe=C" target="_blank">
<img src="https://s3.amazonaws.com/cloudformation-examples/cloudformation-launch-stack.png" alt="Get Caddy on the AWS Marketplace" height="25"/></a>
## Build
To build from source you need **[Git](https://git-scm.com/downloads)** and **[Go](https://golang.org/doc/install)** (1.9 or newer). Follow these instruction for fast building:
To build from source you need **[Git](https://git-scm.com/downloads)** and **[Go](https://golang.org/doc/install)** (1.13 or newer).
- Get the source with `go get github.com/mholt/caddy/caddy` and then run `go get github.com/caddyserver/builds`
- Now `cd $GOPATH/src/github.com/mholt/caddy/caddy` and run `go run build.go`
**To build Caddy without plugins:**
Then make sure the `caddy` binary is in your PATH.
- Run `go get github.com/caddyserver/caddy/caddy`
To build for other platforms, use build.go with the `--goos` and `--goarch` flags.
Caddy will be installed to your `$GOPATH/bin` folder.
With these instructions, the binary will not have embedded version information (see [golang/go#29228](https://github.com/golang/go/issues/29228)), but it is fine for a quick start.
**To build Caddy with plugins (and with version information):**
There is no need to modify the Caddy code to build it with plugins. We will create a simple Go module with our own `main()` that you can use to make custom Caddy builds.
- Create a new folder anywhere and within create a Go file (with an extension of `.go`, such as `main.go`) with the contents below, adjusting to import the plugins you want to include:
```go
package main
import (
"github.com/caddyserver/caddy/caddy/caddymain"
// plug in plugins here, for example:
// _ "import/path/here"
)
func main() {
// optional: disable telemetry
// caddymain.EnableTelemetry = false
caddymain.Run()
}
```
3. `go mod init caddy`
4. Run `go get github.com/caddyserver/caddy`
5. `go install` will then create your binary at `$GOPATH/bin`, or `go build` will put it in the current directory.
**To install Caddy's source code for development:**
- Run `git clone https://github.com/caddyserver/caddy.git` in any folder (doesn't have to be in GOPATH).
You can make changes to the source code from that clone and checkout any commit or tag you wish to develop on.
When building from source, telemetry is enabled by default. You can disable it by changing `caddymain.EnableTelemetry = false` in run.go, or use the `-disabled-metrics` flag at runtime to disable only certain metrics.
## Quick Start
@@ -128,7 +175,7 @@ Caddy is production-ready if you find it to be a good fit for your site and work
**Running as root:** We advise against this. You can still listen on ports < 1024 on Linux using setcap like so: `sudo setcap cap_net_bind_service=+ep ./caddy`
The Caddy project does not officially maintain any system-specific integrations nor suggest how to administer your own system. But your download file includes [unofficial resources](https://github.com/mholt/caddy/tree/master/dist/init) contributed by the community that you may find helpful for running Caddy in production.
The Caddy project does not officially maintain any system-specific integrations nor suggest how to administer your own system. But your download file includes [unofficial resources](https://github.com/caddyserver/caddy/tree/master/dist/init) contributed by the community that you may find helpful for running Caddy in production.
How you choose to run Caddy is up to you. Many users are satisfied with `nohup caddy &`. Others use `screen`. Users who need Caddy to come back up after reboots either do so in the script that caused the reboot, add a command to an init script, or configure a service with their OS.
@@ -137,13 +184,17 @@ If you have questions or concerns about Caddy' underlying crypto implementations
## Contributing
**[Join our forum](https://caddy.community) where you can chat with other Caddy users and developers!** To get familiar with the code base, try [Caddy code search on Sourcegraph](https://sourcegraph.com/github.com/mholt/caddy/-/search)!
**[Join our forum](https://caddy.community) where you can chat with other Caddy users and developers!** To get familiar with the code base, try [Caddy code search on Sourcegraph](https://sourcegraph.com/github.com/caddyserver/caddy/)!
Please see our [contributing guidelines](https://github.com/mholt/caddy/blob/master/.github/CONTRIBUTING.md) for instructions. If you want to write a plugin, check out the [developer wiki](https://github.com/mholt/caddy/wiki).
Please see our [contributing guidelines](https://github.com/caddyserver/caddy/blob/master/.github/CONTRIBUTING.md) for instructions. If you want to write a plugin, check out the [developer wiki](https://github.com/caddyserver/caddy/wiki).
We use GitHub issues and pull requests only for discussing bug reports and the development of specific changes. We welcome all other topics on the [forum](https://caddy.community)!
If you want to contribute to the documentation, please submit pull requests to [caddyserver/website](https://github.com/caddyserver/website).
If you want to contribute to the documentation, please [submit an issue](https://github.com/caddyserver/caddy/issues/new) describing the change that should be made.
### Good First Issue
If you are looking for somewhere to start and would like to help out by working on an existing issue, take a look at our [`Good First Issue`](https://github.com/caddyserver/caddy/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) tag
Thanks for making Caddy -- and the Web -- better!
@@ -154,7 +205,7 @@ Thanks for making Caddy -- and the Web -- better!
- [DNSimple](https://dnsimple.link/resolving-caddy) provides DNS services for Caddy's sites.
- [DNS Spy](https://dnsspy.io) keeps an eye on Caddy's DNS properties.
We thank them for their services. **If you want to help keep Caddy free, please [become a sponsor](https://caddyserver.com/pricing)!**
We thank them for their services. **If you want to help keep Caddy free, please [become a sponsor](https://github.com/sponsors/mholt)!**
## About the Project
-37
View File
@@ -1,37 +0,0 @@
version: "{build}"
hosts:
quic.clemente.io: 127.0.0.1
os: Windows Server 2012 R2
clone_folder: c:\gopath\src\github.com\mholt\caddy
environment:
GOPATH: c:\gopath
install:
- rmdir c:\go /s /q
- appveyor DownloadFile https://storage.googleapis.com/golang/go1.9.windows-amd64.zip
- 7z x go1.9.windows-amd64.zip -y -oC:\ > NUL
- set PATH=%GOPATH%\bin;%PATH%
- go version
- go env
- go get -t ./...
- go get github.com/golang/lint/golint
- go get github.com/FiloSottile/vendorcheck
# Install gometalinter
- go get github.com/alecthomas/gometalinter
build: off
test_script:
- gometalinter --install
- gometalinter --disable-all -E vet -E gofmt -E misspell -E ineffassign -E goimports -E deadcode --tests --vendor ./...
- vendorcheck ./...
- go test -race ./...
after_test:
- golint ./...
deploy: off
+88
View File
@@ -0,0 +1,88 @@
# Mutilated beyond recognition from the example at:
# https://docs.microsoft.com/azure/devops/pipelines/languages/go
trigger:
- master
strategy:
matrix:
linux:
imageName: ubuntu-16.04
gorootDir: /usr/local
mac:
imageName: macos-10.13
gorootDir: /usr/local
windows:
imageName: windows-2019
gorootDir: C:\
pool:
vmImage: $(imageName)
variables:
GOROOT: $(gorootDir)/go
GOPATH: $(system.defaultWorkingDirectory)/gopath
GOBIN: $(GOPATH)/bin
modulePath: '$(GOPATH)/src/github.com/$(build.repository.name)'
# TODO: modules should be the default in Go 1.13, so this won't be needed
GO111MODULE: on
steps:
- bash: |
latestGo=$(curl "https://golang.org/VERSION?m=text")
echo "##vso[task.setvariable variable=LATEST_GO]$latestGo"
echo "Latest Go version: $latestGo"
displayName: "Get latest Go version"
- bash: |
sudo rm -f $(which go)
echo '##vso[task.prependpath]$(GOBIN)'
echo '##vso[task.prependpath]$(GOROOT)/bin'
mkdir -p '$(modulePath)'
shopt -s extglob
shopt -s dotglob
mv !(gopath) '$(modulePath)'
displayName: Remove old Go, set GOBIN/GOROOT, and move project into GOPATH
# Install Go (this varies by platform)
- bash: |
wget "https://dl.google.com/go/$(LATEST_GO).linux-amd64.tar.gz"
sudo tar -C $(gorootDir) -xzf "$(LATEST_GO).linux-amd64.tar.gz"
condition: eq( variables['Agent.OS'], 'Linux' )
displayName: Install Go on Linux
- bash: |
wget "https://dl.google.com/go/$(LATEST_GO).darwin-amd64.tar.gz"
sudo tar -C $(gorootDir) -xzf "$(LATEST_GO).darwin-amd64.tar.gz"
condition: eq( variables['Agent.OS'], 'Darwin' )
displayName: Install Go on macOS
- powershell: |
Write-Host "Downloading Go... (please be patient, I am very slow)"
(New-Object System.Net.WebClient).DownloadFile("https://dl.google.com/go/$(LATEST_GO).windows-amd64.zip", "$(LATEST_GO).windows-amd64.zip")
Write-Host "Extracting Go... (I'm slow too)"
Expand-Archive "$(LATEST_GO).windows-amd64.zip" -DestinationPath "$(gorootDir)"
condition: eq( variables['Agent.OS'], 'Windows_NT' )
displayName: Install Go on Windows
# TODO: When this issue is fixed, replace with installer script:
# https://github.com/golangci/golangci-lint/issues/472
- script: go get -v github.com/golangci/golangci-lint/cmd/golangci-lint
displayName: Install golangci-lint
- bash: |
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
displayName: Print Go version and environment
- script: |
go get -v -t -d ./...
golangci-lint run -E gofmt -E goimports -E misspell
go test -race ./...
workingDirectory: '$(modulePath)'
displayName: Run tests
+119 -85
View File
@@ -20,7 +20,7 @@
// 2. Call LoadCaddyfile() to get the Caddyfile.
// Pass in the name of the server type (like "http").
// Make sure the server type's package is imported
// (import _ "github.com/mholt/caddy/caddyhttp").
// (import _ "github.com/caddyserver/caddy/caddyhttp").
// 3. Call caddy.Start() to start Caddy. You get back
// an Instance, on which you can call Restart() to
// restart it or Stop() to stop it.
@@ -43,7 +43,8 @@ import (
"sync"
"time"
"github.com/mholt/caddy/caddyfile"
"github.com/caddyserver/caddy/caddyfile"
"github.com/caddyserver/caddy/telemetry"
)
// Configurable application parameters
@@ -107,11 +108,12 @@ type Instance struct {
servers []ServerListener
// these callbacks execute when certain events occur
onFirstStartup []func() error // starting, not as part of a restart
onStartup []func() error // starting, even as part of a restart
onRestart []func() error // before restart commences
onShutdown []func() error // stopping, even as part of a restart
onFinalShutdown []func() error // stopping, not as part of a restart
OnFirstStartup []func() error // starting, not as part of a restart
OnStartup []func() error // starting, even as part of a restart
OnRestart []func() error // before restart commences
OnRestartFailed []func() error // if restart failed
OnShutdown []func() error // stopping, even as part of a restart
OnFinalShutdown []func() error // stopping, not as part of a restart
// storing values on an instance is preferable to
// global state because these will get garbage-
@@ -122,6 +124,7 @@ type Instance struct {
StorageMu sync.RWMutex
}
// Instances returns the list of instances.
func Instances() []*Instance {
return instances
}
@@ -160,13 +163,13 @@ func (i *Instance) Stop() error {
// the rest. All the non-nil errors will be returned.
func (i *Instance) ShutdownCallbacks() []error {
var errs []error
for _, shutdownFunc := range i.onShutdown {
for _, shutdownFunc := range i.OnShutdown {
err := shutdownFunc()
if err != nil {
errs = append(errs, err)
}
}
for _, finalShutdownFunc := range i.onFinalShutdown {
for _, finalShutdownFunc := range i.OnFinalShutdown {
err := finalShutdownFunc()
if err != nil {
errs = append(errs, err)
@@ -184,9 +187,28 @@ func (i *Instance) Restart(newCaddyfile Input) (*Instance, error) {
i.wg.Add(1)
defer i.wg.Done()
var err error
// if something went wrong on restart then run onRestartFailed callbacks
defer func() {
r := recover()
if err != nil || r != nil {
for _, fn := range i.OnRestartFailed {
if err := fn(); err != nil {
log.Printf("[ERROR] Restart failed callback returned error: %v", err)
}
}
if err != nil {
log.Printf("[ERROR] Restart failed: %v", err)
}
if r != nil {
log.Printf("[PANIC] Restart: %v", r)
}
}
}()
// run restart callbacks
for _, fn := range i.onRestart {
err := fn()
for _, fn := range i.OnRestart {
err = fn()
if err != nil {
return i, err
}
@@ -222,22 +244,22 @@ func (i *Instance) Restart(newCaddyfile Input) (*Instance, error) {
newInst := &Instance{serverType: newCaddyfile.ServerType(), wg: i.wg, Storage: make(map[interface{}]interface{})}
// attempt to start new instance
err := startWithListenerFds(newCaddyfile, newInst, restartFds)
err = startWithListenerFds(newCaddyfile, newInst, restartFds)
if err != nil {
return i, err
return i, fmt.Errorf("starting with listener file descriptors: %v", err)
}
// success! stop the old instance
for _, shutdownFunc := range i.onShutdown {
err = i.Stop()
if err != nil {
return i, err
}
for _, shutdownFunc := range i.OnShutdown {
err = shutdownFunc()
if err != nil {
return i, err
}
}
err = i.Stop()
if err != nil {
return i, err
}
// Execute instantiation events
EmitEvent(InstanceStartupEvent, newInst)
@@ -254,42 +276,6 @@ func (i *Instance) SaveServer(s Server, ln net.Listener) {
i.servers = append(i.servers, ServerListener{server: s, listener: ln})
}
// HasListenerWithAddress returns whether this package is
// tracking a server using a listener with the address
// addr.
func HasListenerWithAddress(addr string) bool {
instancesMu.Lock()
defer instancesMu.Unlock()
for _, inst := range instances {
for _, sln := range inst.servers {
if listenerAddrEqual(sln.listener, addr) {
return true
}
}
}
return false
}
// listenerAddrEqual compares a listener's address with
// addr. Extra care is taken to match addresses with an
// empty hostname portion, as listeners tend to report
// [::]:80, for example, when the matching address that
// created the listener might be simply :80.
func listenerAddrEqual(ln net.Listener, addr string) bool {
lnAddr := ln.Addr().String()
hostname, port, err := net.SplitHostPort(addr)
if err != nil {
return lnAddr == addr
}
if lnAddr == net.JoinHostPort("::", port) {
return true
}
if lnAddr == net.JoinHostPort("0.0.0.0", port) {
return true
}
return hostname != "" && lnAddr == addr
}
// TCPServer is a type that can listen and serve connections.
// A TCPServer must associate with exactly zero or one net.Listeners.
type TCPServer interface {
@@ -368,6 +354,11 @@ type GracefulServer interface {
// address; you must store the address the
// server is to serve on some other way.
Address() string
// WrapListener wraps a listener with the
// listener middlewares configured for this
// server, if any.
WrapListener(net.Listener) net.Listener
}
// Listener is a net.Listener with an underlying file descriptor.
@@ -487,6 +478,10 @@ func Start(cdyfile Input) (*Instance, error) {
if pidErr := writePidFile(); pidErr != nil {
log.Printf("[ERROR] Could not write pidfile: %v", pidErr)
}
// Execute instantiation events
EmitEvent(InstanceStartupEvent, inst)
return inst, nil
}
@@ -531,14 +526,14 @@ func startWithListenerFds(cdyfile Input, inst *Instance, restartFds map[string]r
// run startup callbacks
if !IsUpgrade() && restartFds == nil {
// first startup means not a restart or upgrade
for _, firstStartupFunc := range inst.onFirstStartup {
for _, firstStartupFunc := range inst.OnFirstStartup {
err = firstStartupFunc()
if err != nil {
return err
}
}
}
for _, startupFunc := range inst.onStartup {
for _, startupFunc := range inst.OnStartup {
err = startupFunc()
if err != nil {
return err
@@ -605,6 +600,12 @@ func ValidateAndExecuteDirectives(cdyfile Input, inst *Instance, justValidate bo
return err
}
for _, sb := range sblocks {
for dir := range sb.Tokens {
telemetry.AppendUnique("directives", dir)
}
}
inst.context = stype.NewContext(inst)
if inst.context == nil {
return fmt.Errorf("server type %s produced a nil Context", stypeName)
@@ -615,6 +616,8 @@ func ValidateAndExecuteDirectives(cdyfile Input, inst *Instance, justValidate bo
return fmt.Errorf("error inspecting server blocks: %v", err)
}
telemetry.Set("num_server_blocks", len(sblocks))
return executeDirectives(inst, cdyfile.Path(), stype.Directives(), sblocks, justValidate)
}
@@ -688,6 +691,11 @@ func executeDirectives(inst *Instance, filename string,
func startServers(serverList []Server, inst *Instance, restartFds map[string]restartTriple) error {
errChan := make(chan error, len(serverList))
// used for signaling to error logging goroutine to terminate
stopChan := make(chan struct{})
// used to track termination of servers
stopWg := &sync.WaitGroup{}
for _, s := range serverList {
var (
ln net.Listener
@@ -704,24 +712,25 @@ func startServers(serverList []Server, inst *Instance, restartFds map[string]res
file := os.NewFile(fdIndex, "")
ln, err = net.FileListener(file)
if err != nil {
return err
return fmt.Errorf("making listener from file: %v", err)
}
err = file.Close()
if err != nil {
return err
return fmt.Errorf("closing copy of listener file: %v", err)
}
}
if fdIndex, ok := loadedGob.ListenerFds["udp"+addr]; ok {
file := os.NewFile(fdIndex, "")
pc, err = net.FilePacketConn(file)
if err != nil {
return err
return fmt.Errorf("making packet connection from file: %v", err)
}
err = file.Close()
if err != nil {
return err
return fmt.Errorf("closing copy of packet connection file: %v", err)
}
}
ln = gs.WrapListener(ln)
}
}
@@ -734,77 +743,97 @@ func startServers(serverList []Server, inst *Instance, restartFds map[string]res
if old.listener != nil {
file, err := old.listener.File()
if err != nil {
return err
return fmt.Errorf("getting old listener file: %v", err)
}
ln, err = net.FileListener(file)
if err != nil {
return err
return fmt.Errorf("getting file listener: %v", err)
}
err = file.Close()
if err != nil {
return err
return fmt.Errorf("closing copy of listener file: %v", err)
}
}
// packetconn
if old.packet != nil {
file, err := old.packet.File()
if err != nil {
return err
return fmt.Errorf("getting old packet file: %v", err)
}
pc, err = net.FilePacketConn(file)
if err != nil {
return err
return fmt.Errorf("getting file packet connection: %v", err)
}
err = file.Close()
if err != nil {
return err
return fmt.Errorf("close copy of packet file: %v", err)
}
}
ln = gs.WrapListener(ln)
}
}
if ln == nil {
ln, err = s.Listen()
if err != nil {
return err
return fmt.Errorf("Listen: %v", err)
}
}
if pc == nil {
pc, err = s.ListenPacket()
if err != nil {
return err
return fmt.Errorf("ListenPacket: %v", err)
}
}
inst.servers = append(inst.servers, ServerListener{server: s, listener: ln, packet: pc})
}
for _, s := range inst.servers {
inst.wg.Add(2)
go func(s Server, ln net.Listener, pc net.PacketConn, inst *Instance) {
defer inst.wg.Done()
stopWg.Add(2)
func(s Server, ln net.Listener, pc net.PacketConn, inst *Instance) {
go func() {
defer func() {
inst.wg.Done()
stopWg.Done()
}()
errChan <- s.Serve(ln)
}()
go func() {
errChan <- s.Serve(ln)
defer inst.wg.Done()
defer func() {
inst.wg.Done()
stopWg.Done()
}()
errChan <- s.ServePacket(pc)
}()
errChan <- s.ServePacket(pc)
}(s, ln, pc, inst)
inst.servers = append(inst.servers, ServerListener{server: s, listener: ln, packet: pc})
}(s.server, s.listener, s.packet, inst)
}
// Log errors that may be returned from Serve() calls,
// these errors should only be occurring in the server loop.
go func() {
for err := range errChan {
if err == nil {
continue
for {
select {
case err := <-errChan:
if err != nil {
if !strings.Contains(err.Error(), "use of closed network connection") {
// this error is normal when closing the listener; see https://github.com/golang/go/issues/4373
log.Println(err)
}
}
case <-stopChan:
return
}
if strings.Contains(err.Error(), "use of closed network connection") {
// this error is normal when closing the listener
continue
}
log.Println(err)
}
}()
go func() {
stopWg.Wait()
stopChan <- struct{}{}
}()
return nil
}
@@ -854,10 +883,15 @@ func Stop() error {
for {
instancesMu.Lock()
if len(instances) == 0 {
instancesMu.Unlock()
break
}
inst := instances[0]
instancesMu.Unlock()
// Increase the instance waitgroup so that the last wait() call in
// caddymain/run.go blocks until this server instance has shut down
inst.wg.Add(1)
defer inst.wg.Done()
if err := inst.Stop(); err != nil {
log.Printf("[ERROR] Stopping %s: %v", inst.serverType, err)
}
@@ -869,7 +903,7 @@ func Stop() error {
// explicitly like a common local hostname. addr must only
// be a host or a host:port combination.
func IsLoopback(addr string) bool {
host, _, err := net.SplitHostPort(addr)
host, _, err := net.SplitHostPort(strings.ToLower(addr))
if err != nil {
host = addr // happens if the addr is just a hostname
}
-87
View File
@@ -1,87 +0,0 @@
// Copyright 2015 Light Code Labs, LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// 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.
// +build dev
// build.go automates proper versioning of caddy binaries.
// Use it like: go run build.go
// You can customize the build with the -goos, -goarch, and
// -goarm CLI options: go run build.go -goos=windows
//
// To get proper version information, this program must be
// run from the directory of this file, and the source code
// must be a working git repository, since it needs to know
// if the source is in a clean state.
//
// This program is NOT required to build Caddy from source
// since it is go-gettable. (You can run plain `go build`
// in this directory to get a binary.) However, issues filed
// without version information will likely be closed.
package main
import (
"flag"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"github.com/caddyserver/builds"
)
var goos, goarch, goarm string
func init() {
flag.StringVar(&goos, "goos", "", "GOOS for which to build")
flag.StringVar(&goarch, "goarch", "", "GOARCH for which to build")
flag.StringVar(&goarm, "goarm", "", "GOARM for which to build")
}
func main() {
flag.Parse()
gopath := os.Getenv("GOPATH")
pwd, err := os.Getwd()
if err != nil {
log.Fatal(err)
}
ldflags, err := builds.MakeLdFlags(filepath.Join(pwd, ".."))
if err != nil {
log.Fatal(err)
}
args := []string{"build", "-ldflags", ldflags}
args = append(args, "-asmflags", fmt.Sprintf("-trimpath=%s", gopath))
args = append(args, "-gcflags", fmt.Sprintf("-trimpath=%s", gopath))
cmd := exec.Command("go", args...)
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
cmd.Env = os.Environ()
for _, env := range []string{
"CGO_ENABLED=0",
"GOOS=" + goos,
"GOARCH=" + goarch,
"GOARM=" + goarm,
} {
cmd.Env = append(cmd.Env, env)
}
err = cmd.Run()
if err != nil {
log.Fatal(err)
}
}
+383 -65
View File
@@ -15,46 +15,59 @@
package caddymain
import (
"bufio"
"errors"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
"runtime"
"runtime/debug"
"strconv"
"strings"
"gopkg.in/natefinch/lumberjack.v2"
"github.com/caddyserver/caddy"
"github.com/caddyserver/caddy/caddyfile"
"github.com/caddyserver/caddy/caddytls"
"github.com/caddyserver/caddy/telemetry"
"github.com/google/uuid"
"github.com/klauspost/cpuid"
"github.com/mholt/certmagic"
lumberjack "gopkg.in/natefinch/lumberjack.v2"
"github.com/xenolf/lego/acmev2"
"github.com/mholt/caddy"
// plug in the HTTP server type
_ "github.com/mholt/caddy/caddyhttp"
"github.com/mholt/caddy/caddytls"
_ "github.com/caddyserver/caddy/caddyhttp" // plug in the HTTP server type
// This is where other plugins get plugged in (imported)
)
func init() {
caddy.TrapSignals()
setVersion()
flag.BoolVar(&caddytls.Agreed, "agree", false, "Agree to the CA's Subscriber Agreement")
flag.StringVar(&caddytls.DefaultCAUrl, "ca", "https://acme-v02.api.letsencrypt.org/directory", "URL to certificate authority's ACME server directory")
flag.BoolVar(&caddytls.DisableHTTPChallenge, "disable-http-challenge", caddytls.DisableHTTPChallenge, "Disable the ACME HTTP challenge")
flag.BoolVar(&caddytls.DisableTLSSNIChallenge, "disable-tls-sni-challenge", caddytls.DisableTLSSNIChallenge, "Disable the ACME TLS-SNI challenge")
flag.BoolVar(&certmagic.Default.Agreed, "agree", false, "Agree to the CA's Subscriber Agreement")
flag.StringVar(&certmagic.Default.CA, "ca", certmagic.Default.CA, "URL to certificate authority's ACME server directory")
flag.StringVar(&certmagic.Default.DefaultServerName, "default-sni", certmagic.Default.DefaultServerName, "If a ClientHello ServerName is empty, use this ServerName to choose a TLS certificate")
flag.BoolVar(&certmagic.Default.DisableHTTPChallenge, "disable-http-challenge", certmagic.Default.DisableHTTPChallenge, "Disable the ACME HTTP challenge")
flag.BoolVar(&certmagic.Default.DisableTLSALPNChallenge, "disable-tls-alpn-challenge", certmagic.Default.DisableTLSALPNChallenge, "Disable the ACME TLS-ALPN challenge")
flag.StringVar(&disabledMetrics, "disabled-metrics", "", "Comma-separated list of telemetry metrics to disable")
flag.StringVar(&conf, "conf", "", "Caddyfile to load (default \""+caddy.DefaultConfigFile+"\")")
flag.StringVar(&cpu, "cpu", "100%", "CPU cap")
flag.BoolVar(&printEnv, "env", false, "Enable to print environment variables")
flag.StringVar(&envFile, "envfile", "", "Path to file with environment variables to load in KEY=VALUE format")
flag.BoolVar(&fromJSON, "json-to-caddyfile", false, "From JSON stdin to Caddyfile stdout")
flag.BoolVar(&plugins, "plugins", false, "List installed plugins")
flag.StringVar(&caddytls.DefaultEmail, "email", "", "Default ACME CA account email address")
flag.DurationVar(&acme.HTTPClient.Timeout, "catimeout", acme.HTTPClient.Timeout, "Default ACME CA HTTP timeout")
flag.StringVar(&certmagic.Default.Email, "email", "", "Default ACME CA account email address")
flag.DurationVar(&certmagic.HTTPTimeout, "catimeout", certmagic.HTTPTimeout, "Default ACME CA HTTP timeout")
flag.StringVar(&logfile, "log", "", "Process log file")
flag.BoolVar(&logTimestamps, "log-timestamps", true, "Enable timestamps for the process log")
flag.IntVar(&logRollMB, "log-roll-mb", 100, "Roll process log when it reaches this many megabytes (0 to disable rolling)")
flag.BoolVar(&logRollCompress, "log-roll-compress", true, "Gzip-compress rolled process log files")
flag.StringVar(&caddy.PidFile, "pidfile", "", "Path to write pid file")
flag.BoolVar(&caddy.Quiet, "quiet", false, "Quiet mode (no initialization output)")
flag.StringVar(&revoke, "revoke", "", "Hostname for which to revoke the certificate")
flag.StringVar(&serverType, "type", "http", "Type of server to run")
flag.BoolVar(&toJSON, "caddyfile-to-json", false, "From Caddyfile stdin to JSON stdout")
flag.BoolVar(&version, "version", false, "Show version")
flag.BoolVar(&validate, "validate", false, "Parse the Caddyfile but do not start the server")
@@ -66,9 +79,18 @@ func init() {
func Run() {
flag.Parse()
module := getBuildModule()
cleanModVersion := strings.TrimPrefix(module.Version, "v")
caddy.AppName = appName
caddy.AppVersion = appVersion
acme.UserAgent = appName + "/" + appVersion
caddy.AppVersion = module.Version
caddy.OnProcessExit = append(caddy.OnProcessExit, certmagic.CleanUpOwnLocks)
certmagic.UserAgent = appName + "/" + cleanModVersion
if !logTimestamps {
// Disable timestamps for logging
log.SetFlags(0)
}
// Set up process log before anything bad happens
switch logfile {
@@ -79,12 +101,47 @@ func Run() {
case "":
log.SetOutput(ioutil.Discard)
default:
log.SetOutput(&lumberjack.Logger{
Filename: logfile,
MaxSize: 100,
MaxAge: 14,
MaxBackups: 10,
})
if logRollMB > 0 {
log.SetOutput(&lumberjack.Logger{
Filename: logfile,
MaxSize: logRollMB,
MaxAge: 14,
MaxBackups: 10,
Compress: logRollCompress,
})
} else {
err := os.MkdirAll(filepath.Dir(logfile), 0755)
if err != nil {
mustLogFatalf("%v", err)
}
f, err := os.OpenFile(logfile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
mustLogFatalf("%v", err)
}
// don't close file; log should be writeable for duration of process
log.SetOutput(f)
}
}
// load all additional envs as soon as possible
if err := LoadEnvFromFile(envFile); err != nil {
mustLogFatalf("%v", err)
}
if printEnv {
for _, v := range os.Environ() {
fmt.Println(v)
}
}
// initialize telemetry client
if EnableTelemetry {
err := initTelemetry()
if err != nil {
mustLogFatalf("[ERROR] Initializing telemetry: %v", err)
}
} else if disabledMetrics != "" {
mustLogFatalf("[ERROR] Cannot disable specific metrics because telemetry is disabled")
}
// Check for one-time actions
@@ -97,9 +154,11 @@ func Run() {
os.Exit(0)
}
if version {
fmt.Printf("%s %s (unofficial)\n", appName, appVersion)
if devBuild && gitShortStat != "" {
fmt.Printf("%s\n%s\n", gitShortStat, gitFilesModified)
if module.Sum != "" {
// a build with a known version will also have a checksum
fmt.Printf("Caddy %s (%s)\n", module.Version, module.Sum)
} else {
fmt.Println(module.Version)
}
os.Exit(0)
}
@@ -108,6 +167,9 @@ func Run() {
os.Exit(0)
}
// Check if we just need to do a Caddyfile Convert and exit
checkJSONCaddyfile()
// Set CPU cap
err := setCPU(cpu)
if err != nil {
@@ -134,14 +196,34 @@ func Run() {
os.Exit(0)
}
// Log Caddy version before start
log.Printf("[INFO] Caddy version: %s", module.Version)
// Start your engines
instance, err := caddy.Start(caddyfileinput)
if err != nil {
mustLogFatalf("%v", err)
}
// Execute instantiation events
caddy.EmitEvent(caddy.InstanceStartupEvent, instance)
// Begin telemetry (these are no-ops if telemetry disabled)
telemetry.Set("caddy_version", module.Version)
telemetry.Set("num_listeners", len(instance.Servers()))
telemetry.Set("server_type", serverType)
telemetry.Set("os", runtime.GOOS)
telemetry.Set("arch", runtime.GOARCH)
telemetry.Set("cpu", struct {
BrandName string `json:"brand_name,omitempty"`
NumLogical int `json:"num_logical,omitempty"`
AESNI bool `json:"aes_ni,omitempty"`
}{
BrandName: cpuid.CPU.BrandName,
NumLogical: runtime.NumCPU(),
AESNI: cpuid.CPU.AesNi(),
})
if containerized := detectContainer(); containerized {
telemetry.Set("container", containerized)
}
telemetry.StartEmitting()
// Twiddle your thumbs
instance.Wait()
@@ -205,25 +287,56 @@ func defaultLoader(serverType string) (caddy.Input, error) {
}, nil
}
// setVersion figures out the version information
// based on variables set by -ldflags.
func setVersion() {
// A development build is one that's not at a tag or has uncommitted changes
devBuild = gitTag == "" || gitShortStat != ""
if buildDate != "" {
buildDate = " " + buildDate
}
// Only set the appVersion if -ldflags was used
if gitNearestTag != "" || gitTag != "" {
if devBuild && gitNearestTag != "" {
appVersion = fmt.Sprintf("%s (+%s%s)",
strings.TrimPrefix(gitNearestTag, "v"), gitCommit, buildDate)
} else if gitTag != "" {
appVersion = strings.TrimPrefix(gitTag, "v")
// getBuildModule returns the build info of Caddy
// from debug.BuildInfo (requires Go modules). If
// no version information is available, a non-nil
// value will still be returned, but with an
// unknown version.
func getBuildModule() *debug.Module {
bi, ok := debug.ReadBuildInfo()
if ok {
// The recommended way to build Caddy involves
// creating a separate main module, which
// preserves caddy a read-only dependency
// TODO: track related Go issue: https://github.com/golang/go/issues/29228
for _, mod := range bi.Deps {
if mod.Path == "github.com/caddyserver/caddy" {
return mod
}
}
}
return &debug.Module{Version: "unknown"}
}
func checkJSONCaddyfile() {
if fromJSON {
jsonBytes, err := ioutil.ReadAll(os.Stdin)
if err != nil {
fmt.Fprintf(os.Stderr, "Read stdin failed: %v", err)
os.Exit(1)
}
caddyfileBytes, err := caddyfile.FromJSON(jsonBytes)
if err != nil {
fmt.Fprintf(os.Stderr, "Converting from JSON failed: %v", err)
os.Exit(2)
}
fmt.Println(string(caddyfileBytes))
os.Exit(0)
}
if toJSON {
caddyfileBytes, err := ioutil.ReadAll(os.Stdin)
if err != nil {
fmt.Fprintf(os.Stderr, "Read stdin failed: %v", err)
os.Exit(1)
}
jsonBytes, err := caddyfile.ToJSON(caddyfileBytes)
if err != nil {
fmt.Fprintf(os.Stderr, "Converting to JSON failed: %v", err)
os.Exit(2)
}
fmt.Println(string(jsonBytes))
os.Exit(0)
}
}
// setCPU parses string cpu and sets GOMAXPROCS
@@ -266,29 +379,234 @@ func setCPU(cpu string) error {
return nil
}
// detectContainer attempts to determine whether the process is
// being run inside a container. References:
// https://tuhrig.de/how-to-know-you-are-inside-a-docker-container/
// https://stackoverflow.com/a/20012536/1048862
// https://gist.github.com/anantkamath/623ce7f5432680749e087cf8cfba9b69
func detectContainer() bool {
if runtime.GOOS != "linux" {
return false
}
file, err := os.Open("/proc/1/cgroup")
if err != nil {
return false
}
defer file.Close()
i := 0
scanner := bufio.NewScanner(file)
for scanner.Scan() {
i++
if i > 1000 {
return false
}
line := scanner.Text()
parts := strings.SplitN(line, ":", 3)
if len(parts) < 3 {
continue
}
if strings.Contains(parts[2], "docker") ||
strings.Contains(parts[2], "lxc") ||
strings.Contains(parts[2], "moby") {
return true
}
}
return false
}
// initTelemetry initializes the telemetry engine.
func initTelemetry() error {
uuidFilename := filepath.Join(caddy.AssetsPath(), "uuid")
if customUUIDFile := os.Getenv("CADDY_UUID_FILE"); customUUIDFile != "" {
uuidFilename = customUUIDFile
}
newUUID := func() uuid.UUID {
id := uuid.New()
err := os.MkdirAll(caddy.AssetsPath(), 0700)
if err != nil {
log.Printf("[ERROR] Persisting instance UUID: %v", err)
return id
}
err = ioutil.WriteFile(uuidFilename, []byte(id.String()), 0600) // human-readable as a string
if err != nil {
log.Printf("[ERROR] Persisting instance UUID: %v", err)
}
return id
}
var id uuid.UUID
// load UUID from storage, or create one if we don't have one
if uuidFile, err := os.Open(uuidFilename); os.IsNotExist(err) {
// no UUID exists yet; create a new one and persist it
id = newUUID()
} else if err != nil {
log.Printf("[ERROR] Loading persistent UUID: %v", err)
id = newUUID()
} else {
defer uuidFile.Close()
uuidBytes, err := ioutil.ReadAll(uuidFile)
if err != nil {
log.Printf("[ERROR] Reading persistent UUID: %v", err)
id = newUUID()
} else {
id, err = uuid.ParseBytes(uuidBytes)
if err != nil {
log.Printf("[ERROR] Parsing UUID: %v", err)
id = newUUID()
}
}
}
// parse and check the list of disabled metrics
var disabledMetricsSlice []string
if len(disabledMetrics) > 0 {
if len(disabledMetrics) > 1024 {
// mitigate disk space exhaustion at the collection endpoint
return fmt.Errorf("too many metrics to disable")
}
disabledMetricsSlice = splitTrim(disabledMetrics, ",")
for _, metric := range disabledMetricsSlice {
if metric == "instance_id" || metric == "timestamp" || metric == "disabled_metrics" {
return fmt.Errorf("instance_id, timestamp, and disabled_metrics cannot be disabled")
}
}
}
// initialize telemetry
telemetry.Init(id, disabledMetricsSlice)
// if any metrics were disabled, report which ones (so we know how representative the data is)
if len(disabledMetricsSlice) > 0 {
telemetry.Set("disabled_metrics", disabledMetricsSlice)
log.Printf("[NOTICE] The following telemetry metrics are disabled: %s", disabledMetrics)
}
return nil
}
// Split string s into all substrings separated by sep and returns a slice of
// the substrings between those separators.
//
// If s does not contain sep and sep is not empty, Split returns a
// slice of length 1 whose only element is s.
//
// If sep is empty, Split splits after each UTF-8 sequence. If both s
// and sep are empty, Split returns an empty slice.
//
// Each item that in result is trim space and not empty string
func splitTrim(s string, sep string) []string {
splitItems := strings.Split(s, sep)
trimItems := make([]string, 0, len(splitItems))
for _, item := range splitItems {
if item = strings.TrimSpace(item); item != "" {
trimItems = append(trimItems, item)
}
}
return trimItems
}
// LoadEnvFromFile loads additional envs if file provided and exists
// Envs in file should be in KEY=VALUE format
func LoadEnvFromFile(envFile string) error {
if envFile == "" {
return nil
}
file, err := os.Open(envFile)
if err != nil {
return err
}
defer file.Close()
envMap, err := ParseEnvFile(file)
if err != nil {
return err
}
for k, v := range envMap {
if err := os.Setenv(k, v); err != nil {
return err
}
}
return nil
}
// ParseEnvFile implements parse logic for environment files
func ParseEnvFile(envInput io.Reader) (map[string]string, error) {
envMap := make(map[string]string)
scanner := bufio.NewScanner(envInput)
var line string
lineNumber := 0
for scanner.Scan() {
line = strings.TrimSpace(scanner.Text())
lineNumber++
// skip lines starting with comment
if strings.HasPrefix(line, "#") {
continue
}
// skip empty line
if len(line) == 0 {
continue
}
fields := strings.SplitN(line, "=", 2)
if len(fields) != 2 {
return nil, fmt.Errorf("Can't parse line %d; line should be in KEY=VALUE format", lineNumber)
}
if strings.Contains(fields[0], " ") {
return nil, fmt.Errorf("Can't parse line %d; KEY contains whitespace", lineNumber)
}
key := fields[0]
val := fields[1]
if key == "" {
return nil, fmt.Errorf("Can't parse line %d; KEY can't be empty string", lineNumber)
}
envMap[key] = val
}
if err := scanner.Err(); err != nil {
return nil, err
}
return envMap, nil
}
const appName = "Caddy"
// Flags that control program flow or startup
var (
serverType string
conf string
cpu string
logfile string
revoke string
version bool
plugins bool
validate bool
serverType string
conf string
cpu string
envFile string
fromJSON bool
logfile string
logTimestamps bool
logRollMB int
logRollCompress bool
revoke string
toJSON bool
version bool
plugins bool
printEnv bool
validate bool
disabledMetrics string
)
// Build information obtained with the help of -ldflags
var (
appVersion = "(untracked dev build)" // inferred at startup
devBuild = true // inferred at startup
buildDate string // date -u
gitTag string // git describe --exact-match HEAD 2> /dev/null
gitNearestTag string // git describe --abbrev=0 --tags HEAD
gitCommit string // git rev-parse HEAD
gitShortStat string // git diff-index --shortstat
gitFilesModified string // git diff-index --name-only HEAD
)
// EnableTelemetry defines whether telemetry is enabled in Run.
var EnableTelemetry = true
+59
View File
@@ -15,7 +15,9 @@
package caddymain
import (
"reflect"
"runtime"
"strings"
"testing"
)
@@ -57,3 +59,60 @@ func TestSetCPU(t *testing.T) {
runtime.GOMAXPROCS(currentCPU)
}
}
func TestSplitTrim(t *testing.T) {
for i, test := range []struct {
input string
output []string
sep string
}{
{"os,arch,cpu,caddy_version", []string{"os", "arch", "cpu", "caddy_version"}, ","},
{"os,arch,cpu,caddy_version,", []string{"os", "arch", "cpu", "caddy_version"}, ","},
{"os,,, arch, cpu, caddy_version,", []string{"os", "arch", "cpu", "caddy_version"}, ","},
{", , os, arch, cpu , caddy_version,, ,", []string{"os", "arch", "cpu", "caddy_version"}, ","},
{"os, ,, arch, cpu , caddy_version,, ,", []string{"os", "arch", "cpu", "caddy_version"}, ","},
} {
got := splitTrim(test.input, test.sep)
if len(got) != len(test.output) {
t.Errorf("Test %d: spliteTrim() = %v, want %v", i, got, test.output)
continue
}
for j, item := range test.output {
if item != got[j] {
t.Errorf("Test %d: spliteTrim() = %v, want %v", i, got, test.output)
break
}
}
}
}
func TestParseEnvFile(t *testing.T) {
tests := []struct {
name string
input string
want map[string]string
wantErr bool
}{
{"parsing KEY=VALUE", "PORT=4096", map[string]string{"PORT": "4096"}, false},
{"empty KEY", "=4096", nil, true},
{"one value", "test", nil, true},
{"comments skipped", "#TEST=1\nPORT=8888", map[string]string{"PORT": "8888"}, false},
{"empty line", "\nPORT=7777", map[string]string{"PORT": "7777"}, false},
{"comments with space skipped", " #TEST=1", map[string]string{}, false},
{"KEY with space", "PORT =8888", nil, true},
{"only spaces", " ", map[string]string{}, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
reader := strings.NewReader(tt.input)
got, err := ParseEnvFile(reader)
if (err != nil) != tt.wantErr {
t.Errorf("ParseEnvFile() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("ParseEnvFile() = %v, want %v", got, tt.want)
}
})
}
}
+1 -1
View File
@@ -19,7 +19,7 @@
package main
import "github.com/mholt/caddy/caddy/caddymain"
import "github.com/caddyserver/caddy/caddy/caddymain"
var run = caddymain.Run // replaced for tests
+77 -38
View File
@@ -15,9 +15,13 @@
package caddy
import (
"net"
"strconv"
"fmt"
"log"
"reflect"
"sync"
"testing"
"github.com/caddyserver/caddy/caddyfile"
)
/*
@@ -48,6 +52,77 @@ func TestCaddyStartStop(t *testing.T) {
}
*/
// CallbackTestContext implements Context interface
type CallbackTestContext struct {
// If MakeServersFail is set to true then MakeServers returns an error
MakeServersFail bool
}
func (h *CallbackTestContext) InspectServerBlocks(name string, sblock []caddyfile.ServerBlock) ([]caddyfile.ServerBlock, error) {
return sblock, nil
}
func (h *CallbackTestContext) MakeServers() ([]Server, error) {
if h.MakeServersFail {
return make([]Server, 0), fmt.Errorf("MakeServers failed")
}
return make([]Server, 0), nil
}
func TestCaddyRestartCallbacks(t *testing.T) {
for i, test := range []struct {
restartFail bool
expectedCalls []string
}{
{false, []string{"OnRestart", "OnShutdown"}},
{true, []string{"OnRestart", "OnRestartFailed"}},
} {
serverName := fmt.Sprintf("%v", i)
// RegisterServerType to make successful restart possible
RegisterServerType(serverName, ServerType{
Directives: func() []string { return []string{} },
// If MakeServersFail is true then the restart will fail due to context failure
NewContext: func(inst *Instance) Context { return &CallbackTestContext{MakeServersFail: test.restartFail} },
})
c := NewTestController(serverName, "")
c.instance = &Instance{
serverType: serverName,
wg: new(sync.WaitGroup),
}
// Register callbacks which save the calls order
calls := make([]string, 0)
c.OnRestart(func() error {
calls = append(calls, "OnRestart")
return nil
})
c.OnRestartFailed(func() error {
calls = append(calls, "OnRestartFailed")
return nil
})
c.OnShutdown(func() error {
calls = append(calls, "OnShutdown")
return nil
})
_, err := c.instance.Restart(CaddyfileInput{Contents: []byte(""), ServerTypeName: serverName})
if err != nil {
log.Printf("[ERROR] Restart failed: %v", err)
}
if !reflect.DeepEqual(calls, test.expectedCalls) {
t.Errorf("Test %d: Callbacks expected: %v, got: %v", i, test.expectedCalls, calls)
}
err = c.instance.Stop()
if err != nil {
log.Printf("[ERROR] Stop failed: %v", err)
}
c.instance.Wait()
}
}
func TestIsLoopback(t *testing.T) {
for i, test := range []struct {
input string
@@ -135,39 +210,3 @@ func TestIsInternal(t *testing.T) {
}
}
}
func TestListenerAddrEqual(t *testing.T) {
ln1, err := net.Listen("tcp", "[::]:0")
if err != nil {
t.Fatal(err)
}
defer ln1.Close()
ln1port := strconv.Itoa(ln1.Addr().(*net.TCPAddr).Port)
ln2, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer ln2.Close()
ln2port := strconv.Itoa(ln2.Addr().(*net.TCPAddr).Port)
for i, test := range []struct {
ln net.Listener
addr string
expect bool
}{
{ln1, ":" + ln2port, false},
{ln1, "0.0.0.0:" + ln2port, false},
{ln1, "0.0.0.0", false},
{ln1, ":" + ln1port, true},
{ln1, "0.0.0.0:" + ln1port, true},
{ln2, ":" + ln2port, false},
{ln2, "127.0.0.1:" + ln1port, false},
{ln2, "127.0.0.1", false},
{ln2, "127.0.0.1:" + ln2port, true},
} {
if got, want := listenerAddrEqual(test.ln, test.addr), test.expect; got != want {
t.Errorf("Test %d (%s == %s): expected %v but was %v", i, test.addr, test.ln.Addr().String(), want, got)
}
}
}
+4 -1
View File
@@ -15,6 +15,7 @@
package caddyfile
import (
"log"
"strings"
"testing"
)
@@ -158,7 +159,9 @@ func TestLexer(t *testing.T) {
func tokenize(input string) (tokens []Token) {
l := lexer{}
l.load(strings.NewReader(input))
if err := l.load(strings.NewReader(input)); err != nil {
log.Printf("[ERROR] load failed: %v", err)
}
for l.next() {
tokens = append(tokens, l.token)
}
+25 -32
View File
@@ -249,9 +249,10 @@ func (p *parser) doImport() error {
if p.definedSnippets != nil && p.definedSnippets[importPattern] != nil {
importedTokens = p.definedSnippets[importPattern]
} else {
// make path relative to Caddyfile rather than current working directory (issue #867)
// and then use glob to get list of matching filenames
absFile, err := filepath.Abs(p.Dispenser.filename)
// make path relative to the file of the _token_ being processed rather
// than current working directory (issue #867) and then use glob to get
// list of matching filenames
absFile, err := filepath.Abs(p.Dispenser.File())
if err != nil {
return p.Errf("Failed to get absolute path of file: %s: %v", p.Dispenser.filename, err)
}
@@ -263,14 +264,19 @@ func (p *parser) doImport() error {
} else {
globPattern = importPattern
}
if strings.Count(globPattern, "*") > 1 || strings.Count(globPattern, "?") > 1 ||
(strings.Contains(globPattern, "[") && strings.Contains(globPattern, "]")) {
// See issue #2096 - a pattern with many glob expansions can hang for too long
return p.Errf("Glob pattern may only contain one wildcard (*), but has others: %s", globPattern)
}
matches, err = filepath.Glob(globPattern)
if err != nil {
return p.Errf("Failed to use import pattern %s: %v", importPattern, err)
}
if len(matches) == 0 {
if strings.Contains(globPattern, "*") {
log.Printf("[WARNING] No files matching import pattern: %s", importPattern)
if strings.ContainsAny(globPattern, "*?[]") {
log.Printf("[WARNING] No files matching import glob pattern: %s", importPattern)
} else {
return p.Errf("File to import not found: %s", importPattern)
}
@@ -283,30 +289,6 @@ func (p *parser) doImport() error {
if err != nil {
return err
}
var importLine int
for i, token := range newTokens {
if token.Text == "import" {
importLine = token.Line
continue
}
if token.Line == importLine {
var abs string
if filepath.IsAbs(token.Text) {
abs = token.Text
} else if !filepath.IsAbs(importFile) {
abs = filepath.Join(filepath.Dir(absFile), token.Text)
} else {
abs = filepath.Join(filepath.Dir(importFile), token.Text)
}
newTokens[i] = Token{
Text: abs,
Line: token.Line,
File: token.File,
}
}
}
importedTokens = append(importedTokens, newTokens...)
}
}
@@ -359,7 +341,7 @@ func (p *parser) doSingleImport(importFile string) ([]Token, error) {
// are loaded into the current server block for later use
// by directive setup functions.
func (p *parser) directive() error {
dir := p.Val()
dir := replaceEnvVars(p.Val())
nesting := 0
// TODO: More helpful error message ("did you mean..." or "maybe you need to install its server type")
@@ -380,6 +362,12 @@ func (p *parser) directive() error {
nesting--
} else if p.Val() == "}" && nesting == 0 {
return p.Err("Unexpected '}' because no matching opening brace")
} else if p.Val() == "import" && p.isNewLine() {
if err := p.doImport(); err != nil {
return err
}
p.cursor-- // cursor is advanced when we continue, so roll back one more
continue
}
p.tokens[p.cursor].Text = replaceEnvVars(p.tokens[p.cursor].Text)
p.block.Tokens[dir] = append(p.block.Tokens[dir], p.tokens[p.cursor])
@@ -439,8 +427,13 @@ func replaceEnvVars(s string) string {
func replaceEnvReferences(s, refStart, refEnd string) string {
index := strings.Index(s, refStart)
for index != -1 {
endIndex := strings.Index(s, refEnd)
if endIndex != -1 {
endIndex := strings.Index(s[index:], refEnd)
if endIndex == -1 {
break
}
endIndex += index
if endIndex > index+len(refStart) {
ref := s[index : endIndex+len(refEnd)]
s = strings.Replace(s, ref, os.Getenv(ref[len(refStart):len(ref)-len(refEnd)]), -1)
} else {
+177 -9
View File
@@ -228,6 +228,17 @@ func TestParseOneAndImport(t *testing.T) {
{`""`, false, []string{}, map[string]int{}},
{``, false, []string{}, map[string]int{}},
// test cases found by fuzzing!
{`import }{$"`, true, []string{}, map[string]int{}},
{`import /*/*.txt`, true, []string{}, map[string]int{}},
{`import /???/?*?o`, true, []string{}, map[string]int{}},
{`import /??`, true, []string{}, map[string]int{}},
{`import /[a-z]`, true, []string{}, map[string]int{}},
{`import {$}`, true, []string{}, map[string]int{}},
{`import {%}`, true, []string{}, map[string]int{}},
{`import {$$}`, true, []string{}, map[string]int{}},
{`import {%%}`, true, []string{}, map[string]int{}},
} {
result, err := testParseOne(test.input)
@@ -360,6 +371,68 @@ func TestRecursiveImport(t *testing.T) {
}
}
func TestDirectiveImport(t *testing.T) {
testParseOne := func(input string) (ServerBlock, error) {
p := testParser(input)
p.Next() // parseOne doesn't call Next() to start, so we must
err := p.parseOne()
return p.block, err
}
isExpected := func(got ServerBlock) bool {
if len(got.Keys) != 1 || got.Keys[0] != "localhost" {
t.Errorf("got keys unexpected: expect localhost, got %v", got.Keys)
return false
}
if len(got.Tokens) != 2 {
t.Errorf("got wrong number of tokens: expect 2, got %d", len(got.Tokens))
return false
}
if len(got.Tokens["dir1"]) != 1 || len(got.Tokens["proxy"]) != 8 {
t.Errorf("got unexpect tokens: %v", got.Tokens)
return false
}
return true
}
directiveFile, err := filepath.Abs("testdata/directive_import_test")
if err != nil {
t.Fatal(err)
}
err = ioutil.WriteFile(directiveFile, []byte(`prop1 1
prop2 2`), 0644)
if err != nil {
t.Fatal(err)
}
defer os.Remove(directiveFile)
// import from existing file
result, err := testParseOne(`localhost
dir1
proxy {
import testdata/directive_import_test
transparent
}`)
if err != nil {
t.Fatal(err)
}
if !isExpected(result) {
t.Error("directive import failed")
}
// import from nonexistent file
_, err = testParseOne(`localhost
dir1
proxy {
import testdata/nonexistent_file
transparent
}`)
if err == nil {
t.Fatal("expected error when importing a nonexistent file")
}
}
func TestParseAll(t *testing.T) {
for i, test := range []struct {
input string
@@ -441,6 +514,7 @@ func TestEnvironmentReplacement(t *testing.T) {
os.Setenv("PORT", "8080")
os.Setenv("ADDRESS", "servername.com")
os.Setenv("FOOBAR", "foobar")
os.Setenv("PARTIAL_DIR", "r1")
// basic test; unix-style env vars
p := testParser(`{$ADDRESS}`)
@@ -449,6 +523,13 @@ func TestEnvironmentReplacement(t *testing.T) {
t.Errorf("Expected key to be '%s' but was '%s'", expected, actual)
}
// basic test; unix-style env vars
p = testParser(`di{$PARTIAL_DIR}`)
blocks, _ = p.parseAll()
if actual, expected := blocks[0].Keys[0], "dir1"; expected != actual {
t.Errorf("Expected key to be '%s' but was '%s'", expected, actual)
}
// multiple vars per token
p = testParser(`{$ADDRESS}:{$PORT}`)
blocks, _ = p.parseAll()
@@ -507,6 +588,13 @@ func TestEnvironmentReplacement(t *testing.T) {
if actual, expected := blocks[0].Tokens["dir1"][1].Text, "Test foobar test"; expected != actual {
t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
}
// after end token
p = testParser(":1234\nanswer \"{{ .Name }} {$FOOBAR}\"")
blocks, _ = p.parseAll()
if actual, expected := blocks[0].Tokens["answer"][1].Text, "{{ .Name }} foobar"; expected != actual {
t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
}
}
func testParser(input string) parser {
@@ -516,15 +604,15 @@ func testParser(input string) parser {
}
func TestSnippets(t *testing.T) {
p := testParser(`(common) {
gzip foo
errors stderr
}
http://example.com {
import common
}
`)
p := testParser(`
(common) {
gzip foo
errors stderr
}
http://example.com {
import common
}
`)
blocks, err := p.parseAll()
if err != nil {
t.Fatal(err)
@@ -550,3 +638,83 @@ func TestSnippets(t *testing.T) {
}
}
func writeStringToTempFileOrDie(t *testing.T, str string) (pathToFile string) {
file, err := ioutil.TempFile("", t.Name())
if err != nil {
panic(err) // get a stack trace so we know where this was called from.
}
if _, err := file.WriteString(str); err != nil {
panic(err)
}
if err := file.Close(); err != nil {
panic(err)
}
return file.Name()
}
func TestImportedFilesIgnoreNonDirectiveImportTokens(t *testing.T) {
fileName := writeStringToTempFileOrDie(t, `
http://example.com {
# This isn't an import directive, it's just an arg with value 'import'
basicauth / import password
}
`)
// Parse the root file that imports the other one.
p := testParser(`import ` + fileName)
blocks, err := p.parseAll()
if err != nil {
t.Fatal(err)
}
for _, b := range blocks {
t.Log(b.Keys)
t.Log(b.Tokens)
}
auth := blocks[0].Tokens["basicauth"]
line := auth[0].Text + " " + auth[1].Text + " " + auth[2].Text + " " + auth[3].Text
if line != "basicauth / import password" {
// Previously, it would be changed to:
// basicauth / import /path/to/test/dir/password
// referencing a file that (probably) doesn't exist and changing the
// password!
t.Errorf("Expected basicauth tokens to be 'basicauth / import password' but got %#q", line)
}
}
func TestSnippetAcrossMultipleFiles(t *testing.T) {
// Make the derived Caddyfile that expects (common) to be defined.
fileName := writeStringToTempFileOrDie(t, `
http://example.com {
import common
}
`)
// Parse the root file that defines (common) and then imports the other one.
p := testParser(`
(common) {
gzip foo
}
import ` + fileName + `
`)
blocks, err := p.parseAll()
if err != nil {
t.Fatal(err)
}
for _, b := range blocks {
t.Log(b.Keys)
t.Log(b.Tokens)
}
if len(blocks) != 1 {
t.Fatalf("Expect exactly one server block. Got %d.", len(blocks))
}
if actual, expected := blocks[0].Keys[0], "http://example.com"; expected != actual {
t.Errorf("Expected server name to be '%s' but was '%s'", expected, actual)
}
if len(blocks[0].Tokens) != 1 {
t.Fatalf("Server block should have tokens from import")
}
if actual, expected := blocks[0].Tokens["gzip"][0].Text, "gzip"; expected != actual {
t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
}
}
+25 -5
View File
@@ -26,14 +26,15 @@ import (
"crypto/subtle"
"fmt"
"io"
"log"
"net/http"
"os"
"path/filepath"
"strings"
"sync"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
"github.com/jimstudt/http-authentication/basic"
"github.com/mholt/caddy/caddyhttp/httpserver"
)
// BasicAuth is middleware to protect resources with a username and password.
@@ -51,6 +52,15 @@ type BasicAuth struct {
func (a BasicAuth) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
var protected, isAuthenticated bool
var realm string
var username string
var password string
var ok bool
// do not check for basic auth on OPTIONS call
if r.Method == http.MethodOptions {
// Pass-through when no paths match
return a.Next.ServeHTTP(w, r)
}
for _, rule := range a.Rules {
for _, res := range rule.Resources {
@@ -63,7 +73,7 @@ func (a BasicAuth) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error
realm = rule.Realm
// parse auth header
username, password, ok := r.BasicAuth()
username, password, ok = r.BasicAuth()
// check credentials
if !ok ||
@@ -94,7 +104,13 @@ func (a BasicAuth) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error
realm = "Restricted"
}
w.Header().Set("WWW-Authenticate", "Basic realm=\""+realm+"\"")
return http.StatusUnauthorized, nil
// Get a replacer so we can provide basic info for the authentication error.
repl := httpserver.NewReplacer(r, nil, "-")
repl.Set("user", username)
errstr := repl.Replace("BasicAuth: user \"{user}\" was not found or password was incorrect. {remote} {host} {uri} {proto}")
err := fmt.Errorf("%s", errstr)
return http.StatusUnauthorized, err
}
// Pass-through when no paths match
@@ -178,11 +194,15 @@ func PlainMatcher(passw string) PasswordMatcher {
// compare hashes of equal length instead of actual password
// to avoid leaking password length
passwHash := sha1.New()
passwHash.Write([]byte(passw))
if _, err := passwHash.Write([]byte(passw)); err != nil {
log.Printf("[ERROR] unable to write password hash: %v", err)
}
passwSum := passwHash.Sum(nil)
return func(pw string) bool {
pwHash := sha1.New()
pwHash.Write([]byte(pw))
if _, err := pwHash.Write([]byte(pw)); err != nil {
log.Printf("[ERROR] unable to write password hash: %v", err)
}
pwSum := pwHash.Sum(nil)
return subtle.ConstantTimeCompare([]byte(pwSum), []byte(passwSum)) == 1
}
+55 -21
View File
@@ -22,9 +22,10 @@ import (
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
)
func TestBasicAuth(t *testing.T) {
@@ -60,17 +61,18 @@ func TestBasicAuth(t *testing.T) {
result int
user string
password string
haserror bool
}
tests := []testType{
{"/testing", http.StatusOK, "okuser", "okpass"},
{"/testing", http.StatusUnauthorized, "baduser", "okpass"},
{"/testing", http.StatusUnauthorized, "okuser", "badpass"},
{"/testing", http.StatusUnauthorized, "OKuser", "okpass"},
{"/testing", http.StatusUnauthorized, "OKuser", "badPASS"},
{"/testing", http.StatusUnauthorized, "", "okpass"},
{"/testing", http.StatusUnauthorized, "okuser", ""},
{"/testing", http.StatusUnauthorized, "", ""},
{"/testing", http.StatusOK, "okuser", "okpass", false},
{"/testing", http.StatusUnauthorized, "baduser", "okpass", true},
{"/testing", http.StatusUnauthorized, "okuser", "badpass", true},
{"/testing", http.StatusUnauthorized, "OKuser", "okpass", true},
{"/testing", http.StatusUnauthorized, "OKuser", "badPASS", true},
{"/testing", http.StatusUnauthorized, "", "okpass", true},
{"/testing", http.StatusUnauthorized, "okuser", "", true},
{"/testing", http.StatusUnauthorized, "", "", true},
}
var test testType
@@ -89,7 +91,9 @@ func TestBasicAuth(t *testing.T) {
rec := httptest.NewRecorder()
result, err := rw.ServeHTTP(rec, req)
if err != nil {
t.Fatalf("Test %d: Could not ServeHTTP: %v", i, err)
if !test.haserror || !strings.HasPrefix(err.Error(), "BasicAuth: user") {
t.Fatalf("Test %d: Could not ServeHTTP: %v", i, err)
}
}
if result != test.result {
t.Errorf("Test %d: Expected status code %d but was %d",
@@ -106,7 +110,7 @@ func TestBasicAuth(t *testing.T) {
}
} else {
if req.Header.Get("Authorization") == "" {
// see issue #1508: https://github.com/mholt/caddy/issues/1508
// see issue #1508: https://github.com/caddyserver/caddy/issues/1508
t.Errorf("Test %d: Expected Authorization header to be retained after successful auth, but was empty", i)
}
}
@@ -124,16 +128,17 @@ func TestMultipleOverlappingRules(t *testing.T) {
}
tests := []struct {
from string
result int
cred string
from string
result int
cred string
haserror bool
}{
{"/t", http.StatusOK, "t:p1"},
{"/t/t", http.StatusOK, "t:p1"},
{"/t/t", http.StatusOK, "t1:p2"},
{"/a", http.StatusOK, "t1:p2"},
{"/t/t", http.StatusUnauthorized, "t1:p3"},
{"/t", http.StatusUnauthorized, "t1:p2"},
{"/t", http.StatusOK, "t:p1", false},
{"/t/t", http.StatusOK, "t:p1", false},
{"/t/t", http.StatusOK, "t1:p2", false},
{"/a", http.StatusOK, "t1:p2", false},
{"/t/t", http.StatusUnauthorized, "t1:p3", true},
{"/t", http.StatusUnauthorized, "t1:p2", true},
}
for i, test := range tests {
@@ -148,7 +153,9 @@ func TestMultipleOverlappingRules(t *testing.T) {
rec := httptest.NewRecorder()
result, err := rw.ServeHTTP(rec, req)
if err != nil {
t.Fatalf("Test %d: Could not ServeHTTP %v", i, err)
if !test.haserror || !strings.HasPrefix(err.Error(), "BasicAuth: user") {
t.Fatalf("Test %d: Could not ServeHTTP %v", i, err)
}
}
if result != test.result {
t.Errorf("Test %d: Expected Header '%d' but was '%d'",
@@ -194,3 +201,30 @@ md5:$apr1$l42y8rex$pOA2VJ0x/0TwaFeAF9nX61`
}
}
}
func TestOptionsMethod(t *testing.T) {
rw := BasicAuth{
Next: httpserver.HandlerFunc(contentHandler),
Rules: []Rule{
{Username: "username", Password: PlainMatcher("password"), Resources: []string{"/testing"}},
},
}
req, err := http.NewRequest(http.MethodOptions, "/testing", nil)
if err != nil {
t.Fatalf("Could not create HTTP request: %v", err)
}
// add basic auth with invalid username
// and password to make sure basic auth is ignored
req.SetBasicAuth("invaliduser", "invalidpassword")
rec := httptest.NewRecorder()
result, err := rw.ServeHTTP(rec, req)
if err != nil {
t.Fatalf("Could not ServeHTTP: %v", err)
}
if result != http.StatusOK {
t.Errorf("Expected status code %d but was %d", http.StatusOK, result)
}
}
+2 -2
View File
@@ -17,8 +17,8 @@ package basicauth
import (
"strings"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
)
func init() {
+2 -2
View File
@@ -21,8 +21,8 @@ import (
"strings"
"testing"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
)
func TestSetup(t *testing.T) {
+3 -3
View File
@@ -15,8 +15,8 @@
package bind
import (
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
)
func init() {
@@ -32,7 +32,7 @@ func setupBind(c *caddy.Controller) error {
if !c.Args(&config.ListenHost) {
return c.ArgErr()
}
config.TLS.ListenHost = config.ListenHost // necessary for ACME challenges, see issue #309
config.TLS.Manager.ListenHost = config.ListenHost // necessary for ACME challenges, see issue #309
}
return nil
}
+3 -3
View File
@@ -17,8 +17,8 @@ package bind
import (
"testing"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
)
func TestSetupBind(t *testing.T) {
@@ -32,7 +32,7 @@ func TestSetupBind(t *testing.T) {
if got, want := cfg.ListenHost, "1.2.3.4"; got != want {
t.Errorf("Expected the config's ListenHost to be %s, was %s", want, got)
}
if got, want := cfg.TLS.ListenHost, "1.2.3.4"; got != want {
if got, want := cfg.TLS.Manager.ListenHost, "1.2.3.4"; got != want {
t.Errorf("Expected the TLS config's ListenHost to be %s, was %s", want, got)
}
}
+19 -19
View File
@@ -29,9 +29,9 @@ import (
"text/template"
"time"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy/caddyhttp/staticfiles"
"github.com/dustin/go-humanize"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/mholt/caddy/caddyhttp/staticfiles"
)
const (
@@ -59,34 +59,34 @@ type Config struct {
// A Listing is the context used to fill out a template.
type Listing struct {
// The name of the directory (the last element of the path)
// The name of the directory (the last element of the path).
Name string
// The full path of the request
// The full path of the request.
Path string
// Whether the parent directory is browsable
// Whether the parent directory is browse-able.
CanGoUp bool
// The items (files and folders) in the path
// The items (files and folders) in the path.
Items []FileInfo
// The number of directories in the listing
// The number of directories in the listing.
NumDirs int
// The number of files (items that aren't directories) in the listing
// The number of files (items that aren't directories) in the listing.
NumFiles int
// Which sorting order is used
// Which sorting order is used.
Sort string
// And which order
// And which order.
Order string
// If ≠0 then Items have been limited to that many elements
// If ≠0 then Items have been limited to that many elements.
ItemsLimitedTo int
// Optional custom variables for use in browse templates
// Optional custom variables for use in browse templates.
User interface{}
httpserver.Context
@@ -186,7 +186,7 @@ func (l bySize) Less(i, j int) bool {
iSize, jSize := l.Items[i].Size, l.Items[j].Size
// Directory sizes depend on the filesystem implementation,
// which is opaque to a visitor, and should indeed does not change if the operator choses to change the fs.
// which is opaque to a visitor, and should indeed does not change if the operator chooses to change the fs.
// For a consistent user experience directories are pulled to the front…
if l.Items[i].IsDir {
iSize = directoryOffset
@@ -244,7 +244,7 @@ func (l Listing) applySort() {
func directoryListing(files []os.FileInfo, canGoUp bool, urlPath string, config *Config) (Listing, bool) {
var (
fileinfos []FileInfo
fileInfos []FileInfo
dirCount, fileCount int
hasIndexFile bool
)
@@ -272,14 +272,14 @@ func directoryListing(files []os.FileInfo, canGoUp bool, urlPath string, config
continue
}
url := url.URL{Path: "./" + name} // prepend with "./" to fix paths with ':' in the name
u := url.URL{Path: "./" + name} // prepend with "./" to fix paths with ':' in the name
fileinfos = append(fileinfos, FileInfo{
fileInfos = append(fileInfos, FileInfo{
IsDir: isDir,
IsSymlink: isSymlink(f),
Name: f.Name(),
Size: f.Size(),
URL: url.String(),
URL: u.String(),
ModTime: f.ModTime().UTC(),
Mode: f.Mode(),
})
@@ -289,7 +289,7 @@ func directoryListing(files []os.FileInfo, canGoUp bool, urlPath string, config
Name: path.Base(urlPath),
Path: urlPath,
CanGoUp: canGoUp,
Items: fileinfos,
Items: fileInfos,
NumDirs: dirCount,
NumFiles: fileCount,
}, hasIndexFile
@@ -504,7 +504,7 @@ func (b Browse) ServeListing(w http.ResponseWriter, r *http.Request, requestedFi
}
buf.WriteTo(w)
_, _ = buf.WriteTo(w)
return http.StatusOK, nil
}
+11 -11
View File
@@ -31,8 +31,8 @@ import (
"text/template"
"time"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/mholt/caddy/caddyhttp/staticfiles"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy/caddyhttp/staticfiles"
)
const testDirPrefix = "caddy_browse_test"
@@ -366,25 +366,25 @@ func TestBrowseJson(t *testing.T) {
}
actualJSONResponse := rec.Body.String()
copyOflisting := listing
copyOfListing := listing
if test.SortBy == "" {
copyOflisting.Sort = "name"
copyOfListing.Sort = "name"
} else {
copyOflisting.Sort = test.SortBy
copyOfListing.Sort = test.SortBy
}
if test.OrderBy == "" {
copyOflisting.Order = "asc"
copyOfListing.Order = "asc"
} else {
copyOflisting.Order = test.OrderBy
copyOfListing.Order = test.OrderBy
}
copyOflisting.applySort()
copyOfListing.applySort()
limit := test.Limit
if limit <= len(copyOflisting.Items) && limit > 0 {
marsh, err = json.Marshal(copyOflisting.Items[:limit])
if limit <= len(copyOfListing.Items) && limit > 0 {
marsh, err = json.Marshal(copyOfListing.Items[:limit])
} else { // if the 'limit' query is empty, or has the wrong value, list everything
marsh, err = json.Marshal(copyOflisting.Items)
marsh, err = json.Marshal(copyOfListing.Items)
}
if err != nil {
+22 -15
View File
@@ -20,9 +20,9 @@ import (
"net/http"
"text/template"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/mholt/caddy/caddyhttp/staticfiles"
"github.com/caddyserver/caddy"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy/caddyhttp/staticfiles"
)
func init() {
@@ -125,6 +125,7 @@ const defaultTemplate = `<!DOCTYPE html>
body {
font-family: sans-serif;
text-rendering: optimizespeed;
background-color: #ffffff;
}
a {
@@ -145,12 +146,12 @@ header,
th:first-child,
td:first-child {
padding-left: 5%;
width: 5%;
}
th:last-child,
td:last-child {
padding-right: 5%;
width: 5%;
}
header {
@@ -241,20 +242,20 @@ td {
font-size: 14px;
}
td:first-child {
width: 100%;
td:nth-child(2) {
width: 80%;
}
td:nth-child(2) {
td:nth-child(3) {
padding: 0 20px 0 20px;
}
th:last-child,
td:last-child {
th:nth-child(4),
td:nth-child(4) {
text-align: right;
}
td:first-child svg {
td:nth-child(2) svg {
position: absolute;
}
@@ -301,12 +302,12 @@ footer {
display: none;
}
td:first-child {
td:nth-child(2) {
width: auto;
}
th:nth-child(2),
td:nth-child(2) {
th:nth-child(3),
td:nth-child(3) {
padding-right: 5%;
text-align: right;
}
@@ -325,7 +326,7 @@ footer {
}
</style>
</head>
<body>
<body onload='filter()'>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="0" width="0" style="position: absolute;">
<defs>
<!-- Folder -->
@@ -390,6 +391,7 @@ footer {
<table aria-describedby="summary">
<thead>
<tr>
<th></th>
<th>
{{- if and (eq .Sort "namedirfirst") (ne .Order "desc")}}
<a href="?sort=namedirfirst&order=desc{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}" class="icon"><svg width="1em" height=".5em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#up-arrow"></use></svg></a>
@@ -425,11 +427,13 @@ footer {
<a href="?sort=time&order=asc{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}">Modified</a>
{{- end}}
</th>
<th class="hideable"></th>
</tr>
</thead>
<tbody>
{{- if .CanGoUp}}
<tr>
<td></td>
<td>
<a href="..">
<span class="goup">Go up</span>
@@ -437,10 +441,12 @@ footer {
</td>
<td>&mdash;</td>
<td class="hideable">&mdash;</td>
<td class="hideable"></td>
</tr>
{{- end}}
{{- range .Items}}
<tr class="file">
<td></td>
<td>
<a href="{{html .URL}}">
{{- if .IsDir}}
@@ -457,6 +463,7 @@ footer {
<td data-order="{{.Size}}">{{.HumanSize}}</td>
{{- end}}
<td class="hideable"><time datetime="{{.HumanModTime "2006-01-02T15:04:05Z"}}">{{.HumanModTime "01/02/2006 03:04:05 PM -07:00"}}</time></td>
<td class="hideable"></td>
</tr>
{{- end}}
</tbody>
+3 -3
View File
@@ -22,8 +22,8 @@ import (
"testing"
"time"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
)
func TestSetup(t *testing.T) {
@@ -53,7 +53,7 @@ func TestSetup(t *testing.T) {
// test case #1 tests instantiation of Config with default values
{"browse /", []string{"/"}, false},
// test case #2 tests detectaction of custom template
// test case #2 tests detection of custom template
{"browse . " + tempTemplatePath, []string{"."}, false},
// test case #3 tests detection of non-existent template
+28 -28
View File
@@ -16,34 +16,34 @@ package caddyhttp
import (
// plug in the server
_ "github.com/mholt/caddy/caddyhttp/httpserver"
_ "github.com/caddyserver/caddy/caddyhttp/httpserver"
// plug in the standard directives
_ "github.com/mholt/caddy/caddyhttp/basicauth"
_ "github.com/mholt/caddy/caddyhttp/bind"
_ "github.com/mholt/caddy/caddyhttp/browse"
_ "github.com/mholt/caddy/caddyhttp/errors"
_ "github.com/mholt/caddy/caddyhttp/expvar"
_ "github.com/mholt/caddy/caddyhttp/extensions"
_ "github.com/mholt/caddy/caddyhttp/fastcgi"
_ "github.com/mholt/caddy/caddyhttp/gzip"
_ "github.com/mholt/caddy/caddyhttp/header"
_ "github.com/mholt/caddy/caddyhttp/index"
_ "github.com/mholt/caddy/caddyhttp/internalsrv"
_ "github.com/mholt/caddy/caddyhttp/limits"
_ "github.com/mholt/caddy/caddyhttp/log"
_ "github.com/mholt/caddy/caddyhttp/markdown"
_ "github.com/mholt/caddy/caddyhttp/mime"
_ "github.com/mholt/caddy/caddyhttp/pprof"
_ "github.com/mholt/caddy/caddyhttp/proxy"
_ "github.com/mholt/caddy/caddyhttp/push"
_ "github.com/mholt/caddy/caddyhttp/redirect"
_ "github.com/mholt/caddy/caddyhttp/requestid"
_ "github.com/mholt/caddy/caddyhttp/rewrite"
_ "github.com/mholt/caddy/caddyhttp/root"
_ "github.com/mholt/caddy/caddyhttp/status"
_ "github.com/mholt/caddy/caddyhttp/templates"
_ "github.com/mholt/caddy/caddyhttp/timeouts"
_ "github.com/mholt/caddy/caddyhttp/websocket"
_ "github.com/mholt/caddy/onevent"
_ "github.com/caddyserver/caddy/caddyhttp/basicauth"
_ "github.com/caddyserver/caddy/caddyhttp/bind"
_ "github.com/caddyserver/caddy/caddyhttp/browse"
_ "github.com/caddyserver/caddy/caddyhttp/errors"
_ "github.com/caddyserver/caddy/caddyhttp/expvar"
_ "github.com/caddyserver/caddy/caddyhttp/extensions"
_ "github.com/caddyserver/caddy/caddyhttp/fastcgi"
_ "github.com/caddyserver/caddy/caddyhttp/gzip"
_ "github.com/caddyserver/caddy/caddyhttp/header"
_ "github.com/caddyserver/caddy/caddyhttp/index"
_ "github.com/caddyserver/caddy/caddyhttp/internalsrv"
_ "github.com/caddyserver/caddy/caddyhttp/limits"
_ "github.com/caddyserver/caddy/caddyhttp/log"
_ "github.com/caddyserver/caddy/caddyhttp/markdown"
_ "github.com/caddyserver/caddy/caddyhttp/mime"
_ "github.com/caddyserver/caddy/caddyhttp/pprof"
_ "github.com/caddyserver/caddy/caddyhttp/proxy"
_ "github.com/caddyserver/caddy/caddyhttp/push"
_ "github.com/caddyserver/caddy/caddyhttp/redirect"
_ "github.com/caddyserver/caddy/caddyhttp/requestid"
_ "github.com/caddyserver/caddy/caddyhttp/rewrite"
_ "github.com/caddyserver/caddy/caddyhttp/root"
_ "github.com/caddyserver/caddy/caddyhttp/status"
_ "github.com/caddyserver/caddy/caddyhttp/templates"
_ "github.com/caddyserver/caddy/caddyhttp/timeouts"
_ "github.com/caddyserver/caddy/caddyhttp/websocket"
_ "github.com/caddyserver/caddy/onevent"
)
+3 -3
View File
@@ -18,16 +18,16 @@ import (
"strings"
"testing"
"github.com/mholt/caddy"
"github.com/caddyserver/caddy"
)
// TODO: this test could be improved; the purpose is to
// ensure that the standard plugins are in fact plugged in
// and registered properly; this is a quick/naive way to do it.
func TestStandardPlugins(t *testing.T) {
numStandardPlugins := 31 // importing caddyhttp plugs in this many plugins
numStandardPlugins := 32 // importing caddyhttp plugs in this many plugins
s := caddy.DescribePlugins()
if got, want := strings.Count(s, "\n"), numStandardPlugins+5; got != want {
if got, want := strings.Count(s, "\n"), numStandardPlugins+4; got != want {
t.Errorf("Expected all standard plugins to be plugged in, got:\n%s", s)
}
}
+7 -12
View File
@@ -22,10 +22,9 @@ import (
"os"
"runtime"
"strings"
"time"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
)
func init() {
@@ -50,7 +49,7 @@ func (h ErrorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, er
status, err := h.Next.ServeHTTP(w, r)
if err != nil {
errMsg := fmt.Sprintf("%s [ERROR %d %s] %v", time.Now().Format(timeFormat), status, r.URL.Path, err)
errMsg := fmt.Sprintf("[ERROR %d %s] %v", status, r.URL.Path, err)
if h.Debug {
// Write error to response instead of to log
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
@@ -79,8 +78,7 @@ func (h ErrorHandler) errorPage(w http.ResponseWriter, r *http.Request, code int
errorPage, err := os.Open(pagePath)
if err != nil {
// An additional error handling an error... <insert grumpy cat here>
h.Log.Printf("%s [NOTICE %d %s] could not load error page: %v",
time.Now().Format(timeFormat), code, r.URL.String(), err)
h.Log.Printf("[NOTICE %d %s] could not load error page: %v", code, r.URL.String(), err)
httpserver.DefaultErrorFunc(w, r, code)
return
}
@@ -93,8 +91,7 @@ func (h ErrorHandler) errorPage(w http.ResponseWriter, r *http.Request, code int
if err != nil {
// Epic fail... sigh.
h.Log.Printf("%s [NOTICE %d %s] could not respond with %s: %v",
time.Now().Format(timeFormat), code, r.URL.String(), pagePath, err)
h.Log.Printf("[NOTICE %d %s] could not respond with %s: %v", code, r.URL.String(), pagePath, err)
httpserver.DefaultErrorFunc(w, r, code)
}
@@ -142,13 +139,13 @@ func (h ErrorHandler) recovery(w http.ResponseWriter, r *http.Request) {
}
// Trim file path
delim := "/caddy/"
delim := "/github.com/caddyserver/caddy/"
pkgPathPos := strings.Index(file, delim)
if pkgPathPos > -1 && len(file) > pkgPathPos+len(delim) {
file = file[pkgPathPos+len(delim):]
}
panicMsg := fmt.Sprintf("%s [PANIC %s] %s:%d - %v", time.Now().Format(timeFormat), r.URL.String(), file, line, rec)
panicMsg := fmt.Sprintf("[PANIC %s] %s:%d - %v", r.URL.String(), file, line, rec)
if h.Debug {
// Write error and stack trace to the response rather than to a log
var stackBuf [4096]byte
@@ -160,5 +157,3 @@ func (h ErrorHandler) recovery(w http.ResponseWriter, r *http.Request) {
h.errorPage(w, r, http.StatusInternalServerError)
}
}
const timeFormat = "02/Jan/2006:15:04:05 -0700"
+2 -2
View File
@@ -26,7 +26,7 @@ import (
"strings"
"testing"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
)
func TestErrors(t *testing.T) {
@@ -153,7 +153,7 @@ func TestVisibleErrorWithPanic(t *testing.T) {
body := rec.Body.String()
if !strings.Contains(body, "[PANIC /] caddyhttp/errors/errors_test.go") {
if !strings.Contains(body, "[PANIC /]") {
t.Errorf("Expected response body to contain error log line, but it didn't:\n%s", body)
}
if !strings.Contains(body, panicMsg) {
+6 -2
View File
@@ -20,8 +20,8 @@ import (
"path/filepath"
"strconv"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
)
// setup configures a new errors middleware instance.
@@ -123,6 +123,10 @@ func errorsParse(c *caddy.Controller) (*ErrorHandler, error) {
}
}
if len(args) > 1 {
return handler, c.Errf("Only 1 Argument expected for errors directive")
}
// Configuration may be in a block
err := optionalBlock()
if err != nil {
+7 -2
View File
@@ -19,8 +19,8 @@ import (
"reflect"
"testing"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
)
func TestSetup(t *testing.T) {
@@ -179,6 +179,11 @@ func TestErrorsParse(t *testing.T) {
* generic_error.html
* generic_error.html
}`, true, ErrorHandler{ErrorPages: map[int]string{}, Log: &httpserver.Logger{}}},
{`errors /path error.txt {
404
}`, true, ErrorHandler{ErrorPages: map[int]string{}, Log: &httpserver.Logger{}}},
{`errors /path error.txt`, true, ErrorHandler{ErrorPages: map[int]string{}, Log: &httpserver.Logger{}}},
}
for i, test := range tests {
+1 -1
View File
@@ -19,7 +19,7 @@ import (
"fmt"
"net/http"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
)
// ExpVar is a simple struct to hold expvar's configuration
+1 -1
View File
@@ -20,7 +20,7 @@ import (
"net/http/httptest"
"testing"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
)
func TestExpVar(t *testing.T) {
+2 -2
View File
@@ -19,8 +19,8 @@ import (
"runtime"
"sync"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
)
func init() {
+2 -2
View File
@@ -17,8 +17,8 @@ package expvar
import (
"testing"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
)
func TestSetup(t *testing.T) {
+1 -1
View File
@@ -26,7 +26,7 @@ import (
"path"
"strings"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
)
// Ext can assume an extension from clean URLs.
+2 -2
View File
@@ -15,8 +15,8 @@
package extensions
import (
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
)
func init() {
+2 -2
View File
@@ -17,8 +17,8 @@ package extensions
import (
"testing"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
)
func TestSetup(t *testing.T) {
+16 -8
View File
@@ -35,9 +35,9 @@ import (
"crypto/tls"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/mholt/caddy/caddytls"
"github.com/caddyserver/caddy"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy/caddytls"
)
// Handler is a middleware type that can handle requests as a FastCGI client.
@@ -85,6 +85,8 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error)
// but we also want to be flexible for the script we proxy to.
fpath := r.URL.Path
// We trim those characters because they are served as plain text if appended after .php on Windows
fpath = strings.TrimRight(fpath, " .")
if idx, ok := httpserver.IndexFile(h.FileSys, fpath, rule.IndexFiles); ok {
fpath = idx
@@ -102,7 +104,8 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error)
}
// These criteria work well in this order for PHP sites
if !h.exists(fpath) || fpath[len(fpath)-1] == '/' || strings.HasSuffix(fpath, rule.Ext) {
// We lower path and Ext as on Windows, the system is case insensitive, so .PHP is served as .php
if !h.exists(fpath) || fpath[len(fpath)-1] == '/' || strings.HasSuffix(strings.ToLower(fpath), strings.ToLower(rule.Ext)) {
// Create environment for CGI script
env, err := h.buildEnv(r, rule, fpath)
@@ -242,9 +245,6 @@ func (h Handler) exists(path string) bool {
func (h Handler) buildEnv(r *http.Request, rule Rule, fpath string) (map[string]string, error) {
var env map[string]string
// Get absolute path of requested resource
absPath := filepath.Join(rule.Root, fpath)
// Separate remote IP and port; more lenient than net.SplitHostPort
var ip, port string
if idx := strings.LastIndex(r.RemoteAddr, ":"); idx > -1 {
@@ -266,11 +266,13 @@ func (h Handler) buildEnv(r *http.Request, rule Rule, fpath string) (map[string]
docURI := fpath[:splitPos+len(rule.SplitPath)]
pathInfo := fpath[splitPos+len(rule.SplitPath):]
scriptName := fpath
scriptFilename := absPath
// Strip PATH_INFO from SCRIPT_NAME
scriptName = strings.TrimSuffix(scriptName, pathInfo)
// SCRIPT_FILENAME is the absolute path of SCRIPT_NAME
scriptFilename := filepath.Join(rule.Root, scriptName)
// Add vhost path prefix to scriptName. Otherwise, some PHP software will
// have difficulty discovering its URL.
pathPrefix, _ := r.Context().Value(caddy.CtxKey("path_prefix")).(string)
@@ -286,6 +288,11 @@ func (h Handler) buildEnv(r *http.Request, rule Rule, fpath string) (map[string]
// Retrieve name of remote user that was set by some downstream middleware such as basicauth.
remoteUser, _ := r.Context().Value(httpserver.RemoteUserCtxKey).(string)
requestScheme := "http"
if r.TLS != nil {
requestScheme = "https"
}
// Some variables are unused but cleared explicitly to prevent
// the parent environment from interfering.
env = map[string]string{
@@ -302,6 +309,7 @@ func (h Handler) buildEnv(r *http.Request, rule Rule, fpath string) (map[string]
"REMOTE_IDENT": "", // Not used
"REMOTE_USER": remoteUser,
"REQUEST_METHOD": r.Method,
"REQUEST_SCHEME": requestScheme,
"SERVER_NAME": h.ServerName,
"SERVER_PORT": h.ServerPort,
"SERVER_PROTOCOL": r.Proto,
+55 -19
View File
@@ -16,18 +16,20 @@ package fastcgi
import (
"context"
"log"
"net"
"net/http"
"net/http/fcgi"
"net/http/httptest"
"net/url"
"path/filepath"
"strconv"
"sync"
"testing"
"time"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
)
func TestServeHTTP(t *testing.T) {
@@ -38,11 +40,20 @@ func TestServeHTTP(t *testing.T) {
if err != nil {
t.Fatalf("Unable to create listener for test: %v", err)
}
defer listener.Close()
go fcgi.Serve(listener, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Length", bodyLenStr)
w.Write([]byte(body))
}))
defer func() { _ = listener.Close() }()
go func() {
err := fcgi.Serve(listener, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Length", bodyLenStr)
_, err := w.Write([]byte(body))
if err != nil {
log.Printf("[ERROR] unable to write header: %v", err)
}
}))
if err != nil {
log.Printf("[ERROR] unable to start server: %v", err)
}
}()
handler := Handler{
Next: nil,
@@ -146,7 +157,7 @@ func TestBuildEnv(t *testing.T) {
SplitPath: ".php",
IndexFiles: []string{"index.php"},
}
url, err := url.Parse("http://localhost:2015/fgci_test.php?test=foobar")
u, err := url.Parse("http://localhost:2015/fgci_test.php?test=foobar")
if err != nil {
t.Error("Unexpected error:", err.Error())
}
@@ -154,7 +165,7 @@ func TestBuildEnv(t *testing.T) {
var newReq = func() *http.Request {
r := http.Request{
Method: "GET",
URL: url,
URL: u,
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
@@ -238,6 +249,21 @@ func TestBuildEnv(t *testing.T) {
envExpected = newEnv()
envExpected["SCRIPT_NAME"] = "/test/fgci_test.php"
testBuildEnv(r, rule, fpath, envExpected)
// 7. Test SCRIPT_NAME,SCRIPT_FILENAME do not include PATH_INFO
fpath = "/fgci_test.php/extra/paths"
r = newReq()
envExpected = newEnv()
envExpected["PATH_INFO"] = "/extra/paths"
envExpected["SCRIPT_NAME"] = "/fgci_test.php"
envExpected["SCRIPT_FILENAME"] = filepath.FromSlash("/fgci_test.php")
testBuildEnv(r, rule, fpath, envExpected)
// 8. Test REQUEST_SCHEME in env
r = newReq()
envExpected = newEnv()
envExpected["REQUEST_SCHEME"] = "http"
testBuildEnv(r, rule, fpath, envExpected)
}
func TestReadTimeout(t *testing.T) {
@@ -258,7 +284,7 @@ func TestReadTimeout(t *testing.T) {
if err != nil {
t.Fatalf("Test %d: Unable to create listener for test: %v", i, err)
}
defer listener.Close()
defer func() { _ = listener.Close() }()
handler := Handler{
Next: nil,
@@ -277,11 +303,16 @@ func TestReadTimeout(t *testing.T) {
w := httptest.NewRecorder()
wg.Add(1)
go fcgi.Serve(listener, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
time.Sleep(test.sleep)
w.WriteHeader(http.StatusOK)
wg.Done()
}))
go func() {
err := fcgi.Serve(listener, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
time.Sleep(test.sleep)
w.WriteHeader(http.StatusOK)
wg.Done()
}))
if err != nil {
log.Printf("[ERROR] unable to start server: %v", err)
}
}()
got, err := handler.ServeHTTP(w, r)
if test.shouldErr {
@@ -318,7 +349,7 @@ func TestSendTimeout(t *testing.T) {
if err != nil {
t.Fatalf("Test %d: Unable to create listener for test: %v", i, err)
}
defer listener.Close()
defer func() { _ = listener.Close() }()
handler := Handler{
Next: nil,
@@ -336,9 +367,14 @@ func TestSendTimeout(t *testing.T) {
}
w := httptest.NewRecorder()
go fcgi.Serve(listener, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
go func() {
err := fcgi.Serve(listener, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
if err != nil {
log.Printf("[ERROR] unable to start server: %v", err)
}
}()
got, err := handler.ServeHTTP(w, r)
if test.shouldErr {
+9 -11
View File
@@ -175,15 +175,13 @@ func (rec *record) read(r io.Reader) (buf []byte, err error) {
// FCGIClient implements a FastCGI client, which is a standard for
// interfacing external applications with Web servers.
type FCGIClient struct {
mutex sync.Mutex
rwc io.ReadWriteCloser
h header
buf bytes.Buffer
stderr bytes.Buffer
keepAlive bool
reqID uint16
readTimeout time.Duration
sendTimeout time.Duration
mutex sync.Mutex
rwc io.ReadWriteCloser
h header
buf bytes.Buffer
stderr bytes.Buffer
keepAlive bool
reqID uint16
}
// DialWithDialerContext connects to the fcgi responder at the specified network address, using custom net.Dialer
@@ -216,7 +214,7 @@ func Dial(network, address string) (fcgi *FCGIClient, err error) {
return DialContext(context.Background(), network, address)
}
// Close closes fcgi connnection
// Close closes fcgi connection
func (c *FCGIClient) Close() {
c.rwc.Close()
}
@@ -397,7 +395,7 @@ func (c *FCGIClient) Do(p map[string]string, req io.Reader) (r io.Reader, err er
body := newWriter(c, Stdin)
if req != nil {
io.Copy(body, req)
_, _ = io.Copy(body, req)
}
body.Close()
+26 -15
View File
@@ -44,7 +44,7 @@ import (
// test fcgi protocol includes:
// Get, Post, Post in multipart/form-data, and Post with files
// each key should be the md5 of the value or the file uploaded
// sepicify remote fcgi responer ip:port to test with php
// specify remote fcgi responder ip:port to test with php
// test failed if the remote fcgi(script) failed md5 verification
// and output "FAILED" in response
const (
@@ -59,7 +59,9 @@ type FastCGIServer struct{}
func (s FastCGIServer) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
req.ParseMultipartForm(100000000)
if err := req.ParseMultipartForm(100000000); err != nil {
log.Printf("[ERROR] failed to parse: %v", err)
}
stat := "PASSED"
fmt.Fprintln(resp, "-")
@@ -68,15 +70,15 @@ func (s FastCGIServer) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
length := 0
for k0, v0 := range req.Form {
h := md5.New()
io.WriteString(h, v0[0])
md5 := fmt.Sprintf("%x", h.Sum(nil))
_, _ = io.WriteString(h, v0[0])
_md5 := fmt.Sprintf("%x", h.Sum(nil))
length += len(k0)
length += len(v0[0])
// echo error when key != md5(val)
if md5 != k0 {
fmt.Fprintln(resp, "server:err ", md5, k0)
// echo error when key != _md5(val)
if _md5 != k0 {
fmt.Fprintln(resp, "server:err ", _md5, k0)
stat = "FAILED"
}
}
@@ -197,8 +199,12 @@ func generateRandFile(size int) (p string, m string) {
for i := 0; i < size/16; i++ {
buf := make([]byte, 16)
binary.PutVarint(buf, rand.Int63())
fo.Write(buf)
h.Write(buf)
if _, err := fo.Write(buf); err != nil {
log.Printf("[ERROR] failed to write buffer: %v\n", err)
}
if _, err := h.Write(buf); err != nil {
log.Printf("[ERROR] failed to write buffer: %v\n", err)
}
}
m = fmt.Sprintf("%x", h.Sum(nil))
return
@@ -214,12 +220,13 @@ func DisabledTest(t *testing.T) {
go func() {
listener, err := net.Listen("tcp", ipPort)
if err != nil {
// handle error
log.Println("listener creation failed: ", err)
}
srv := new(FastCGIServer)
fcgi.Serve(listener, srv)
if err := fcgi.Serve(listener, srv); err != nil {
log.Print("[ERROR] failed to start server: ", err)
}
}()
time.Sleep(1 * time.Second)
@@ -244,7 +251,7 @@ func DisabledTest(t *testing.T) {
for i := 0x00; i < 0xff; i++ {
v0 := strings.Repeat(string(i), 256)
h := md5.New()
io.WriteString(h, v0)
_, _ = io.WriteString(h, v0)
k0 := fmt.Sprintf("%x", h.Sum(nil))
data += k0 + "=" + url.QueryEscape(v0) + "&"
}
@@ -261,7 +268,7 @@ func DisabledTest(t *testing.T) {
for i := 0x00; i < 0xff; i++ {
v0 := strings.Repeat(string(i), 4096)
h := md5.New()
io.WriteString(h, v0)
_, _ = io.WriteString(h, v0)
k0 := fmt.Sprintf("%x", h.Sum(nil))
p1[k0] = v0
}
@@ -285,6 +292,10 @@ func DisabledTest(t *testing.T) {
delete(f0, "m0")
sendFcgi(1, fcgiParams, nil, nil, f0)
os.Remove(path0)
os.Remove(path1)
if err := os.Remove(path0); err != nil {
log.Println("[ERROR] failed to remove path: ", err)
}
if err := os.Remove(path1); err != nil {
log.Println("[ERROR] failed to remove path: ", err)
}
}
+9 -4
View File
@@ -23,10 +23,12 @@ import (
"strings"
"time"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
)
var defaultTimeout = 60 * time.Second
func init() {
caddy.RegisterPlugin("fastcgi", caddy.Plugin{
ServerType: "http",
@@ -76,8 +78,11 @@ func fastcgiParse(c *caddy.Controller) ([]Rule, error) {
}
rule := Rule{
Root: absRoot,
Path: args[0],
Root: absRoot,
Path: args[0],
ConnectTimeout: defaultTimeout,
ReadTimeout: defaultTimeout,
SendTimeout: defaultTimeout,
}
upstreams := []string{args[1]}
+43 -12
View File
@@ -19,9 +19,10 @@ import (
"fmt"
"net"
"testing"
"time"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
)
func TestSetup(t *testing.T) {
@@ -53,6 +54,18 @@ func TestSetup(t *testing.T) {
if addr != "127.0.0.1:9000" {
t.Errorf("Expected 127.0.0.1:9000 as the Address")
}
if myHandler.Rules[0].ConnectTimeout != 60*time.Second {
t.Errorf("Expected default value of 60 seconds")
}
if myHandler.Rules[0].ReadTimeout != 60*time.Second {
t.Errorf("Expected default value of 60 seconds")
}
if myHandler.Rules[0].SendTimeout != 60*time.Second {
t.Errorf("Expected default value of 60 seconds")
}
}
func TestFastcgiParse(t *testing.T) {
@@ -64,21 +77,23 @@ func TestFastcgiParse(t *testing.T) {
{`fastcgi /blog 127.0.0.1:9000 php`,
false, []Rule{{
Path: "/blog",
balancer: &roundRobin{addresses: []string{"127.0.0.1:9000"}},
Ext: ".php",
SplitPath: ".php",
IndexFiles: []string{"index.php"},
Path: "/blog",
balancer: &roundRobin{addresses: []string{"127.0.0.1:9000"}},
Ext: ".php",
SplitPath: ".php",
IndexFiles: []string{"index.php"},
SendTimeout: 60 * time.Second,
}}},
{`fastcgi / 127.0.0.1:9001 {
split .html
}`,
false, []Rule{{
Path: "/",
balancer: &roundRobin{addresses: []string{"127.0.0.1:9001"}},
Ext: "",
SplitPath: ".html",
IndexFiles: []string{},
Path: "/",
balancer: &roundRobin{addresses: []string{"127.0.0.1:9001"}},
Ext: "",
SplitPath: ".html",
IndexFiles: []string{},
SendTimeout: 60 * time.Second,
}}},
{`fastcgi / 127.0.0.1:9001 {
split .html
@@ -91,6 +106,17 @@ func TestFastcgiParse(t *testing.T) {
SplitPath: ".html",
IndexFiles: []string{},
IgnoredSubPaths: []string{"/admin", "/user"},
SendTimeout: 60 * time.Second,
}}},
{`fastcgi / 127.0.0.1:9001 {
send_timeout 30s
}`,
false, []Rule{{
Path: "/",
balancer: &roundRobin{addresses: []string{"127.0.0.1:9001"}},
Ext: "",
IndexFiles: []string{},
SendTimeout: 30 * time.Second,
}}},
}
for i, test := range tests {
@@ -146,6 +172,11 @@ func TestFastcgiParse(t *testing.T) {
t.Errorf("Test %d expected %dth FastCGI IgnoredSubPaths to be %s , but got %s",
i, j, test.expectedFastcgiConfig[j].IgnoredSubPaths, actualFastcgiConfig.IgnoredSubPaths)
}
if actualFastcgiConfig.SendTimeout != test.expectedFastcgiConfig[j].SendTimeout {
t.Errorf("Test %d expected %dth FastCGI SendTimeout to be %s , but got %s",
i, j, test.expectedFastcgiConfig[j].SendTimeout, actualFastcgiConfig.SendTimeout)
}
}
}
+45 -13
View File
@@ -17,12 +17,13 @@
package gzip
import (
"compress/gzip"
"io"
"net/http"
"strings"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
)
func init() {
@@ -65,21 +66,31 @@ outer:
}
}
// gzipWriter modifies underlying writer at init,
// use a discard writer instead to leave ResponseWriter in
// original form.
gzipWriter := getWriter(c.Level)
defer putWriter(c.Level, gzipWriter)
// In order to avoid unused memory allocation, gzip.putWriter only be called when gzip compression happened.
// see https://github.com/caddyserver/caddy/issues/2395
gz := &gzipResponseWriter{
Writer: gzipWriter,
ResponseWriterWrapper: &httpserver.ResponseWriterWrapper{ResponseWriter: w},
newWriter: func() io.Writer {
// gzipWriter modifies underlying writer at init,
// use a discard writer instead to leave ResponseWriter in
// original form.
return getWriter(c.Level)
},
}
defer func() {
if gzWriter, ok := gz.internalWriter.(*gzip.Writer); ok {
putWriter(c.Level, gzWriter)
}
}()
var rw http.ResponseWriter
// if no response filter is used
if len(c.ResponseFilters) == 0 {
// replace discard writer with ResponseWriter
gzipWriter.Reset(w)
if gzWriter, ok := gz.Writer().(*gzip.Writer); ok {
gzWriter.Reset(w)
}
rw = gz
} else {
// wrap gzip writer with ResponseFilterWriter
@@ -103,12 +114,13 @@ outer:
return g.Next.ServeHTTP(w, r)
}
// gzipResponeWriter wraps the underlying Write method
// gzipResponseWriter wraps the underlying Write method
// with a gzip.Writer to compress the output.
type gzipResponseWriter struct {
io.Writer
internalWriter io.Writer
*httpserver.ResponseWriterWrapper
statusCodeWritten bool
newWriter func() io.Writer
}
// WriteHeader wraps the underlying WriteHeader method to prevent
@@ -118,7 +130,19 @@ type gzipResponseWriter struct {
func (w *gzipResponseWriter) WriteHeader(code int) {
w.Header().Del("Content-Length")
w.Header().Set("Content-Encoding", "gzip")
w.Header().Add("Vary", "Accept-Encoding")
varyList, exist := w.Header()["Vary"]
shouldAddVary := true
if exist {
for _, vary := range varyList {
if vary == "Accept-Encoding" {
shouldAddVary = false
break
}
}
}
if shouldAddVary {
w.Header().Add("Vary", "Accept-Encoding")
}
originalEtag := w.Header().Get("ETag")
if originalEtag != "" && !strings.HasPrefix(originalEtag, "W/") {
w.Header().Set("ETag", "W/"+originalEtag)
@@ -135,9 +159,17 @@ func (w *gzipResponseWriter) Write(b []byte) (int, error) {
if !w.statusCodeWritten {
w.WriteHeader(http.StatusOK)
}
n, err := w.Writer.Write(b)
n, err := w.Writer().Write(b)
return n, err
}
//Writer use a lazy way to initialize Writer
func (w *gzipResponseWriter) Writer() io.Writer {
if w.internalWriter == nil {
w.internalWriter = w.newWriter()
}
return w.internalWriter
}
// Interface guards
var _ httpserver.HTTPInterfaces = (*gzipResponseWriter)(nil)
+1 -1
View File
@@ -23,7 +23,7 @@ import (
"strings"
"testing"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
)
func TestGzipHandler(t *testing.T) {
+2 -2
View File
@@ -18,7 +18,7 @@ import (
"net/http"
"path"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
)
// RequestFilter determines if a request should be gzipped.
@@ -30,7 +30,7 @@ type RequestFilter interface {
// defaultExtensions is the list of default extensions for which to enable gzipping.
var defaultExtensions = []string{"", ".txt", ".htm", ".html", ".css", ".php", ".js", ".json",
".md", ".mdown", ".xml", ".svg", ".go", ".cgi", ".py", ".pl", ".aspx", ".asp"}
".md", ".mdown", ".xml", ".svg", ".go", ".cgi", ".py", ".pl", ".aspx", ".asp", ".m3u", ".m3u8", ".wasm"}
// DefaultExtFilter creates an ExtFilter with default extensions.
func DefaultExtFilter() ExtFilter {
+1 -1
View File
@@ -82,7 +82,7 @@ func (r *ResponseFilterWriter) WriteHeader(code int) {
if r.shouldCompress {
// replace discard writer with ResponseWriter
if gzWriter, ok := r.gzipResponseWriter.Writer.(*gzip.Writer); ok {
if gzWriter, ok := r.gzipResponseWriter.Writer().(*gzip.Writer); ok {
gzWriter.Reset(r.ResponseWriter)
}
// use gzip WriteHeader to include and delete
+15 -6
View File
@@ -17,11 +17,12 @@ package gzip
import (
"compress/gzip"
"fmt"
"log"
"net/http"
"net/http/httptest"
"testing"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
)
func TestLengthFilter(t *testing.T) {
@@ -47,7 +48,7 @@ func TestLengthFilter(t *testing.T) {
for j, filter := range filters {
r := httptest.NewRecorder()
r.Header().Set("Content-Length", fmt.Sprint(ts.length))
wWriter := NewResponseFilterWriter([]ResponseFilter{filter}, &gzipResponseWriter{gzip.NewWriter(r), &httpserver.ResponseWriterWrapper{ResponseWriter: r}, false})
wWriter := NewResponseFilterWriter([]ResponseFilter{filter}, &gzipResponseWriter{gzip.NewWriter(r), &httpserver.ResponseWriterWrapper{ResponseWriter: r}, false, nil})
if filter.ShouldCompress(wWriter) != ts.shouldCompress[j] {
t.Errorf("Test %v: Expected %v found %v", i, ts.shouldCompress[j], filter.ShouldCompress(r))
}
@@ -77,7 +78,9 @@ func TestResponseFilterWriter(t *testing.T) {
for i, ts := range tests {
server.Next = httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
w.Header().Set("Content-Length", fmt.Sprint(len(ts.body)))
w.Write([]byte(ts.body))
if _, err := w.Write([]byte(ts.body)); err != nil {
log.Println("[ERROR] failed to write response: ", err)
}
return 200, nil
})
@@ -86,7 +89,9 @@ func TestResponseFilterWriter(t *testing.T) {
w := httptest.NewRecorder()
server.ServeHTTP(w, r)
if _, err := server.ServeHTTP(w, r); err != nil {
log.Println("[ERROR] unable to serve a gzipped response: ", err)
}
resp := w.Body.String()
@@ -109,7 +114,9 @@ func TestResponseGzippedOutput(t *testing.T) {
server.Next = httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
w.Header().Set("Content-Encoding", "gzip")
w.Write([]byte("gzipped"))
if _, err := w.Write([]byte("gzipped")); err != nil {
log.Println("[ERROR] failed to write response: ", err)
}
return 200, nil
})
@@ -117,7 +124,9 @@ func TestResponseGzippedOutput(t *testing.T) {
r.Header.Set("Accept-Encoding", "gzip")
w := httptest.NewRecorder()
server.ServeHTTP(w, r)
if _, err := server.ServeHTTP(w, r); err != nil {
log.Println("[ERROR] unable to serve a gzipped response: ", err)
}
resp := w.Body.String()
if resp != "gzipped" {
+2 -2
View File
@@ -22,8 +22,8 @@ import (
"strings"
"sync"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
)
// setup configures a new gzip middleware instance.
+2 -2
View File
@@ -17,8 +17,8 @@ package gzip
import (
"testing"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
)
func TestSetup(t *testing.T) {
+1 -1
View File
@@ -21,7 +21,7 @@ import (
"net/http"
"strings"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
)
// Headers is middleware that adds headers to the responses
+11 -4
View File
@@ -16,6 +16,7 @@ package header
import (
"fmt"
"log"
"net/http"
"net/http/httptest"
"os"
@@ -23,7 +24,7 @@ import (
"sort"
"testing"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
)
func TestHeader(t *testing.T) {
@@ -69,7 +70,9 @@ func TestHeader(t *testing.T) {
// preset header
rec.Header().Set("Server", "Caddy")
he.ServeHTTP(rec, req)
if _, err := he.ServeHTTP(rec, req); err != nil {
log.Println("[ERROR] ServeHTTP failed: ", err)
}
if got := rec.Header().Get(test.name); got != test.value {
t.Errorf("Test %d: Expected %s header to be %q but was %q",
@@ -81,7 +84,9 @@ func TestHeader(t *testing.T) {
func TestMultipleHeaders(t *testing.T) {
he := Headers{
Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
fmt.Fprint(w, "This is a test")
if _, err := fmt.Fprint(w, "This is a test"); err != nil {
log.Println("[ERROR] Fprint failed: ", err)
}
return 0, nil
}),
Rules: []Rule{
@@ -97,7 +102,9 @@ func TestMultipleHeaders(t *testing.T) {
}
rec := httptest.NewRecorder()
he.ServeHTTP(rec, req)
if _, err := he.ServeHTTP(rec, req); err != nil {
log.Println("[ERROR] ServeHTTP failed: ", err)
}
desiredHeaders := []string{"</css/main.css>; rel=preload", "</images/image.png>; rel=preload"}
actualHeaders := rec.HeaderMap[http.CanonicalHeaderKey("Link")]
+2 -2
View File
@@ -17,8 +17,8 @@ package header
import (
"net/http"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
)
func init() {
+2 -2
View File
@@ -20,8 +20,8 @@ import (
"reflect"
"testing"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
)
func TestSetup(t *testing.T) {
+2 -2
View File
@@ -20,7 +20,7 @@ import (
"regexp"
"strings"
"github.com/mholt/caddy"
"github.com/caddyserver/caddy"
)
// SetupIfMatcher parses `if` or `if_op` in the current dispenser block.
@@ -191,7 +191,7 @@ func (m IfMatcher) Or(r *http.Request) bool {
}
// IfMatcherKeyword checks if the next value in the dispenser is a keyword for 'if' config block.
// If true, remaining arguments in the dispinser are cleard to keep the dispenser valid for use.
// If true, remaining arguments in the dispenser are cleared to keep the dispenser valid for use.
func IfMatcherKeyword(c *caddy.Controller) bool {
if c.Val() == "if" || c.Val() == "if_op" {
// clear remaining args
+1 -1
View File
@@ -21,7 +21,7 @@ import (
"strings"
"testing"
"github.com/mholt/caddy"
"github.com/caddyserver/caddy"
)
func TestConditions(t *testing.T) {
+31 -18
View File
@@ -18,9 +18,11 @@ import (
"fmt"
"net"
"net/http"
"strconv"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddytls"
"github.com/caddyserver/caddy"
"github.com/caddyserver/caddy/caddytls"
"github.com/mholt/certmagic"
)
func activateHTTPS(cctx caddy.Context) error {
@@ -37,10 +39,13 @@ func activateHTTPS(cctx caddy.Context) error {
// place certificates and keys on disk
for _, c := range ctx.siteConfigs {
if c.TLS.OnDemand {
if !c.TLS.Managed {
continue
}
if c.TLS.Manager.OnDemand != nil {
continue // obtain these certificates on-demand instead
}
err := c.TLS.ObtainCert(c.TLS.Hostname, operatorPresent)
err := c.TLS.Manager.ObtainCert(c.TLS.Hostname, operatorPresent)
if err != nil {
return err
}
@@ -62,9 +67,14 @@ func activateHTTPS(cctx caddy.Context) error {
// on the ports we'd need to do ACME before we finish starting; parent process
// already running renewal ticker, so renewal won't be missed anyway.)
if !caddy.IsUpgrade() {
err = caddytls.RenewManagedCertificates(true)
if err != nil {
return err
ctx.instance.StorageMu.RLock()
certCache, ok := ctx.instance.Storage[caddytls.CertCacheInstStorageKey].(*certmagic.Cache)
ctx.instance.StorageMu.RUnlock()
if ok && certCache != nil {
err = certCache.RenewManagedCertificates()
if err != nil {
return err
}
}
}
@@ -95,13 +105,14 @@ func markQualifiedForAutoHTTPS(configs []*SiteConfig) {
// value will always be nil.
func enableAutoHTTPS(configs []*SiteConfig, loadCertificates bool) error {
for _, cfg := range configs {
if cfg == nil || cfg.TLS == nil || !cfg.TLS.Managed || cfg.TLS.OnDemand {
if cfg == nil || cfg.TLS == nil || !cfg.TLS.Managed ||
cfg.TLS.Manager == nil || cfg.TLS.Manager.OnDemand != nil {
continue
}
cfg.TLS.Enabled = true
cfg.Addr.Scheme = "https"
if loadCertificates && caddytls.HostQualifies(cfg.TLS.Hostname) {
_, err := cfg.TLS.CacheManagedCertificate(cfg.TLS.Hostname)
if loadCertificates && certmagic.HostQualifies(cfg.TLS.Hostname) {
_, err := cfg.TLS.Manager.CacheManagedCertificate(cfg.TLS.Hostname)
if err != nil {
return err
}
@@ -113,9 +124,9 @@ func enableAutoHTTPS(configs []*SiteConfig, loadCertificates bool) error {
// Set default port of 443 if not explicitly set
if cfg.Addr.Port == "" &&
cfg.TLS.Enabled &&
(!cfg.TLS.Manual || cfg.TLS.OnDemand) &&
(!cfg.TLS.Manual || cfg.TLS.Manager.OnDemand != nil) &&
cfg.Addr.Host != "localhost" {
cfg.Addr.Port = HTTPSPort
cfg.Addr.Port = strconv.Itoa(certmagic.HTTPSPort)
}
}
return nil
@@ -128,10 +139,12 @@ func enableAutoHTTPS(configs []*SiteConfig, loadCertificates bool) error {
// only set up redirects for configs that qualify. It returns the updated list of
// all configs.
func makePlaintextRedirects(allConfigs []*SiteConfig) []*SiteConfig {
httpPort := strconv.Itoa(certmagic.HTTPPort)
httpsPort := strconv.Itoa(certmagic.HTTPSPort)
for i, cfg := range allConfigs {
if cfg.TLS.Managed &&
!hostHasOtherPort(allConfigs, i, HTTPPort) &&
(cfg.Addr.Port == HTTPSPort || !hostHasOtherPort(allConfigs, i, HTTPSPort)) {
!hostHasOtherPort(allConfigs, i, httpPort) &&
(cfg.Addr.Port == httpsPort || !hostHasOtherPort(allConfigs, i, httpsPort)) {
allConfigs = append(allConfigs, redirPlaintextHost(cfg))
}
}
@@ -157,10 +170,10 @@ func hostHasOtherPort(allConfigs []*SiteConfig, thisConfigIdx int, otherPort str
// redirPlaintextHost returns a new plaintext HTTP configuration for
// a virtualHost that simply redirects to cfg, which is assumed to
// be the HTTPS configuration. The returned configuration is set
// to listen on HTTPPort. The TLS field of cfg must not be nil.
// to listen on certmagic.HTTPPort. The TLS field of cfg must not be nil.
func redirPlaintextHost(cfg *SiteConfig) *SiteConfig {
redirPort := cfg.Addr.Port
if redirPort == HTTPSPort {
if redirPort == strconv.Itoa(certmagic.HTTPSPort) {
// By default, HTTPSPort should be DefaultHTTPSPort,
// which of course doesn't need to be explicitly stated
// in the Location header. Even if HTTPSPort is changed
@@ -200,14 +213,14 @@ func redirPlaintextHost(cfg *SiteConfig) *SiteConfig {
}
host := cfg.Addr.Host
port := HTTPPort
port := strconv.Itoa(certmagic.HTTPPort)
addr := net.JoinHostPort(host, port)
return &SiteConfig{
Addr: Address{Original: addr, Host: host, Port: port},
ListenHost: cfg.ListenHost,
middleware: []Middleware{redirMiddleware},
TLS: &caddytls.Config{AltHTTPPort: cfg.TLS.AltHTTPPort, AltTLSSNIPort: cfg.TLS.AltTLSSNIPort},
TLS: &caddytls.Config{Manager: cfg.TLS.Manager},
Timeouts: cfg.Timeouts,
}
}
+24 -15
View File
@@ -16,12 +16,15 @@ package httpserver
import (
"fmt"
"log"
"net"
"net/http"
"net/http/httptest"
"strconv"
"testing"
"github.com/mholt/caddy/caddytls"
"github.com/caddyserver/caddy/caddytls"
"github.com/mholt/certmagic"
)
func TestRedirPlaintextHost(t *testing.T) {
@@ -53,7 +56,7 @@ func TestRedirPlaintextHost(t *testing.T) {
},
{
Host: "foohost",
Port: HTTPSPort, // since this is the 'default' HTTPS port, should not be included in Location value
Port: strconv.Itoa(certmagic.HTTPSPort), // since this is the 'default' HTTPS port, should not be included in Location value
},
{
Host: "*.example.com",
@@ -81,7 +84,7 @@ func TestRedirPlaintextHost(t *testing.T) {
if actual, expected := cfg.ListenHost, testcase.ListenHost; actual != expected {
t.Errorf("Test %d: Expected redir config to have bindhost %s but got %s", i, expected, actual)
}
if actual, expected := cfg.Addr.Port, HTTPPort; actual != expected {
if actual, expected := cfg.Addr.Port, strconv.Itoa(certmagic.HTTPPort); actual != expected {
t.Errorf("Test %d: Expected redir config to have port '%s' but got '%s'", i, expected, actual)
}
@@ -175,11 +178,13 @@ func TestMakePlaintextRedirects(t *testing.T) {
func TestEnableAutoHTTPS(t *testing.T) {
configs := []*SiteConfig{
{Addr: Address{Host: "example.com"}, TLS: &caddytls.Config{Managed: true}},
{Addr: Address{Host: "example.com"}, TLS: &caddytls.Config{Managed: true, Manager: &certmagic.Config{}}},
{}, // not managed - no changes!
}
enableAutoHTTPS(configs, false)
if err := enableAutoHTTPS(configs, false); err != nil {
log.Println("[ERROR] enableAutoHTTPS failed: ", err)
}
if !configs[0].TLS.Enabled {
t.Errorf("Expected config 0 to have TLS.Enabled == true, but it was false")
@@ -196,18 +201,18 @@ func TestEnableAutoHTTPS(t *testing.T) {
func TestMarkQualifiedForAutoHTTPS(t *testing.T) {
// TODO: caddytls.TestQualifiesForManagedTLS and this test share nearly the same config list...
configs := []*SiteConfig{
{Addr: Address{Host: ""}, TLS: new(caddytls.Config)},
{Addr: Address{Host: "localhost"}, TLS: new(caddytls.Config)},
{Addr: Address{Host: "123.44.3.21"}, TLS: new(caddytls.Config)},
{Addr: Address{Host: "example.com"}, TLS: new(caddytls.Config)},
{Addr: Address{Host: ""}, TLS: newManagedConfig()},
{Addr: Address{Host: "localhost"}, TLS: newManagedConfig()},
{Addr: Address{Host: "123.44.3.21"}, TLS: newManagedConfig()},
{Addr: Address{Host: "example.com"}, TLS: newManagedConfig()},
{Addr: Address{Host: "example.com"}, TLS: &caddytls.Config{Manual: true}},
{Addr: Address{Host: "example.com"}, TLS: &caddytls.Config{ACMEEmail: "off"}},
{Addr: Address{Host: "example.com"}, TLS: &caddytls.Config{ACMEEmail: "foo@bar.com"}},
{Addr: Address{Host: "example.com", Scheme: "http"}, TLS: new(caddytls.Config)},
{Addr: Address{Host: "example.com", Port: "80"}, TLS: new(caddytls.Config)},
{Addr: Address{Host: "example.com", Port: "1234"}, TLS: new(caddytls.Config)},
{Addr: Address{Host: "example.com", Scheme: "https"}, TLS: new(caddytls.Config)},
{Addr: Address{Host: "example.com", Port: "80", Scheme: "https"}, TLS: new(caddytls.Config)},
{Addr: Address{Host: "example.com"}, TLS: &caddytls.Config{ACMEEmail: "foo@bar.com", Manager: &certmagic.Config{}}},
{Addr: Address{Host: "example.com", Scheme: "http"}, TLS: newManagedConfig()},
{Addr: Address{Host: "example.com", Port: "80"}, TLS: newManagedConfig()},
{Addr: Address{Host: "example.com", Port: "1234"}, TLS: newManagedConfig()},
{Addr: Address{Host: "example.com", Scheme: "https"}, TLS: newManagedConfig()},
{Addr: Address{Host: "example.com", Port: "80", Scheme: "https"}, TLS: newManagedConfig()},
}
expectedManagedCount := 4
@@ -224,3 +229,7 @@ func TestMarkQualifiedForAutoHTTPS(t *testing.T) {
t.Errorf("Expected %d managed configs, but got %d", expectedManagedCount, count)
}
}
func newManagedConfig() *caddytls.Config {
return &caddytls.Config{Manager: &certmagic.Config{}}
}
+4 -6
View File
@@ -23,8 +23,8 @@ import (
"strings"
"sync"
"github.com/hashicorp/go-syslog"
"github.com/mholt/caddy"
"github.com/caddyserver/caddy"
gsyslog "github.com/hashicorp/go-syslog"
)
var remoteSyslogPrefixes = map[string]string{
@@ -79,10 +79,8 @@ func (l Logger) MaskIP(ip string) string {
if reqIP.To4() != nil {
return reqIP.Mask(l.V4ipMask).String()
} else {
return reqIP.Mask(l.V6ipMask).String()
}
return reqIP.Mask(l.V6ipMask).String()
}
// ShouldLog returns true if the path is not exempted from
@@ -162,7 +160,7 @@ selectwriter:
return err
}
if l.Roller != nil {
if l.Roller != nil && !l.Roller.Disabled {
file.Close()
l.Roller.Filename = l.Output
l.writer = l.Roller.GetLogWriter()
+7 -2
View File
@@ -20,6 +20,7 @@ import (
"bytes"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
@@ -179,9 +180,13 @@ func bootServer(location string, ch chan format.LogParts) (*syslog.Server, error
switch address.network {
case "tcp":
server.ListenTCP(address.address)
if err := server.ListenTCP(address.address); err != nil {
log.Println("[ERROR] server failed to listen on TCP address: ", err)
}
case "udp":
server.ListenUDP(address.address)
if err := server.ListenUDP(address.address); err != nil {
log.Println("[ERROR] server failed to listen on UDP address: ", err)
}
}
server.SetHandler(syslog.NewChannelHandler(ch))
+5 -1
View File
@@ -21,7 +21,7 @@ import (
"path"
"time"
"github.com/mholt/caddy"
"github.com/caddyserver/caddy"
)
func init() {
@@ -117,6 +117,10 @@ func (c ConfigSelector) Select(r *http.Request) (config HandlerConfig) {
// path separator, just like URLs. IndexFle handles path manipulation
// internally for systems that use different path separators.
func IndexFile(root http.FileSystem, fpath string, indexFiles []string) (string, bool) {
if len(fpath) == 0 {
// https://caddy.community/t/panic-runtime-error-index-out-of-range/5781
fpath = "/"
}
if fpath[len(fpath)-1] != '/' || root == nil {
return "", false
}
+64 -46
View File
@@ -24,6 +24,9 @@ import (
"strconv"
"strings"
"sync"
"github.com/caddyserver/caddy/caddytls"
"github.com/caddyserver/caddy/telemetry"
)
// tlsHandler is a http.Handler that will inject a value
@@ -49,6 +52,9 @@ type tlsHandler struct {
// Halderman, et. al. in "The Security Impact of HTTPS Interception" (NDSS '17):
// https://jhalderm.com/pub/papers/interception-ndss17.pdf
func (h *tlsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// TODO: one request per connection, we should report UA in connection with
// handshake (reported in caddytls package) and our MITM assessment
if h.listener == nil {
h.next.ServeHTTP(w, r)
return
@@ -59,11 +65,16 @@ func (h *tlsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.listener.helloInfosMu.RUnlock()
ua := r.Header.Get("User-Agent")
uaHash := telemetry.FastHash([]byte(ua))
// report this request's UA in connection with this ClientHello
go telemetry.AppendUnique("tls_client_hello_ua:"+caddytls.ClientHelloInfo(info).Key(), uaHash)
var checked, mitm bool
if r.Header.Get("X-BlueCoat-Via") != "" || // Blue Coat (masks User-Agent header to generic values)
r.Header.Get("X-FCCKV2") != "" || // Fortinet
info.advertisesHeartbeatSupport() { // no major browsers have ever implemented Heartbeat
// TODO: Move the heartbeat check into each "looksLike" function...
checked = true
mitm = true
} else if strings.Contains(ua, "Edge") || strings.Contains(ua, "MSIE") ||
@@ -97,6 +108,13 @@ func (h *tlsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if checked {
r = r.WithContext(context.WithValue(r.Context(), MitmCtxKey, mitm))
if mitm {
go telemetry.AppendUnique("http_mitm", "likely")
} else {
go telemetry.AppendUnique("http_mitm", "unlikely")
}
} else {
go telemetry.AppendUnique("http_mitm", "unknown")
}
if mitm && h.closeOnMITM {
@@ -195,6 +213,11 @@ func (c *clientHelloConn) Read(b []byte) (n int, err error) {
c.listener.helloInfos[c.Conn.RemoteAddr().String()] = rawParsed
c.listener.helloInfosMu.Unlock()
// report this ClientHello to telemetry
chKey := caddytls.ClientHelloInfo(rawParsed).Key()
go telemetry.SetNested("tls_client_hello", chKey, rawParsed)
go telemetry.AppendUnique("tls_client_hello_count", chKey)
c.readHello = true
return
}
@@ -215,6 +238,7 @@ func parseRawClientHello(data []byte) (info rawHelloInfo) {
if len(data) < 42 {
return
}
info.Version = uint16(data[4])<<8 | uint16(data[5])
sessionIDLen := int(data[38])
if sessionIDLen > 32 || len(data) < 39+sessionIDLen {
return
@@ -231,9 +255,9 @@ func parseRawClientHello(data []byte) (info rawHelloInfo) {
}
numCipherSuites := cipherSuiteLen / 2
// read in the cipher suites
info.cipherSuites = make([]uint16, numCipherSuites)
info.CipherSuites = make([]uint16, numCipherSuites)
for i := 0; i < numCipherSuites; i++ {
info.cipherSuites[i] = uint16(data[2+2*i])<<8 | uint16(data[3+2*i])
info.CipherSuites[i] = uint16(data[2+2*i])<<8 | uint16(data[3+2*i])
}
data = data[2+cipherSuiteLen:]
if len(data) < 1 {
@@ -244,7 +268,7 @@ func parseRawClientHello(data []byte) (info rawHelloInfo) {
if len(data) < 1+compressionMethodsLen {
return
}
info.compressionMethods = data[1 : 1+compressionMethodsLen]
info.CompressionMethods = data[1 : 1+compressionMethodsLen]
data = data[1+compressionMethodsLen:]
@@ -272,7 +296,7 @@ func parseRawClientHello(data []byte) (info rawHelloInfo) {
}
// record that the client advertised support for this extension
info.extensions = append(info.extensions, extension)
info.Extensions = append(info.Extensions, extension)
switch extension {
case extensionSupportedCurves:
@@ -285,10 +309,10 @@ func parseRawClientHello(data []byte) (info rawHelloInfo) {
return
}
numCurves := l / 2
info.curves = make([]tls.CurveID, numCurves)
info.Curves = make([]tls.CurveID, numCurves)
d := data[2:]
for i := 0; i < numCurves; i++ {
info.curves[i] = tls.CurveID(d[0])<<8 | tls.CurveID(d[1])
info.Curves[i] = tls.CurveID(d[0])<<8 | tls.CurveID(d[1])
d = d[2:]
}
case extensionSupportedPoints:
@@ -300,8 +324,8 @@ func parseRawClientHello(data []byte) (info rawHelloInfo) {
if length != l+1 {
return
}
info.points = make([]uint8, l)
copy(info.points, data[1:])
info.Points = make([]uint8, l)
copy(info.Points, data[1:])
}
data = data[length:]
@@ -352,18 +376,12 @@ func (l *tlsHelloListener) Accept() (net.Conn, error) {
// by Durumeric, Halderman, et. al. in
// "The Security Impact of HTTPS Interception":
// https://jhalderm.com/pub/papers/interception-ndss17.pdf
type rawHelloInfo struct {
cipherSuites []uint16
extensions []uint16
compressionMethods []byte
curves []tls.CurveID
points []uint8
}
type rawHelloInfo caddytls.ClientHelloInfo
// advertisesHeartbeatSupport returns true if info indicates
// that the client supports the Heartbeat extension.
func (info rawHelloInfo) advertisesHeartbeatSupport() bool {
for _, ext := range info.extensions {
for _, ext := range info.Extensions {
if ext == extensionHeartbeat {
return true
}
@@ -386,31 +404,31 @@ func (info rawHelloInfo) looksLikeFirefox() bool {
// Note: Firefox 55+ doesn't appear to advertise 0xFF03 (65283, short headers). It used to be between 5 and 13.
// Note: Firefox on Fedora (or RedHat) doesn't include ECC suites because of patent liability.
requiredExtensionsOrder := []uint16{23, 65281, 10, 11, 35, 16, 5, 13}
if !assertPresenceAndOrdering(requiredExtensionsOrder, info.extensions, true) {
if !assertPresenceAndOrdering(requiredExtensionsOrder, info.Extensions, true) {
return false
}
// We check for both presence of curves and their ordering.
requiredCurves := []tls.CurveID{29, 23, 24, 25}
if len(info.curves) < len(requiredCurves) {
if len(info.Curves) < len(requiredCurves) {
return false
}
for i := range requiredCurves {
if info.curves[i] != requiredCurves[i] {
if info.Curves[i] != requiredCurves[i] {
return false
}
}
if len(info.curves) > len(requiredCurves) {
if len(info.Curves) > len(requiredCurves) {
// newer Firefox (55 Nightly?) may have additional curves at end of list
allowedCurves := []tls.CurveID{256, 257}
for i := range allowedCurves {
if info.curves[len(requiredCurves)+i] != allowedCurves[i] {
if info.Curves[len(requiredCurves)+i] != allowedCurves[i] {
return false
}
}
}
if hasGreaseCiphers(info.cipherSuites) {
if hasGreaseCiphers(info.CipherSuites) {
return false
}
@@ -437,7 +455,7 @@ func (info rawHelloInfo) looksLikeFirefox() bool {
tls.TLS_RSA_WITH_AES_256_CBC_SHA, // 0x35
tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, // 0xa
}
return assertPresenceAndOrdering(expectedCipherSuiteOrder, info.cipherSuites, false)
return assertPresenceAndOrdering(expectedCipherSuiteOrder, info.CipherSuites, false)
}
// looksLikeChrome returns true if info looks like a handshake
@@ -478,20 +496,20 @@ func (info rawHelloInfo) looksLikeChrome() bool {
TLS_DHE_RSA_WITH_AES_128_CBC_SHA: {}, // 0x33
TLS_DHE_RSA_WITH_AES_256_CBC_SHA: {}, // 0x39
}
for _, ext := range info.cipherSuites {
for _, ext := range info.CipherSuites {
if _, ok := chromeCipherExclusions[ext]; ok {
return false
}
}
// Chrome does not include curve 25 (CurveP521) (as of Chrome 56, Feb. 2017).
for _, curve := range info.curves {
for _, curve := range info.Curves {
if curve == 25 {
return false
}
}
if !hasGreaseCiphers(info.cipherSuites) {
if !hasGreaseCiphers(info.CipherSuites) {
return false
}
@@ -509,19 +527,19 @@ func (info rawHelloInfo) looksLikeEdge() bool {
// More specifically, the OCSP status request extension appears
// *directly* before the other two extensions, which occur in that
// order. (I contacted the authors for clarification and verified it.)
for i, ext := range info.extensions {
for i, ext := range info.Extensions {
if ext == extensionOCSPStatusRequest {
if len(info.extensions) <= i+2 {
if len(info.Extensions) <= i+2 {
return false
}
if info.extensions[i+1] != extensionSupportedCurves ||
info.extensions[i+2] != extensionSupportedPoints {
if info.Extensions[i+1] != extensionSupportedCurves ||
info.Extensions[i+2] != extensionSupportedPoints {
return false
}
}
}
for _, cs := range info.cipherSuites {
for _, cs := range info.CipherSuites {
// As of Feb. 2017, Edge does not have 0xff, but Avast adds it
if cs == scsvRenegotiation {
return false
@@ -532,7 +550,7 @@ func (info rawHelloInfo) looksLikeEdge() bool {
}
}
if hasGreaseCiphers(info.cipherSuites) {
if hasGreaseCiphers(info.CipherSuites) {
return false
}
@@ -558,23 +576,23 @@ func (info rawHelloInfo) looksLikeSafari() bool {
// We check for the presence and order of the extensions.
requiredExtensionsOrder := []uint16{10, 11, 13, 13172, 16, 5, 18, 23}
if !assertPresenceAndOrdering(requiredExtensionsOrder, info.extensions, true) {
if !assertPresenceAndOrdering(requiredExtensionsOrder, info.Extensions, true) {
// Safari on iOS 11 (beta) uses different set/ordering of extensions
requiredExtensionsOrderiOS11 := []uint16{65281, 0, 23, 13, 5, 13172, 18, 16, 11, 10}
if !assertPresenceAndOrdering(requiredExtensionsOrderiOS11, info.extensions, true) {
if !assertPresenceAndOrdering(requiredExtensionsOrderiOS11, info.Extensions, true) {
return false
}
} else {
// For these versions of Safari, expect TLS_EMPTY_RENEGOTIATION_INFO_SCSV first.
if len(info.cipherSuites) < 1 {
if len(info.CipherSuites) < 1 {
return false
}
if info.cipherSuites[0] != scsvRenegotiation {
if info.CipherSuites[0] != scsvRenegotiation {
return false
}
}
if hasGreaseCiphers(info.cipherSuites) {
if hasGreaseCiphers(info.CipherSuites) {
return false
}
@@ -599,19 +617,19 @@ func (info rawHelloInfo) looksLikeSafari() bool {
tls.TLS_RSA_WITH_AES_256_CBC_SHA, // 0x35
tls.TLS_RSA_WITH_AES_128_CBC_SHA, // 0x2f
}
return assertPresenceAndOrdering(expectedCipherSuiteOrder, info.cipherSuites, true)
return assertPresenceAndOrdering(expectedCipherSuiteOrder, info.CipherSuites, true)
}
// looksLikeTor returns true if the info looks like a ClientHello from Tor browser
// (based on Firefox).
func (info rawHelloInfo) looksLikeTor() bool {
requiredExtensionsOrder := []uint16{10, 11, 16, 5, 13}
if !assertPresenceAndOrdering(requiredExtensionsOrder, info.extensions, true) {
if !assertPresenceAndOrdering(requiredExtensionsOrder, info.Extensions, true) {
return false
}
// check for session tickets support; Tor doesn't support them to prevent tracking
for _, ext := range info.extensions {
for _, ext := range info.Extensions {
if ext == 35 {
return false
}
@@ -619,12 +637,12 @@ func (info rawHelloInfo) looksLikeTor() bool {
// We check for both presence of curves and their ordering, including
// an optional curve at the beginning (for Tor based on Firefox 52)
infoCurves := info.curves
if len(info.curves) == 4 {
if info.curves[0] != 29 {
infoCurves := info.Curves
if len(info.Curves) == 4 {
if info.Curves[0] != 29 {
return false
}
infoCurves = info.curves[1:]
infoCurves = info.Curves[1:]
}
requiredCurves := []tls.CurveID{23, 24, 25}
if len(infoCurves) < len(requiredCurves) {
@@ -636,7 +654,7 @@ func (info rawHelloInfo) looksLikeTor() bool {
}
}
if hasGreaseCiphers(info.cipherSuites) {
if hasGreaseCiphers(info.CipherSuites) {
return false
}
@@ -663,7 +681,7 @@ func (info rawHelloInfo) looksLikeTor() bool {
tls.TLS_RSA_WITH_AES_256_CBC_SHA, // 0x35
tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, // 0xa
}
return assertPresenceAndOrdering(expectedCipherSuiteOrder, info.cipherSuites, false)
return assertPresenceAndOrdering(expectedCipherSuiteOrder, info.CipherSuites, false)
}
// assertPresenceAndOrdering will return true if candidateList contains
+29 -25
View File
@@ -32,44 +32,48 @@ func TestParseClientHello(t *testing.T) {
// curl 7.51.0 (x86_64-apple-darwin16.0) libcurl/7.51.0 SecureTransport zlib/1.2.8
inputHex: `010000a6030358a28c73a71bdfc1f09dee13fecdc58805dcce42ac44254df548f14645f7dc2c00004400ffc02cc02bc024c023c00ac009c008c030c02fc028c027c014c013c012009f009e006b0067003900330016009d009c003d003c0035002f000a00af00ae008d008c008b01000039000a00080006001700180019000b00020100000d00120010040102010501060104030203050306030005000501000000000012000000170000`,
expected: rawHelloInfo{
cipherSuites: []uint16{255, 49196, 49195, 49188, 49187, 49162, 49161, 49160, 49200, 49199, 49192, 49191, 49172, 49171, 49170, 159, 158, 107, 103, 57, 51, 22, 157, 156, 61, 60, 53, 47, 10, 175, 174, 141, 140, 139},
extensions: []uint16{10, 11, 13, 5, 18, 23},
compressionMethods: []byte{0},
curves: []tls.CurveID{23, 24, 25},
points: []uint8{0},
Version: 0x303,
CipherSuites: []uint16{255, 49196, 49195, 49188, 49187, 49162, 49161, 49160, 49200, 49199, 49192, 49191, 49172, 49171, 49170, 159, 158, 107, 103, 57, 51, 22, 157, 156, 61, 60, 53, 47, 10, 175, 174, 141, 140, 139},
Extensions: []uint16{10, 11, 13, 5, 18, 23},
CompressionMethods: []byte{0},
Curves: []tls.CurveID{23, 24, 25},
Points: []uint8{0},
},
},
{
// Chrome 56
inputHex: `010000c003031dae75222dae1433a5a283ddcde8ddabaefbf16d84f250eee6fdff48cdfff8a00000201a1ac02bc02fc02cc030cca9cca8cc14cc13c013c014009c009d002f0035000a010000777a7a0000ff010001000000000e000c0000096c6f63616c686f73740017000000230000000d00140012040308040401050308050501080606010201000500050100000000001200000010000e000c02683208687474702f312e3175500000000b00020100000a000a0008aaaa001d001700182a2a000100`,
expected: rawHelloInfo{
cipherSuites: []uint16{6682, 49195, 49199, 49196, 49200, 52393, 52392, 52244, 52243, 49171, 49172, 156, 157, 47, 53, 10},
extensions: []uint16{31354, 65281, 0, 23, 35, 13, 5, 18, 16, 30032, 11, 10, 10794},
compressionMethods: []byte{0},
curves: []tls.CurveID{43690, 29, 23, 24},
points: []uint8{0},
Version: 0x303,
CipherSuites: []uint16{6682, 49195, 49199, 49196, 49200, 52393, 52392, 52244, 52243, 49171, 49172, 156, 157, 47, 53, 10},
Extensions: []uint16{31354, 65281, 0, 23, 35, 13, 5, 18, 16, 30032, 11, 10, 10794},
CompressionMethods: []byte{0},
Curves: []tls.CurveID{43690, 29, 23, 24},
Points: []uint8{0},
},
},
{
// Firefox 51
inputHex: `010000bd030375f9022fc3a6562467f3540d68013b2d0b961979de6129e944efe0b35531323500001ec02bc02fcca9cca8c02cc030c00ac009c013c01400330039002f0035000a010000760000000e000c0000096c6f63616c686f737400170000ff01000100000a000a0008001d001700180019000b00020100002300000010000e000c02683208687474702f312e31000500050100000000ff030000000d0020001e040305030603020308040805080604010501060102010402050206020202`,
expected: rawHelloInfo{
cipherSuites: []uint16{49195, 49199, 52393, 52392, 49196, 49200, 49162, 49161, 49171, 49172, 51, 57, 47, 53, 10},
extensions: []uint16{0, 23, 65281, 10, 11, 35, 16, 5, 65283, 13},
compressionMethods: []byte{0},
curves: []tls.CurveID{29, 23, 24, 25},
points: []uint8{0},
Version: 0x303,
CipherSuites: []uint16{49195, 49199, 52393, 52392, 49196, 49200, 49162, 49161, 49171, 49172, 51, 57, 47, 53, 10},
Extensions: []uint16{0, 23, 65281, 10, 11, 35, 16, 5, 65283, 13},
CompressionMethods: []byte{0},
Curves: []tls.CurveID{29, 23, 24, 25},
Points: []uint8{0},
},
},
{
// openssl s_client (OpenSSL 0.9.8zh 14 Jan 2016)
inputHex: `0100012b03035d385236b8ca7b7946fa0336f164e76bf821ed90e8de26d97cc677671b6f36380000acc030c02cc028c024c014c00a00a500a300a1009f006b006a0069006800390038003700360088008700860085c032c02ec02ac026c00fc005009d003d00350084c02fc02bc027c023c013c00900a400a200a0009e00670040003f003e0033003200310030009a0099009800970045004400430042c031c02dc029c025c00ec004009c003c002f009600410007c011c007c00cc00200050004c012c008001600130010000dc00dc003000a00ff0201000055000b000403000102000a001c001a00170019001c001b0018001a0016000e000d000b000c0009000a00230000000d0020001e060106020603050105020503040104020403030103020303020102020203000f000101`,
expected: rawHelloInfo{
cipherSuites: []uint16{49200, 49196, 49192, 49188, 49172, 49162, 165, 163, 161, 159, 107, 106, 105, 104, 57, 56, 55, 54, 136, 135, 134, 133, 49202, 49198, 49194, 49190, 49167, 49157, 157, 61, 53, 132, 49199, 49195, 49191, 49187, 49171, 49161, 164, 162, 160, 158, 103, 64, 63, 62, 51, 50, 49, 48, 154, 153, 152, 151, 69, 68, 67, 66, 49201, 49197, 49193, 49189, 49166, 49156, 156, 60, 47, 150, 65, 7, 49169, 49159, 49164, 49154, 5, 4, 49170, 49160, 22, 19, 16, 13, 49165, 49155, 10, 255},
extensions: []uint16{11, 10, 35, 13, 15},
compressionMethods: []byte{1, 0},
curves: []tls.CurveID{23, 25, 28, 27, 24, 26, 22, 14, 13, 11, 12, 9, 10},
points: []uint8{0, 1, 2},
Version: 0x303,
CipherSuites: []uint16{49200, 49196, 49192, 49188, 49172, 49162, 165, 163, 161, 159, 107, 106, 105, 104, 57, 56, 55, 54, 136, 135, 134, 133, 49202, 49198, 49194, 49190, 49167, 49157, 157, 61, 53, 132, 49199, 49195, 49191, 49187, 49171, 49161, 164, 162, 160, 158, 103, 64, 63, 62, 51, 50, 49, 48, 154, 153, 152, 151, 69, 68, 67, 66, 49201, 49197, 49193, 49189, 49166, 49156, 156, 60, 47, 150, 65, 7, 49169, 49159, 49164, 49154, 5, 4, 49170, 49160, 22, 19, 16, 13, 49165, 49155, 10, 255},
Extensions: []uint16{11, 10, 35, 13, 15},
CompressionMethods: []byte{1, 0},
Curves: []tls.CurveID{23, 25, 28, 27, 24, 26, 22, 14, 13, 11, 12, 9, 10},
Points: []uint8{0, 1, 2},
},
},
} {
@@ -331,15 +335,15 @@ func TestHeuristicFunctionsAndHandler(t *testing.T) {
// in other words, if one returns true, the others
// should return false, with as little logic as possible,
// but with enough logic to force TLS proxies to do a
// good job preserving characterstics of the handshake.
// good job preserving characteristics of the handshake.
if (isChrome && (isFirefox || isSafari || isEdge || isTor)) ||
(isFirefox && (isChrome || isSafari || isEdge || isTor)) ||
(isSafari && (isChrome || isFirefox || isEdge || isTor)) ||
(isEdge && (isChrome || isFirefox || isSafari || isTor)) ||
(isTor && (isChrome || isFirefox || isSafari || isEdge)) {
t.Errorf("[%s] Test %d: Multiple fingerprinting functions matched: "+
"Chrome=%v Firefox=%v Safari=%v Edge=%v Tor=%v\n\tparsed hello dec: %+v\n\tparsed hello hex: %#x\n",
client, i, isChrome, isFirefox, isSafari, isEdge, isTor, parsed, parsed)
"Chrome=%v Firefox=%v Safari=%v Edge=%v Tor=%v\n\tparsed hello dec: %+v\n",
client, i, isChrome, isFirefox, isSafari, isEdge, isTor, parsed)
}
// test the handler and detection results
@@ -367,8 +371,8 @@ func TestHeuristicFunctionsAndHandler(t *testing.T) {
if got != want {
t.Errorf("[%s] Test %d: Expected MITM=%v but got %v (type assertion OK (checked)=%v)",
client, i, want, got, checked)
t.Errorf("[%s] Test %d: Looks like Chrome=%v Firefox=%v Safari=%v Edge=%v Tor=%v\n\tparsed hello dec: %+v\n\tparsed hello hex: %#x\n",
client, i, isChrome, isFirefox, isSafari, isEdge, isTor, parsed, parsed)
t.Errorf("[%s] Test %d: Looks like Chrome=%v Firefox=%v Safari=%v Edge=%v Tor=%v\n\tparsed hello dec: %+v\n",
client, i, isChrome, isFirefox, isSafari, isEdge, isTor, parsed)
}
}
}
+164 -63
View File
@@ -15,6 +15,7 @@
package httpserver
import (
"crypto/tls"
"flag"
"fmt"
"log"
@@ -22,20 +23,23 @@ import (
"net/url"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyfile"
"github.com/mholt/caddy/caddyhttp/staticfiles"
"github.com/mholt/caddy/caddytls"
"github.com/caddyserver/caddy"
"github.com/caddyserver/caddy/caddyfile"
"github.com/caddyserver/caddy/caddyhttp/staticfiles"
"github.com/caddyserver/caddy/caddytls"
"github.com/caddyserver/caddy/telemetry"
"github.com/mholt/certmagic"
)
const serverType = "http"
func init() {
flag.StringVar(&HTTPPort, "http-port", HTTPPort, "Default port to use for HTTP")
flag.StringVar(&HTTPSPort, "https-port", HTTPSPort, "Default port to use for HTTPS")
flag.IntVar(&certmagic.HTTPPort, "http-port", certmagic.HTTPPort, "Default port to use for HTTP")
flag.IntVar(&certmagic.HTTPSPort, "https-port", certmagic.HTTPSPort, "Default port to use for HTTPS")
flag.StringVar(&Host, "host", DefaultHost, "Default host")
flag.StringVar(&Port, "port", DefaultPort, "Default port")
flag.StringVar(&Root, "root", DefaultRoot, "Root path of default site")
@@ -65,6 +69,12 @@ func init() {
caddy.RegisterParsingCallback(serverType, "root", hideCaddyfile)
caddy.RegisterParsingCallback(serverType, "tls", activateHTTPS)
caddytls.RegisterConfigGetter(serverType, func(c *caddy.Controller) *caddytls.Config { return GetConfig(c).TLS })
// disable the caddytls package reporting ClientHellos
// to telemetry, since our MITM detector does this but
// with more information than the standard lib provides
// (as of May 2018)
caddytls.ClientHelloTelemetry = false
}
// hideCaddyfile hides the source/origin Caddyfile if it is within the
@@ -118,6 +128,8 @@ func (h *httpContext) saveConfig(key string, cfg *SiteConfig) {
// be parsed and executed.
func (h *httpContext) InspectServerBlocks(sourceFile string, serverBlocks []caddyfile.ServerBlock) ([]caddyfile.ServerBlock, error) {
siteAddrs := make(map[string]string)
httpPort := strconv.Itoa(certmagic.HTTPPort)
httpsPort := strconv.Itoa(certmagic.HTTPSPort)
// For each address in each server block, make a new config
for _, sb := range serverBlocks {
@@ -161,21 +173,32 @@ func (h *httpContext) InspectServerBlocks(sourceFile string, serverBlocks []cadd
// If default HTTP or HTTPS ports have been customized,
// make sure the ACME challenge ports match
var altHTTPPort, altTLSSNIPort string
if HTTPPort != DefaultHTTPPort {
altHTTPPort = HTTPPort
var altHTTPPort, altTLSALPNPort int
if httpPort != DefaultHTTPPort {
portInt, err := strconv.Atoi(httpPort)
if err != nil {
return nil, err
}
altHTTPPort = portInt
}
if HTTPSPort != DefaultHTTPSPort {
altTLSSNIPort = HTTPSPort
if httpsPort != DefaultHTTPSPort {
portInt, err := strconv.Atoi(httpsPort)
if err != nil {
return nil, err
}
altTLSALPNPort = portInt
}
// Make our caddytls.Config, which has a pointer to the
// instance's certificate cache and enough information
// to use automatic HTTPS when the time comes
caddytlsConfig := caddytls.NewConfig(h.instance)
caddytlsConfig, err := caddytls.NewConfig(h.instance)
if err != nil {
return nil, fmt.Errorf("creating new caddytls configuration: %v", err)
}
caddytlsConfig.Hostname = addr.Host
caddytlsConfig.AltHTTPPort = altHTTPPort
caddytlsConfig.AltTLSSNIPort = altTLSSNIPort
caddytlsConfig.Manager.AltHTTPPort = altHTTPPort
caddytlsConfig.Manager.AltTLSALPNPort = altTLSALPNPort
// Save the config to our master list, and key it for lookups
cfg := &SiteConfig{
@@ -207,13 +230,48 @@ func (h *httpContext) InspectServerBlocks(sourceFile string, serverBlocks []cadd
// MakeServers uses the newly-created siteConfigs to
// create and return a list of server instances.
func (h *httpContext) MakeServers() ([]caddy.Server, error) {
// make sure TLS is disabled for explicitly-HTTP sites
// (necessary when HTTP address shares a block containing tls)
httpPort := strconv.Itoa(certmagic.HTTPPort)
httpsPort := strconv.Itoa(certmagic.HTTPSPort)
// make a rough estimate as to whether we're in a "production
// environment/system" - start by assuming that most production
// servers will set their default CA endpoint to a public,
// trusted CA (obviously not a perfect heuristic)
var looksLikeProductionCA bool
for _, publicCAEndpoint := range caddytls.KnownACMECAs {
if strings.Contains(certmagic.Default.CA, publicCAEndpoint) {
looksLikeProductionCA = true
break
}
}
// Iterate each site configuration and make sure that:
// 1) TLS is disabled for explicitly-HTTP sites (necessary
// when an HTTP address shares a block containing tls)
// 2) if QUIC is enabled, TLS ClientAuth is not, because
// currently, QUIC does not support ClientAuth (TODO:
// revisit this when our QUIC implementation supports it)
// 3) if TLS ClientAuth is used, StrictHostMatching is on
var atLeastOneSiteLooksLikeProduction bool
for _, cfg := range h.siteConfigs {
// see if all the addresses (both sites and
// listeners) are loopback to help us determine
// if this is a "production" instance or not
if !atLeastOneSiteLooksLikeProduction {
if !caddy.IsLoopback(cfg.Addr.Host) &&
!caddy.IsLoopback(cfg.ListenHost) &&
(caddytls.QualifiesForManagedTLS(cfg) ||
certmagic.HostQualifies(cfg.Addr.Host)) {
atLeastOneSiteLooksLikeProduction = true
}
}
// make sure TLS is disabled for explicitly-HTTP sites
// (necessary when HTTP address shares a block containing tls)
if !cfg.TLS.Enabled {
continue
}
if cfg.Addr.Port == HTTPPort || cfg.Addr.Scheme == "http" {
if cfg.Addr.Port == httpPort || cfg.Addr.Scheme == "http" {
cfg.TLS.Enabled = false
log.Printf("[WARNING] TLS disabled for %s", cfg.Addr)
} else if cfg.Addr.Scheme == "" {
@@ -224,11 +282,22 @@ func (h *httpContext) MakeServers() ([]caddy.Server, error) {
// is incorrect for this site.
cfg.Addr.Scheme = "https"
}
if cfg.Addr.Port == "" && ((!cfg.TLS.Manual && !cfg.TLS.SelfSigned) || cfg.TLS.OnDemand) {
if cfg.Addr.Port == "" && ((!cfg.TLS.Manual && !cfg.TLS.SelfSigned) || cfg.TLS.Manager.OnDemand != nil) {
// this is vital, otherwise the function call below that
// sets the listener address will use the default port
// instead of 443 because it doesn't know about TLS.
cfg.Addr.Port = HTTPSPort
cfg.Addr.Port = httpsPort
}
if cfg.TLS.ClientAuth != tls.NoClientCert {
if QUIC {
return nil, fmt.Errorf("cannot enable TLS client authentication with QUIC, because QUIC does not yet support it")
}
// this must be enabled so that a client cannot connect
// using SNI for another site on this listener that
// does NOT require ClientAuth, and then send HTTP
// requests with the Host header of this site which DOES
// require client auth, thus bypassing it...
cfg.StrictHostMatching = true
}
}
@@ -248,6 +317,18 @@ func (h *httpContext) MakeServers() ([]caddy.Server, error) {
servers = append(servers, s)
}
// NOTE: This value is only a "good guess". Quite often, development
// environments will use internal DNS or a local hosts file to serve
// real-looking domains in local development. We can't easily tell
// which without doing a DNS lookup, so this guess is definitely naive,
// and if we ever want a better guess, we will have to do DNS lookups.
deploymentGuess := "dev"
if looksLikeProductionCA && atLeastOneSiteLooksLikeProduction {
deploymentGuess = "prod"
}
telemetry.Set("http_deployment_guess", deploymentGuess)
telemetry.Set("http_num_sites", len(h.siteConfigs))
return servers, nil
}
@@ -273,7 +354,11 @@ func GetConfig(c *caddy.Controller) *SiteConfig {
// we should only get here during tests because directive
// actions typically skip the server blocks where we make
// the configs
cfg := &SiteConfig{Root: Root, TLS: new(caddytls.Config), IndexPages: staticfiles.DefaultIndexPages}
cfg := &SiteConfig{
Root: Root,
TLS: &caddytls.Config{Manager: certmagic.NewDefault()},
IndexPages: staticfiles.DefaultIndexPages,
}
ctx.saveConfig(key, cfg)
return cfg
}
@@ -328,6 +413,8 @@ func groupSiteConfigsByListenAddr(configs []*SiteConfig) (map[string][]*SiteConf
// parts of an address. The component parts may be
// updated to the correct values as setup proceeds,
// but the original value should never be changed.
//
// The Host field must be in a normalized form.
type Address struct {
Original, Scheme, Host, Port, Path string
}
@@ -339,7 +426,7 @@ func (a Address) String() string {
}
scheme := a.Scheme
if scheme == "" {
if a.Port == HTTPSPort {
if a.Port == strconv.Itoa(certmagic.HTTPSPort) {
scheme = "https"
} else {
scheme = "http"
@@ -349,11 +436,12 @@ func (a Address) String() string {
if s != "" {
s += "://"
}
s += a.Host
if a.Port != "" &&
((scheme == "https" && a.Port != DefaultHTTPSPort) ||
(scheme == "http" && a.Port != DefaultHTTPPort)) {
s += ":" + a.Port
s += net.JoinHostPort(a.Host, a.Port)
} else {
s += a.Host
}
if a.Path != "" {
s += a.Path
@@ -376,10 +464,17 @@ func (a Address) Normalize() Address {
if !CaseSensitivePath {
path = strings.ToLower(path)
}
// ensure host is normalized if it's an IP address
host := a.Host
if ip := net.ParseIP(host); ip != nil {
host = ip.String()
}
return Address{
Original: a.Original,
Scheme: strings.ToLower(a.Scheme),
Host: strings.ToLower(a.Host),
Host: strings.ToLower(host),
Port: a.Port,
Path: path,
}
@@ -395,11 +490,10 @@ func (a Address) Key() string {
if a.Host != "" {
res += a.Host
}
if a.Port != "" {
if strings.HasPrefix(a.Original[len(res):], ":"+a.Port) {
// insert port only if the original has its own explicit port
res += ":" + a.Port
}
// insert port only if the original has its own explicit port
if a.Port != "" && len(a.Original) >= len(res) &&
strings.HasPrefix(a.Original[len(res):], ":"+a.Port) {
res += ":" + a.Port
}
if a.Path != "" {
res += a.Path
@@ -412,6 +506,18 @@ func (a Address) Key() string {
func standardizeAddress(str string) (Address, error) {
input := str
httpPort := strconv.Itoa(certmagic.HTTPPort)
httpsPort := strconv.Itoa(certmagic.HTTPSPort)
// As of Go 1.12.8 (Aug 2019), ports that are service names such
// as ":http" and ":https" are no longer parsed as they were
// before, which is a breaking change for us. Attempt to smooth
// this over for now by replacing those strings with their port
// equivalents. See
// https://github.com/golang/go/commit/3226f2d492963d361af9dfc6714ef141ba606713
str = strings.Replace(str, ":https", ":"+httpsPort, 1)
str = strings.Replace(str, ":http", ":"+httpPort, 1)
// Split input into components (prepend with // to assert host by default)
if !strings.Contains(str, "//") && !strings.HasPrefix(str, "/") {
str = "//" + str
@@ -433,32 +539,28 @@ func standardizeAddress(str string) (Address, error) {
// see if we can set port based off scheme
if port == "" {
if u.Scheme == "http" {
port = HTTPPort
port = httpPort
} else if u.Scheme == "https" {
port = HTTPSPort
port = httpsPort
}
}
// repeated or conflicting scheme is confusing, so error
if u.Scheme != "" && (port == "http" || port == "https") {
return Address{}, fmt.Errorf("[%s] scheme specified twice in address", input)
}
// error if scheme and port combination violate convention
if (u.Scheme == "http" && port == HTTPSPort) || (u.Scheme == "https" && port == HTTPPort) {
if (u.Scheme == "http" && port == httpsPort) || (u.Scheme == "https" && port == httpPort) {
return Address{}, fmt.Errorf("[%s] scheme and port violate convention", input)
}
// standardize http and https ports to their respective port numbers
if port == "http" {
u.Scheme = "http"
port = HTTPPort
} else if port == "https" {
u.Scheme = "https"
port = HTTPSPort
// (this behavior changed in Go 1.12.8)
if u.Scheme == "" {
if port == httpPort {
u.Scheme = "http"
} else if port == httpsPort {
u.Scheme = "https"
}
}
return Address{Original: input, Scheme: u.Scheme, Host: host, Port: port, Path: u.Path}, err
return Address{Original: input, Scheme: u.Scheme, Host: host, Port: port, Path: u.Path}, nil
}
// RegisterDevDirective splices name into the list of directives
@@ -534,6 +636,7 @@ var directives = []string{
"startup", // TODO: Deprecate this directive
"shutdown", // TODO: Deprecate this directive
"on",
"supervisor", // github.com/lucaslorentz/caddy-supervisor
"request_id",
"realip", // github.com/captncraig/caddy-realip
"git", // github.com/abiosoft/caddy-git
@@ -547,30 +650,33 @@ var directives = []string{
"cache", // github.com/nicolasazrak/caddy-cache
"rewrite",
"ext",
"minify", // github.com/hacdias/caddy-minify
"gzip",
"header",
"geoip", // github.com/kodnaplakal/caddy-geoip
"errors",
"authz", // github.com/casbin/caddy-authz
"filter", // github.com/echocat/caddy-filter
"minify", // github.com/hacdias/caddy-minify
"ipfilter", // github.com/pyed/ipfilter
"ratelimit", // github.com/xuqingfeng/caddy-rate-limit
"search", // github.com/pedronasser/caddy-search
"recaptcha", // github.com/defund/caddy-recaptcha
"expires", // github.com/epicagency/caddy-expires
"forwardproxy", // github.com/caddyserver/forwardproxy
"basicauth",
"redir",
"status",
"cors", // github.com/captncraig/cors/caddy
"nobots", // github.com/Xumeiquer/nobots
"cors", // github.com/captncraig/cors/caddy
"s3browser", // github.com/techknowlogick/caddy-s3browser
"nobots", // github.com/Xumeiquer/nobots
"mime",
"login", // github.com/tarent/loginsrv/caddy
"reauth", // github.com/freman/caddy-reauth
"jwt", // github.com/BTBurke/caddy-jwt
"jsonp", // github.com/pschlump/caddy-jsonp
"upload", // blitznote.com/src/caddy.upload
"multipass", // github.com/namsral/multipass/caddy
"login", // github.com/tarent/loginsrv/caddy
"reauth", // github.com/freman/caddy-reauth
"extauth", // github.com/BTBurke/caddy-extauth
"jwt", // github.com/BTBurke/caddy-jwt
"permission", // github.com/dhaavi/caddy-permission
"jsonp", // github.com/pschlump/caddy-jsonp
"upload", // blitznote.com/src/caddy.upload
"multipass", // github.com/namsral/multipass/caddy
"internal",
"pprof",
"expvar",
@@ -579,21 +685,22 @@ var directives = []string{
"prometheus", // github.com/miekg/caddy-prometheus
"templates",
"proxy",
"pubsub", // github.com/jung-kurt/caddy-pubsub
"fastcgi",
"cgi", // github.com/jung-kurt/caddy-cgi
"websocket",
"filemanager", // github.com/hacdias/filemanager/caddy/filemanager
"filebrowser", // github.com/filebrowser/caddy
"webdav", // github.com/hacdias/caddy-webdav
"markdown",
"browse",
"jekyll", // github.com/hacdias/filemanager/caddy/jekyll
"hugo", // github.com/hacdias/filemanager/caddy/hugo
"mailout", // github.com/SchumacherFM/mailout
"awses", // github.com/miquella/caddy-awses
"awslambda", // github.com/coopernurse/caddy-awslambda
"grpc", // github.com/pieterlouw/caddy-grpc
"gopkg", // github.com/zikes/gopkg
"restic", // github.com/restic/caddy
"wkd", // github.com/emersion/caddy-wkd
"dyndns", // github.com/linkonoid/caddy-dyndns
}
const (
@@ -629,10 +736,4 @@ var (
// QUIC indicates whether QUIC is enabled or not.
QUIC bool
// HTTPPort is the port to use for HTTP.
HTTPPort = DefaultHTTPPort
// HTTPSPort is the port to use for HTTPS.
HTTPSPort = DefaultHTTPSPort
)
+29 -13
View File
@@ -22,8 +22,8 @@ import (
"fmt"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyfile"
"github.com/caddyserver/caddy"
"github.com/caddyserver/caddy/caddyfile"
)
func TestStandardizeAddress(t *testing.T) {
@@ -43,12 +43,12 @@ func TestStandardizeAddress(t *testing.T) {
{`:`, "", "", "", "", false},
{`localhost:http`, "http", "localhost", "80", "", false},
{`localhost:https`, "https", "localhost", "443", "", false},
{`:http`, "http", "", "80", "", false},
{`:https`, "https", "", "443", "", false},
{`http://localhost:https`, "", "", "", "", true}, // conflict
{`http://localhost:http`, "", "", "", "", true}, // repeated scheme
{`http://localhost:443`, "", "", "", "", true}, // not conventional
{`https://localhost:80`, "", "", "", "", true}, // not conventional
{`:http`, "http", "", "80", "", false}, // as of Go 1.12.8, service name in port is no longer supported
{`:https`, "https", "", "443", "", false}, // as of Go 1.12.8, service name in port is no longer supported
{`http://localhost:https`, "", "", "", "", true}, // conflict
{`http://localhost:http`, "http", "localhost", "80", "", false}, // repeated scheme -- test adjusted for Go 1.12.8 (expect no error)
{`http://localhost:443`, "", "", "", "", true}, // not conventional
{`https://localhost:80`, "", "", "", "", true}, // not conventional
{`http://localhost`, "http", "localhost", "80", "", false},
{`https://localhost`, "https", "localhost", "443", "", false},
{`http://127.0.0.1`, "http", "127.0.0.1", "80", "", false},
@@ -58,8 +58,8 @@ func TestStandardizeAddress(t *testing.T) {
{`https://127.0.0.1:1234`, "https", "127.0.0.1", "1234", "", false},
{`http://[::1]:1234`, "http", "::1", "1234", "", false},
{``, "", "", "", "", false},
{`::1`, "", "::1", "", "", true},
{`localhost::`, "", "localhost::", "", "", true},
{`::1`, "", "::1", "", "", false}, // test adjusted for Go 1.12.8 (expect no error)
{`localhost::`, "", "localhost::", "", "", false}, // test adjusted for Go 1.12.8 (expect no error)
{`#$%@`, "", "", "", "", true},
{`host/path`, "", "host", "", "/path", false},
{`http://host/`, "http", "host", "80", "/", false},
@@ -67,7 +67,7 @@ func TestStandardizeAddress(t *testing.T) {
{`:1234/asdf`, "", "", "1234", "/asdf", false},
{`http://host/path`, "http", "host", "80", "/path", false},
{`https://host:443/path/foo`, "https", "host", "443", "/path/foo", false},
{`host:80/path`, "", "host", "80", "/path", false},
{`host:80/path`, "http", "host", "80", "/path", false}, // test adjusted for Go 1.12.8 (expect "http" scheme)
{`host:https/path`, "https", "host", "443", "/path", false},
{`/path`, "", "", "", "/path", false},
} {
@@ -212,6 +212,10 @@ func TestKeyNormalization(t *testing.T) {
orig string
res string
}{
{
orig: "http://host:1234/path",
res: "http://host:1234/path",
},
{
orig: "HTTP://A/ABCDEF",
res: "http://a/ABCDEF",
@@ -221,8 +225,20 @@ func TestKeyNormalization(t *testing.T) {
res: "a/ABCDEF",
},
{
orig: "A:2015/Port",
res: "a:2015/Port",
orig: "A:2015/Path",
res: "a:2015/Path",
},
{
orig: ":80",
res: "http://",
},
{
orig: ":443",
res: "https://",
},
{
orig: ":1234",
res: ":1234",
},
}
for _, item := range caseSensitiveData {
+1 -1
View File
@@ -217,7 +217,7 @@ func (rb *ResponseBuffer) ReadFrom(src io.Reader) (int64, error) {
// https://go-review.googlesource.com/c/22134#message-ff351762308fe05f6b72a487d6842e3988916486
buf := respBufPool.Get().([]byte)
n, err := io.CopyBuffer(rb.ResponseWriterWrapper, src, buf)
respBufPool.Put(buf) // defer'ing this slowed down benchmarks a smidgin, I think
respBufPool.Put(buf) // deferring this slowed down benchmarks a smidgin, I think
return n, err
}
return rb.Buffer.ReadFrom(src)
+1 -1
View File
@@ -44,7 +44,7 @@ func TestWrite(t *testing.T) {
responseTestString := "test"
recordRequest := NewResponseRecorder(w)
buf := []byte(responseTestString)
recordRequest.Write(buf)
_, _ = recordRequest.Write(buf)
if recordRequest.size != len(buf) {
t.Fatalf("Expected the bytes written counter to be %d, but instead found %d\n", len(buf), recordRequest.size)
}
+103 -13
View File
@@ -16,6 +16,10 @@ package httpserver
import (
"bytes"
"crypto/sha256"
"crypto/x509"
"encoding/pem"
"fmt"
"io"
"io/ioutil"
"net"
@@ -28,8 +32,8 @@ import (
"strings"
"time"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddytls"
"github.com/caddyserver/caddy"
"github.com/caddyserver/caddy/caddytls"
)
// requestReplacer is a strings.Replacer which is used to
@@ -243,6 +247,15 @@ func round(d, r time.Duration) time.Duration {
return d
}
// getPeerCert returns peer certificate
func (r *replacer) getPeerCert() *x509.Certificate {
if r.request.TLS != nil && len(r.request.TLS.PeerCertificates) > 0 {
return r.request.TLS.PeerCertificates[0]
}
return nil
}
// getSubstitution retrieves value from corresponding key
func (r *replacer) getSubstitution(key string) string {
// search custom replacements first
@@ -355,10 +368,14 @@ func (r *replacer) getSubstitution(key string) string {
return url.QueryEscape(r.request.URL.RequestURI())
case "{when}":
return now().Format(timeFormat)
case "{when_iso_local}":
return now().Format(timeFormatISO)
case "{when_iso}":
return now().UTC().Format(timeFormatISOUTC)
case "{when_unix}":
return strconv.FormatInt(now().Unix(), 10)
case "{when_unix_ms}":
return strconv.FormatInt(nanoToMilliseconds(now().UnixNano()), 10)
case "{file}":
_, file := path.Split(r.request.URL.Path)
return file
@@ -413,24 +430,92 @@ func (r *replacer) getSubstitution(key string) string {
return strconv.FormatInt(convertToMilliseconds(elapsedDuration), 10)
case "{tls_protocol}":
if r.request.TLS != nil {
for k, v := range caddytls.SupportedProtocols {
if v == r.request.TLS.Version {
return k
}
if name, err := caddytls.GetSupportedProtocolName(r.request.TLS.Version); err == nil {
return name
} else {
return "tls" // this should never happen, but guard in case
}
return "tls" // this should never happen, but guard in case
}
return r.emptyValue // because not using a secure channel
case "{tls_cipher}":
if r.request.TLS != nil {
for k, v := range caddytls.SupportedCiphersMap {
if v == r.request.TLS.CipherSuite {
return k
}
if name, err := caddytls.GetSupportedCipherName(r.request.TLS.CipherSuite); err == nil {
return name
} else {
return "UNKNOWN" // this should never happen, but guard in case
}
return "UNKNOWN" // this should never happen, but guard in case
}
return r.emptyValue
case "{tls_client_escaped_cert}":
cert := r.getPeerCert()
if cert != nil {
pemBlock := pem.Block{
Type: "CERTIFICATE",
Bytes: cert.Raw,
}
return url.QueryEscape(string(pem.EncodeToMemory(&pemBlock)))
}
return r.emptyValue
case "{tls_client_fingerprint}":
cert := r.getPeerCert()
if cert != nil {
return fmt.Sprintf("%x", sha256.Sum256(cert.Raw))
}
return r.emptyValue
case "{tls_client_i_dn}":
cert := r.getPeerCert()
if cert != nil {
return cert.Issuer.String()
}
return r.emptyValue
case "{tls_client_raw_cert}":
cert := r.getPeerCert()
if cert != nil {
return string(cert.Raw)
}
return r.emptyValue
case "{tls_client_s_dn}":
cert := r.getPeerCert()
if cert != nil {
return cert.Subject.String()
}
return r.emptyValue
case "{tls_client_serial}":
cert := r.getPeerCert()
if cert != nil {
return fmt.Sprintf("%x", cert.SerialNumber)
}
return r.emptyValue
case "{tls_client_v_end}":
cert := r.getPeerCert()
if cert != nil {
return cert.NotAfter.In(time.UTC).Format("Jan 02 15:04:05 2006 MST")
}
return r.emptyValue
case "{tls_client_v_remain}":
cert := r.getPeerCert()
if cert != nil {
now := time.Now().In(time.UTC)
days := int64(cert.NotAfter.Sub(now).Seconds() / 86400)
return strconv.FormatInt(days, 10)
}
return r.emptyValue
case "{tls_client_v_start}":
cert := r.getPeerCert()
if cert != nil {
return cert.NotBefore.Format("Jan 02 15:04:05 2006 MST")
}
return r.emptyValue
case "{server_port}":
_, port, err := net.SplitHostPort(r.request.Host)
if err != nil {
if r.request.TLS != nil {
return "443"
} else {
return "80"
}
}
return port
default:
// {labelN}
if strings.HasPrefix(key, "{label") {
@@ -450,9 +535,13 @@ func (r *replacer) getSubstitution(key string) string {
return r.emptyValue
}
func nanoToMilliseconds(d int64) int64 {
return d / 1e6
}
// convertToMilliseconds returns the number of milliseconds in the given duration
func convertToMilliseconds(d time.Duration) int64 {
return d.Nanoseconds() / 1e6
return nanoToMilliseconds(d.Nanoseconds())
}
// Set sets key to value in the r.customReplacements map.
@@ -462,6 +551,7 @@ func (r *replacer) Set(key, value string) {
const (
timeFormat = "02/Jan/2006:15:04:05 -0700"
timeFormatISO = "2006-01-02T15:04:05" // ISO 8601 with timezone to be assumed as local
timeFormatISOUTC = "2006-01-02T15:04:05Z" // ISO 8601 with timezone to be assumed as UTC
headerContentType = "Content-Type"
contentTypeJSON = "application/json"
+145 -5
View File
@@ -16,12 +16,21 @@ package httpserver
import (
"context"
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"os"
"strconv"
"strings"
"testing"
"time"
"github.com/caddyserver/caddy/caddytls"
)
func TestNewReplacer(t *testing.T) {
@@ -67,7 +76,7 @@ func TestReplace(t *testing.T) {
request.Header.Set("CustomAdd", "caddy")
request.Header.Set("Cookie", "foo=bar; taste=delicious")
// add some respons headers
// add some response headers
recordRequest.Header().Set("Custom", "CustomResponseHeader")
hostname, err := os.Hostname()
@@ -77,7 +86,8 @@ func TestReplace(t *testing.T) {
old := now
now = func() time.Time {
return time.Date(2006, 1, 2, 15, 4, 5, 02, time.FixedZone("hardcoded", -7))
// Note that the `-7` is seconds, not hours.
return time.Date(2006, 1, 2, 15, 4, 5, 99999999, time.FixedZone("hardcoded", -7))
}
defer func() {
now = old
@@ -92,7 +102,9 @@ func TestReplace(t *testing.T) {
{"The response status is {status}.", "The response status is 200."},
{"{when}", "02/Jan/2006:15:04:05 +0000"},
{"{when_iso}", "2006-01-02T15:04:12Z"},
{"{when_iso_local}", "2006-01-02T15:04:05"},
{"{when_unix}", "1136214252"},
{"{when_unix_ms}", "1136214252099"},
{"The Custom header is {>Custom}.", "The Custom header is foobarbaz."},
{"The CustomAdd header is {>CustomAdd}.", "The CustomAdd header is caddy."},
{"The Custom response header is {<Custom}.", "The Custom response header is CustomResponseHeader."},
@@ -115,6 +127,7 @@ func TestReplace(t *testing.T) {
{"{label1} {label2} {label3} {label4}", "localhost local - -"},
{"Label with missing number is {label} or {labelQQ}", "Label with missing number is - or -"},
{"\\{ 'hostname': '{hostname}' \\}", "{ 'hostname': '" + hostname + "' }"},
{"{server_port}", "80"},
}
for _, c := range testCases {
@@ -147,6 +160,130 @@ func TestReplace(t *testing.T) {
}
}
func TestCustomServerPort(t *testing.T) {
w := httptest.NewRecorder()
recordRequest := NewResponseRecorder(w)
reader := strings.NewReader(`{"username": "dennis"}`)
request, err := http.NewRequest("POST", "http://localhost.local:8000/?foo=bar", reader)
if err != nil {
t.Fatalf("Failed to make request: %v", err)
}
ctx := context.WithValue(request.Context(), OriginalURLCtxKey, *request.URL)
request = request.WithContext(ctx)
repl := NewReplacer(request, recordRequest, "-")
testCase := struct {
template string
expect string
}{
template: "{server_port}",
expect: "8000",
}
if expected, actual := testCase.expect, repl.Replace(testCase.template); expected != actual {
t.Errorf("for template '%s', expected '%s', got '%s'", testCase.template, expected, actual)
}
}
func TestTlsReplace(t *testing.T) {
w := httptest.NewRecorder()
recordRequest := NewResponseRecorder(w)
clientCertText := []byte(`-----BEGIN CERTIFICATE-----
MIIB9jCCAV+gAwIBAgIBAjANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDDA1DYWRk
eSBUZXN0IENBMB4XDTE4MDcyNDIxMzUwNVoXDTI4MDcyMTIxMzUwNVowHTEbMBkG
A1UEAwwSY2xpZW50LmxvY2FsZG9tYWluMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB
iQKBgQDFDEpzF0ew68teT3xDzcUxVFaTII+jXH1ftHXxxP4BEYBU4q90qzeKFneF
z83I0nC0WAQ45ZwHfhLMYHFzHPdxr6+jkvKPASf0J2v2HDJuTM1bHBbik5Ls5eq+
fVZDP8o/VHKSBKxNs8Goc2NTsr5b07QTIpkRStQK+RJALk4x9QIDAQABo0swSTAJ
BgNVHRMEAjAAMAsGA1UdDwQEAwIHgDAaBgNVHREEEzARgglsb2NhbGhvc3SHBH8A
AAEwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDQYJKoZIhvcNAQELBQADgYEANSjz2Sk+
eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV
3Q9fgDkiUod+uIK0IynzIKvw+Cjg+3nx6NQ0IM0zo8c7v398RzB4apbXKZyeeqUH
9fNwfEi+OoXR6s+upSKobCmLGLGi9Na5s5g=
-----END CERTIFICATE-----`)
block, _ := pem.Decode(clientCertText)
if block == nil {
t.Fatalf("failed to decode PEM certificate")
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
t.Fatalf("failed to decode PEM certificate: %v", err)
}
request := &http.Request{
Method: "GET",
Host: "foo.com",
URL: &url.URL{
Scheme: "https",
Path: "/path/",
Host: "foo.com",
},
Header: http.Header{},
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
RemoteAddr: "192.0.2.1:1234",
RequestURI: "https://foo.com/path/",
TLS: &tls.ConnectionState{
Version: tls.VersionTLS12,
HandshakeComplete: true,
ServerName: "foo.com",
CipherSuite: tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
PeerCertificates: []*x509.Certificate{cert},
},
}
repl := NewReplacer(request, recordRequest, "-")
now := time.Now().In(time.UTC)
days := int64(cert.NotAfter.Sub(now).Seconds() / 86400)
pemBlock := pem.Block{
Type: "CERTIFICATE",
Bytes: cert.Raw,
}
protocol, _ := caddytls.GetSupportedProtocolName(request.TLS.Version)
cipher, _ := caddytls.GetSupportedCipherName(request.TLS.CipherSuite)
cEscapedCert := url.QueryEscape(string(pem.EncodeToMemory(&pemBlock)))
cFingerprint := fmt.Sprintf("%x", sha256.Sum256(cert.Raw))
cIDn := cert.Issuer.String()
cRawCert := string(cert.Raw)
cSDn := cert.Subject.String()
cSerial := fmt.Sprintf("%x", cert.SerialNumber)
cVEnd := cert.NotAfter.In(time.UTC).Format("Jan 02 15:04:05 2006 MST")
cVRemain := strconv.FormatInt(days, 10)
cVStart := cert.NotBefore.Format("Jan 02 15:04:05 2006 MST")
testCases := []struct {
template string
expect string
}{
{"{tls_protocol}", protocol},
{"{tls_cipher}", cipher},
{"{tls_client_escaped_cert}", cEscapedCert},
{"{tls_client_fingerprint}", cFingerprint},
{"{tls_client_i_dn}", cIDn},
{"{tls_client_raw_cert}", cRawCert},
{"{tls_client_s_dn}", cSDn},
{"{tls_client_serial}", cSerial},
{"{tls_client_v_end}", cVEnd},
{"{tls_client_v_remain}", cVRemain},
{"{tls_client_v_start}", cVStart},
{"{server_port}", "443"},
}
for _, c := range testCases {
if expected, actual := c.expect, repl.Replace(c.template); expected != actual {
t.Errorf("for template '%s', expected '%s', got '%s'", c.template, expected, actual)
}
}
}
func BenchmarkReplace(b *testing.B) {
w := httptest.NewRecorder()
recordRequest := NewResponseRecorder(w)
@@ -166,10 +303,11 @@ func BenchmarkReplace(b *testing.B) {
request.Header.Set("CustomAdd", "caddy")
request.Header.Set("Cookie", "foo=bar; taste=delicious")
// add some respons headers
// add some response headers
recordRequest.Header().Set("Custom", "CustomResponseHeader")
now = func() time.Time {
// Note that the `-7` is seconds, not hours.
return time.Date(2006, 1, 2, 15, 4, 5, 02, time.FixedZone("hardcoded", -7))
}
@@ -198,10 +336,11 @@ func BenchmarkReplaceEscaped(b *testing.B) {
request.Header.Set("CustomAdd", "caddy")
request.Header.Set("Cookie", "foo=bar; taste=delicious")
// add some respons headers
// add some response headers
recordRequest.Header().Set("Custom", "CustomResponseHeader")
now = func() time.Time {
// Note that the `-7` is seconds, not hours.
return time.Date(2006, 1, 2, 15, 4, 5, 02, time.FixedZone("hardcoded", -7))
}
@@ -228,6 +367,7 @@ func TestResponseRecorderNil(t *testing.T) {
old := now
now = func() time.Time {
// Note that the `-7` is seconds, not hours.
return time.Date(2006, 1, 2, 15, 4, 5, 02, time.FixedZone("hardcoded", -7))
}
defer func() {
@@ -333,7 +473,7 @@ func TestRound(t *testing.T) {
}
}
func TestMillisecondConverstion(t *testing.T) {
func TestMillisecondConversion(t *testing.T) {
var testCases = map[time.Duration]int64{
2 * time.Second: 2000,
9039492 * time.Nanosecond: 9,
+13 -8
View File
@@ -20,11 +20,12 @@ import (
"path/filepath"
"strconv"
"gopkg.in/natefinch/lumberjack.v2"
lumberjack "gopkg.in/natefinch/lumberjack.v2"
)
// LogRoller implements a type that provides a rolling logger.
type LogRoller struct {
Disabled bool
Filename string
MaxSize int
MaxAge int
@@ -66,10 +67,11 @@ func IsLogRollerSubdirective(subdir string) bool {
return subdir == directiveRotateSize ||
subdir == directiveRotateAge ||
subdir == directiveRotateKeep ||
subdir == directiveRotateCompress
subdir == directiveRotateCompress ||
subdir == directiveRotateDisable
}
var invalidRollerParameterErr = errors.New("invalid roller parameter")
var errInvalidRollParameter = errors.New("invalid roller parameter")
// ParseRoller parses roller contents out of c.
func ParseRoller(l *LogRoller, what string, where ...string) error {
@@ -79,16 +81,16 @@ func ParseRoller(l *LogRoller, what string, where ...string) error {
// rotate_compress doesn't accept any parameters.
// others only accept one parameter
if (what == directiveRotateCompress && len(where) != 0) ||
(what != directiveRotateCompress && len(where) != 1) {
return invalidRollerParameterErr
if ((what == directiveRotateCompress || what == directiveRotateDisable) && len(where) != 0) ||
((what != directiveRotateCompress && what != directiveRotateDisable) && len(where) != 1) {
return errInvalidRollParameter
}
var (
value int
err error
)
if what != directiveRotateCompress {
if what != directiveRotateCompress && what != directiveRotateDisable {
value, err = strconv.Atoi(where[0])
if err != nil {
return err
@@ -96,6 +98,8 @@ func ParseRoller(l *LogRoller, what string, where ...string) error {
}
switch what {
case directiveRotateDisable:
l.Disabled = true
case directiveRotateSize:
l.MaxSize = value
case directiveRotateAge:
@@ -127,6 +131,7 @@ const (
// defaultRotateKeep is 10 files.
defaultRotateKeep = 10
directiveRotateDisable = "rotate_disable"
directiveRotateSize = "rotate_size"
directiveRotateAge = "rotate_age"
directiveRotateKeep = "rotate_keep"
@@ -135,4 +140,4 @@ const (
// lumberjacks maps log filenames to the logger
// that is being used to keep them rolled/maintained.
var lumberjacks = make(map[string]*lumberjack.Logger)
var lumberjacks = make(map[string]io.Writer)
+102 -36
View File
@@ -29,21 +29,19 @@ import (
"path/filepath"
"runtime"
"strings"
"sync"
"time"
"github.com/lucas-clemente/quic-go/h2quic"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/staticfiles"
"github.com/mholt/caddy/caddytls"
"github.com/caddyserver/caddy"
"github.com/caddyserver/caddy/caddyhttp/staticfiles"
"github.com/caddyserver/caddy/caddytls"
"github.com/caddyserver/caddy/telemetry"
"github.com/lucas-clemente/quic-go/http3"
)
// Server is the HTTP server implementation.
type Server struct {
Server *http.Server
quicServer *h2quic.Server
listener net.Listener
listenerMu sync.Mutex
quicServer *http3.Server
sites []*SiteConfig
connTimeout time.Duration // max time to wait for a connection before force stop
tlsGovChan chan struct{} // close to stop the TLS maintenance goroutine
@@ -106,7 +104,7 @@ func NewServer(addr string, group []*SiteConfig) (*Server, error) {
if s.Server.TLSConfig != nil {
// enable QUIC if desired (requires HTTP/2)
if HTTP2 && QUIC {
s.quicServer = &h2quic.Server{Server: s.Server}
s.quicServer = &http3.Server{Server: s.Server}
s.Server.Handler = s.wrapWithSvcHeaders(s.Server.Handler)
}
@@ -236,7 +234,9 @@ func makeHTTPServerWithTimeouts(addr string, group []*SiteConfig) *http.Server {
func (s *Server) wrapWithSvcHeaders(previousHandler http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
s.quicServer.SetQuicHeaders(w.Header())
if err := s.quicServer.SetQuicHeaders(w.Header()); err != nil {
log.Println("[Error] failed to set proper headers for QUIC: ", err)
}
previousHandler.ServeHTTP(w, r)
}
}
@@ -245,7 +245,7 @@ func (s *Server) wrapWithSvcHeaders(previousHandler http.Handler) http.HandlerFu
// used to serve requests.
func (s *Server) Listen() (net.Listener, error) {
if s.Server == nil {
return nil, fmt.Errorf("Server field is nil")
return nil, fmt.Errorf("server field is nil")
}
ln, err := net.Listen("tcp", s.Server.Addr)
@@ -273,16 +273,26 @@ func (s *Server) Listen() (net.Listener, error) {
ln = tcpKeepAliveListener{TCPListener: tcpLn}
}
cln := s.WrapListener(ln)
// Very important to return a concrete caddy.Listener
// implementation for graceful restarts.
return cln.(caddy.Listener), nil
}
// WrapListener wraps ln in the listener middlewares configured
// for this server.
func (s *Server) WrapListener(ln net.Listener) net.Listener {
if ln == nil {
return nil
}
cln := ln.(caddy.Listener)
for _, site := range s.sites {
for _, m := range site.listenerMiddleware {
cln = m(cln)
}
}
// Very important to return a concrete caddy.Listener
// implementation for graceful restarts.
return cln.(caddy.Listener), nil
return cln
}
// ListenPacket creates udp connection for QUIC if it is enabled,
@@ -299,10 +309,6 @@ func (s *Server) ListenPacket() (net.PacketConn, error) {
// Serve serves requests on ln. It blocks until ln is closed.
func (s *Server) Serve(ln net.Listener) error {
s.listenerMu.Lock()
s.listener = ln
s.listenerMu.Unlock()
if s.Server.TLSConfig != nil {
// Create TLS listener - note that we do not replace s.listener
// with this TLS listener; tls.listener is unexported and does
@@ -318,11 +324,19 @@ func (s *Server) Serve(ln net.Listener) error {
s.tlsGovChan = caddytls.RotateSessionTicketKeys(s.Server.TLSConfig)
}
defer func() {
if s.quicServer != nil {
if err := s.quicServer.Close(); err != nil {
log.Println("[ERROR] failed to close QUIC server: ", err)
}
}
}()
err := s.Server.Serve(ln)
if s.quicServer != nil {
s.quicServer.Close()
if err != nil && err != http.ErrServerClosed {
return err
}
return err
return nil
}
// ServePacket serves QUIC requests on pc until it is closed.
@@ -345,6 +359,16 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
}()
// record the User-Agent string (with a cap on its length to mitigate attacks)
ua := r.Header.Get("User-Agent")
if len(ua) > 512 {
ua = ua[:512]
}
uaHash := telemetry.FastHash([]byte(ua)) // this is a normalized field
go telemetry.SetNested("http_user_agent", uaHash, ua)
go telemetry.AppendUnique("http_user_agent_count", uaHash)
go telemetry.Increment("http_request_count")
// copy the original, unchanged URL into the context
// so it can be referenced by middlewares
urlCopy := *r.URL
@@ -388,24 +412,26 @@ func (s *Server) serveHTTP(w http.ResponseWriter, r *http.Request) (int, error)
if vhost == nil {
// check for ACME challenge even if vhost is nil;
// could be a new host coming online soon
if caddytls.HTTPChallengeHandler(w, r, "localhost") {
// could be a new host coming online soon - choose any
// vhost's cert manager configuration, I guess
if len(s.sites) > 0 && s.sites[0].TLS.Manager.HandleHTTPChallenge(w, r) {
return 0, nil
}
// otherwise, log the error and write a message to the client
remoteHost, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
remoteHost = r.RemoteAddr
}
WriteSiteNotFound(w, r) // don't add headers outside of this function
WriteSiteNotFound(w, r) // don't add headers outside of this function (http.forwardproxy)
log.Printf("[INFO] %s - No such site at %s (Remote: %s, Referer: %s)",
hostname, s.Server.Addr, remoteHost, r.Header.Get("Referer"))
return 0, nil
}
// we still check for ACME challenge if the vhost exists,
// because we must apply its HTTP challenge config settings
if caddytls.HTTPChallengeHandler(w, r, vhost.ListenHost) {
// because the HTTP challenge might be disabled by its config
if vhost.TLS.Manager.HandleHTTPChallenge(w, r) {
return 0, nil
}
@@ -416,6 +442,18 @@ func (s *Server) serveHTTP(w http.ResponseWriter, r *http.Request) (int, error)
r.URL = trimPathPrefix(r.URL, pathPrefix)
}
// enforce strict host matching, which ensures that the SNI
// value (if any), matches the Host header; essential for
// sites that rely on TLS ClientAuth sharing a port with
// sites that do not - if mismatched, close the connection
if vhost.StrictHostMatching && r.TLS != nil &&
strings.ToLower(r.TLS.ServerName) != strings.ToLower(hostname) {
r.Close = true
log.Printf("[ERROR] %s - strict host matching: SNI (%s) and HTTP Host (%s) values differ",
vhost.Addr, r.TLS.ServerName, hostname)
return http.StatusForbidden, nil
}
return vhost.middlewareChain.ServeHTTP(w, r)
}
@@ -469,16 +507,34 @@ func (s *Server) Stop() error {
// OnStartupComplete lists the sites served by this server
// and any relevant information, assuming caddy.Quiet == false.
func (s *Server) OnStartupComplete() {
if caddy.Quiet {
return
if !caddy.Quiet {
firstSite := s.sites[0]
scheme := "HTTP"
if firstSite.TLS.Enabled {
scheme = "HTTPS"
}
fmt.Println("")
fmt.Printf("Serving %s on port "+firstSite.Port()+" \n", scheme)
s.outputSiteInfo(false)
fmt.Println("")
}
// Print out process log without header comment
s.outputSiteInfo(true)
}
func (s *Server) outputSiteInfo(isProcessLog bool) {
for _, site := range s.sites {
output := site.Addr.String()
if caddy.IsLoopback(s.Address()) && !caddy.IsLoopback(site.Addr.Host) {
output += " (only accessible on this machine)"
}
fmt.Println(output)
log.Println(output)
if isProcessLog {
log.Printf("[INFO] Serving %s \n", output)
} else {
fmt.Println(output)
}
}
}
@@ -498,13 +554,21 @@ type tcpKeepAliveListener struct {
}
// Accept accepts the connection with a keep-alive enabled.
func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
func (ln tcpKeepAliveListener) Accept() (net.Conn, error) {
tc, err := ln.AcceptTCP()
if err != nil {
return
return nil, err
}
if err = tc.SetKeepAlive(true); err != nil {
return nil, err
}
// OpenBSD has no user-settable per-socket TCP keepalive
// https://github.com/caddyserver/caddy/pull/2787
if runtime.GOOS != "openbsd" {
if err = tc.SetKeepAlivePeriod(3 * time.Minute); err != nil {
return nil, err
}
}
tc.SetKeepAlive(true)
tc.SetKeepAlivePeriod(3 * time.Minute)
return tc, nil
}
@@ -542,7 +606,9 @@ func WriteTextResponse(w http.ResponseWriter, status int, body string) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.WriteHeader(status)
w.Write([]byte(body))
if _, err := w.Write([]byte(body)); err != nil {
log.Println("[Error] failed to write body: ", err)
}
}
// SafePath joins siteRoot and reqPath and converts it to a path that can
+12 -2
View File
@@ -17,7 +17,7 @@ package httpserver
import (
"time"
"github.com/mholt/caddy/caddytls"
"github.com/caddyserver/caddy/caddytls"
)
// SiteConfig contains information about a site
@@ -36,6 +36,16 @@ type SiteConfig struct {
// TLS configuration
TLS *caddytls.Config
// If true, the Host header in the HTTP request must
// match the SNI value in the TLS handshake (if any).
// This should be enabled whenever a site relies on
// TLS client authentication, for example; or any time
// you want to enforce that THIS site's TLS config
// is used and not the TLS config of any other site
// on the same listener. TODO: Check how relevant this
// is with TLS 1.3.
StrictHostMatching bool
// Uncompiled middleware stack
middleware []Middleware
@@ -76,7 +86,7 @@ type SiteConfig struct {
}
// Timeouts specify various timeouts for a server to use.
// If the assocated bool field is true, then the duration
// If the associated bool field is true, then the duration
// value should be treated literally (i.e. a zero-value
// duration would mean "no timeout"). If false, the duration
// was left unset, so a zero-value duration would mean to
+17 -2
View File
@@ -19,11 +19,13 @@ import (
"crypto/rand"
"fmt"
"io/ioutil"
"log"
mathrand "math/rand"
"net"
"net/http"
"net/url"
"path"
"strconv"
"strings"
"sync"
"text/template"
@@ -31,6 +33,8 @@ import (
"os"
"github.com/caddyserver/caddy/caddytls"
"github.com/mholt/certmagic"
"github.com/russross/blackfriday"
)
@@ -176,7 +180,7 @@ func (c Context) Port() (string, error) {
if err != nil {
if !strings.Contains(c.Req.Host, ":") {
// common with sites served on the default port 80
return HTTPPort, nil
return strconv.Itoa(certmagic.HTTPPort), nil
}
return "", err
}
@@ -419,7 +423,9 @@ func (c Context) RandomString(minLen, maxLen int) string {
// secureRandomBytes returns a number of bytes using crypto/rand.
secureRandomBytes := func(numBytes int) []byte {
randomBytes := make([]byte, numBytes)
rand.Read(randomBytes)
if _, err := rand.Read(randomBytes); err != nil {
log.Println("[ERROR] failed to read bytes: ", err)
}
return randomBytes
}
@@ -448,6 +454,15 @@ func (c Context) AddLink(link string) string {
return ""
}
// Returns either TLS protocol version if TLS used or empty string otherwise
func (c Context) TLSVersion() (ret string) {
if c.Req.TLS != nil {
// Safe to ignore an error
ret, _ = caddytls.GetSupportedProtocolName(c.Req.TLS.Version)
}
return
}
// buffer pool for .Include context actions
var includeBufs = sync.Pool{
New: func() interface{} {
+41 -3
View File
@@ -16,6 +16,7 @@ package httpserver
import (
"bytes"
"crypto/tls"
"fmt"
"io/ioutil"
"net"
@@ -100,7 +101,7 @@ func TestInclude(t *testing.T) {
for i, test := range tests {
testPrefix := getTestPrefix(i)
// WriteFile truncates the contentt
// WriteFile truncates the content
err := ioutil.WriteFile(absInFilePath, []byte(test.fileContent), os.ModePerm)
if err != nil {
t.Fatal(testPrefix+"Failed to create test file. Error was: %v", err)
@@ -161,7 +162,7 @@ func TestMarkdown(t *testing.T) {
for i, test := range tests {
testPrefix := getTestPrefix(i)
// WriteFile truncates the contentt
// WriteFile truncates the content
err := ioutil.WriteFile(absInFilePath, []byte(test.fileContent), os.ModePerm)
if err != nil {
t.Fatal(testPrefix+"Failed to create test file. Error was: %v", err)
@@ -277,7 +278,7 @@ func TestHostname(t *testing.T) {
// // Test 3 - ipv6 without port and brackets
// {"2001:4860:4860::8888", "google-public-dns-a.google.com."},
// Test 4 - no hostname available
{"1.1.1.1", "1.1.1.1"},
{"0.0.0.0", "0.0.0.0"},
}
for i, test := range tests {
@@ -922,3 +923,40 @@ func TestAddLink(t *testing.T) {
})
}
}
func TestTlsVersion(t *testing.T) {
for _, test := range []struct {
tlsState *tls.ConnectionState
expectedResult string
}{
{
&tls.ConnectionState{Version: tls.VersionTLS10},
"tls1.0",
},
{
&tls.ConnectionState{Version: tls.VersionTLS11},
"tls1.1",
},
{
&tls.ConnectionState{Version: tls.VersionTLS12},
"tls1.2",
},
// TLS not used
{
nil,
"",
},
// Unsupported version
{
&tls.ConnectionState{Version: 0x0399},
"",
},
} {
context := getContextOrFail(t)
context.Req.TLS = test.tlsState
result := context.TLSVersion()
if result != test.expectedResult {
t.Errorf("Expected %s got %s", test.expectedResult, result)
}
}
}
+6 -1
View File
@@ -32,7 +32,12 @@ type vhostTrie struct {
// newVHostTrie returns a new vhostTrie.
func newVHostTrie() *vhostTrie {
return &vhostTrie{edges: make(map[string]*vhostTrie), fallbackHosts: []string{"0.0.0.0", ""}}
// TODO: fallbackHosts doesn't discriminate between network interfaces;
// i.e. if there is a host "0.0.0.0", it could match a request coming
// in to "[::1]" (and vice-versa) even though the IP versions differ.
// This might be OK, or maybe it's not desirable. The 'bind' directive
// can be used to restrict what interface a listener binds to.
return &vhostTrie{edges: make(map[string]*vhostTrie), fallbackHosts: []string{"0.0.0.0", "[::]", ""}}
}
// Insert adds stack to t keyed by key. The key should be
+7 -2
View File
@@ -15,6 +15,7 @@
package httpserver
import (
"log"
"net/http"
"net/http/httptest"
"testing"
@@ -103,7 +104,9 @@ func populateTestTrie(trie *vhostTrie, keys []string) {
func(key string) {
site := &SiteConfig{
middlewareChain: HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
w.Write([]byte(key))
if _, err := w.Write([]byte(key)); err != nil {
log.Println("[ERROR] failed to write bytes: ", err)
}
return 0, nil
}),
}
@@ -139,7 +142,9 @@ func assertTestTrie(t *testing.T, trie *vhostTrie, tests []vhostTrieTest, hasWil
// And it must be the correct value
resp := httptest.NewRecorder()
site.middlewareChain.ServeHTTP(resp, nil)
if _, err := site.middlewareChain.ServeHTTP(resp, nil); err != nil {
log.Println("[ERROR] failed to serve HTTP: ", err)
}
actualHandlerKey := resp.Body.String()
if actualHandlerKey != test.expectedKey {
t.Errorf("Test %d: Expected match '%s' but matched '%s'",
+2 -2
View File
@@ -15,8 +15,8 @@
package index
import (
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
)
func init() {
+3 -3
View File
@@ -17,9 +17,9 @@ package index
import (
"testing"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/mholt/caddy/caddyhttp/staticfiles"
"github.com/caddyserver/caddy"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy/caddyhttp/staticfiles"
)
func TestIndexIncompleteParams(t *testing.T) {
+1 -1
View File
@@ -23,7 +23,7 @@ package internalsrv
import (
"net/http"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
)
// Internal middleware protects internal locations from external requests -
+5 -2
View File
@@ -16,13 +16,14 @@ package internalsrv
import (
"fmt"
"log"
"net/http"
"net/http/httptest"
"testing"
"strconv"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
)
const (
@@ -126,7 +127,9 @@ func internalTestHandlerFunc(w http.ResponseWriter, r *http.Request) (int, error
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", contentTypeOctetStream)
w.Header().Set("Content-Length", strconv.Itoa(len(internalProtectedData)))
w.Write([]byte(internalProtectedData))
if _, err := w.Write([]byte(internalProtectedData)); err != nil {
log.Println("[ERROR] failed to write bytes: ", err)
}
return 0, nil
}
+8 -3
View File
@@ -15,8 +15,8 @@
package internalsrv
import (
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
)
func init() {
@@ -33,7 +33,12 @@ func setup(c *caddy.Controller) error {
return err
}
httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
// Append Internal paths to Caddy config HiddenFiles to ensure
// files do not appear in Browse
config := httpserver.GetConfig(c)
config.HiddenFiles = append(config.HiddenFiles, paths...)
config.AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
return Internal{Next: next, Paths: paths}
})
+2 -2
View File
@@ -17,8 +17,8 @@ package internalsrv
import (
"testing"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
)
func TestSetup(t *testing.T) {
+1 -1
View File
@@ -18,7 +18,7 @@ import (
"io"
"net/http"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
)
// Limit is a middleware to control request body size
+5 -2
View File
@@ -16,12 +16,13 @@ package limits
import (
"io/ioutil"
"log"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
)
func TestBodySizeLimit(t *testing.T) {
@@ -39,7 +40,9 @@ func TestBodySizeLimit(t *testing.T) {
}
r := httptest.NewRequest("GET", "/", strings.NewReader(expectContent+expectContent))
l.ServeHTTP(httptest.NewRecorder(), r)
if _, err := l.ServeHTTP(httptest.NewRecorder(), r); err != nil {
log.Println("[ERROR] failed to serve HTTP: ", err)
}
if got := string(gotContent); got != expectContent {
t.Errorf("expected content[%s], got[%s]", expectContent, got)
}
+2 -2
View File
@@ -20,8 +20,8 @@ import (
"strconv"
"strings"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
)
const (
+2 -2
View File
@@ -18,8 +18,8 @@ import (
"reflect"
"testing"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
)
const (
+2 -2
View File
@@ -20,8 +20,8 @@ import (
"net"
"net/http"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
)
func init() {
+8 -3
View File
@@ -17,12 +17,13 @@ package log
import (
"bytes"
"io/ioutil"
"log"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
)
type erroringMiddleware struct{}
@@ -89,7 +90,9 @@ func TestLogRequestBody(t *testing.T) {
}},
Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
// drain up body
ioutil.ReadAll(r.Body)
if _, err := ioutil.ReadAll(r.Body); err != nil {
log.Println("[ERROR] failed to read request body: ", err)
}
return 0, nil
}),
}
@@ -153,7 +156,9 @@ func TestMultiEntries(t *testing.T) {
}},
Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
// drain up body
ioutil.ReadAll(r.Body)
if _, err := ioutil.ReadAll(r.Body); err != nil {
log.Println("[ERROR] failed to read request body: ", err)
}
return 0, nil
}),
}
+2 -2
View File
@@ -18,8 +18,8 @@ import (
"net"
"strings"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
)
// setup sets up the logging middleware.
+3 -3
View File
@@ -19,8 +19,8 @@ import (
"reflect"
"testing"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
)
func TestSetup(t *testing.T) {
@@ -336,7 +336,7 @@ func TestLogParse(t *testing.T) {
{`log access.log { rotate_size }`, true, nil},
{`log access.log { ipmask }`, true, nil},
{`log access.log { invalid_option 1 }`, true, nil},
{`log / acccess.log "{remote} - [{when}] "{method} {port}" {scheme} {mitm} "`, true, nil},
{`log / access.log "{remote} - [{when}] "{method} {port}" {scheme} {mitm} "`, true, nil},
}
for i, test := range tests {
c := caddy.NewTestController("http", test.inputLogRules)
+6 -3
View File
@@ -17,6 +17,7 @@
package markdown
import (
"log"
"net/http"
"os"
"path"
@@ -25,7 +26,7 @@ import (
"text/template"
"time"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
"github.com/russross/blackfriday"
)
@@ -142,7 +143,7 @@ func (md Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error
case err == nil: // nop
case os.IsPermission(err):
return http.StatusForbidden, err
case os.IsExist(err):
case os.IsNotExist(err):
return http.StatusNotFound, nil
default: // did we run out of FD?
return http.StatusInternalServerError, err
@@ -168,7 +169,9 @@ func (md Markdown) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error
w.Header().Set("Content-Length", strconv.Itoa(len(html)))
httpserver.SetLastModifiedHeader(w, lastModTime)
if r.Method == http.MethodGet {
w.Write(html)
if _, err := w.Write(html); err != nil {
log.Println("[ERROR] failed to write html response: ", err)
}
}
return http.StatusOK, nil
}
+2 -2
View File
@@ -23,8 +23,8 @@ import (
"testing"
"text/template"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
"github.com/russross/blackfriday"
)
+1 -1
View File
@@ -29,7 +29,7 @@ func (n *NoneParser) Type() string {
return "None"
}
// Init prepases and parses the metadata and markdown file
// Init preparses and parses the metadata and markdown file
func (n *NoneParser) Init(b *bytes.Buffer) bool {
m := make(map[string]interface{})
n.metadata = NewMetadata(m)
+3 -3
View File
@@ -19,9 +19,9 @@ import (
"io/ioutil"
"os"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/mholt/caddy/caddyhttp/markdown/metadata"
"github.com/mholt/caddy/caddyhttp/markdown/summary"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy/caddyhttp/markdown/metadata"
"github.com/caddyserver/caddy/caddyhttp/markdown/summary"
"github.com/russross/blackfriday"
)
+1 -1
View File
@@ -19,7 +19,7 @@ import (
"strings"
"testing"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/caddyserver/caddy/caddyhttp/httpserver"
)
func TestConfig_Markdown(t *testing.T) {

Some files were not shown because too many files have changed in this diff Show More