Compare commits

..

1461 Commits

Author SHA1 Message Date
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 f379bf3421 Version 0.10.12 2018-03-26 22:09:10 -06:00
Toby Allen 1896b420d8 log: 'except' subdirective to skip logging certain requests (#2028)
* proof of concept

* Initial implementation with debug code

* Tidy up debug code

* remove unneeded import

* removed extra line

* Move ShouldLog function to rule entry Logger type

* add tests for ShouldLog

* Added tests for log exceptions

* Fix logic

* fix govet fail for test

* Updates requested for code clarity

* Update requested for style

* log: Minor style tweaks to logic of log exceptions
2018-03-26 17:17:43 -06:00
Matthew Holt 1580169e2b vendor: Update quic-go 2018-03-25 22:37:41 -06:00
Matt Holt 95514da91b Merge pull request #2072 from mholt/acmev2
tls: Use ACMEv2 and support automatic wildcard certificates
2018-03-25 22:09:03 -06:00
Matthew Holt 18ff8748e7 caddytls: Default to Let's Encrypt's ACMEv2 production endpoint 2018-03-25 22:00:18 -06:00
Matthew Holt 2ed1dd6afc Merge branch 'master' into acmev2
# Conflicts:
#	caddyhttp/httpserver/replacer.go
#	caddyhttp/httpserver/replacer_test.go
2018-03-25 21:56:11 -06:00
Matthew Holt 8039a7127f telemetry: Remove a metric, clarify another, and fix tests 2018-03-25 21:50:07 -06:00
Denis a8dfa9f0b7 httpserver: CaseSensitivePath applied to paths in site keys (#2034)
* different cases in path make different keys

* Respect CaseSensitivePath variable when matching paths
2018-03-25 21:32:30 -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 Fay f1eaae9b0d httpserver: Rework Replacer loop to ignore escaped braces (#2075)
* httpserver.Replacer: Rework loop to ignore escaped placeholder braces

* Fix typo and ineffectual assignment to ret

* Remove redundant idxOffset declaration, simplify escape check

* Add benchmark tests for new Replacer code
2018-03-18 20:42:43 -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
elcore 2716e272c1 Purge event hooks after USR1 reload, fix #2044 (#2047)
* caddy: Purge event hooks after USR1 reload

* caddy: Remove event hook purge logging

* caddy: Remove deleteEventHook

* caddy: use old event hooks in case of an unsuccessful restart

* caddy: implement restoreEventHooks
2018-03-17 18:29:22 -06:00
David Somers ca34a3e1aa httpserver: Placeholders for tls_protocol and tls_cipher (#2062)
Also add SSL_PROTOCOL and SSL_CIPHER env vars for fastcgi.

* Implement placeholders for ssl_protocol and ssl_cipher

* gofmt

* goimports

* Housekeeping and implement as {tls_protocol} and {tls_cipher}
2018-03-17 17:27:10 -06:00
Toby Allen 3ee6d30659 httpserver: Fix #2038 (query string being lost from URI) (#2039) 2018-03-17 17:17:42 -06:00
Matthew Holt ef40659c70 Merge branch 'master' into acmev2 2018-03-17 17:05:30 -06:00
Matt Holt 6e2de19d9f tls: Fall back to certificate keyed by empty name (fixes #2035) (#2037)
* tls: Fall back to certificate keyed by empty name (fixes #2035)

This should only happen for sites defined with an empty hostname (like
":8080") and which are using self-signed certificates or some other
funky self-managed certificate. But that certificate should arguably
be used for all incoming SNI names.

* tls: Revert to serving any certificate if no match, regardless of SNI

Also fix self-signed certs to include IP addresses in their name
if they are configured to serve an IP address

* Remove tests which are now irrelevant (behavior reverted)

It would be good to revisit this in the future.
2018-03-17 17:03:12 -06:00
Matthew Holt 3afb1ae380 Merge branch 'master' into acmev2 2018-03-17 11:30:21 -06:00
Matthew Holt 37c852c382 tls: Add 'wildcard' subdirective to force wildcard certificate
Should only be used when many sites are defined in the Caddyfile, and
you would run up against Let's Encrypt rate limits without a wildcard.
2018-03-17 11:29:19 -06:00
Matthew Holt 3d01f46efa Dangit, goimports imported the wrong acme package 2018-03-15 19:38:29 -06:00
Matthew Holt 3a6496c268 tls: Support distributed solving of the HTTP-01 challenge
Caddy can now obtain certificates when behind load balancers and/or in
fleet/cluster configurations, without needing any extra configuration.
The only requirement is sharing the same $CADDYPATH/acme folder.
This works with the HTTP challenge, whereas before the DNS challenge
was required. This commit allows one Caddy instance to initiate the
HTTP challenge and another to complete it.

When sharing that folder, certificate management is synchronized and
coordinated, without the Caddy instances needing to know about each
other. No load balancer reconfiguration should be required, either.

Currently, this is only supported when using FileStorage for TLS
storage (which is ~99.999% of users).
2018-03-15 19:30:45 -06:00
Andrey Blinov 64c9f20919 httpserver: Add geoip directive (closes #1819) (#2066)
* Add Geoip plugin to httpserver/plugin.go

* Move GeoIP plugin higher
2018-03-15 07:30:25 -06:00
Matthew Holt d10d8c23c4 httpserver: Add a couple test cases for the Replacer on {labelN} 2018-03-14 22:11:13 -06:00
Matthew Holt 3cd36fd47d tls: Replace '*' with 'wildcard_' in OCSP staple filenames (fix #2071)
Windows doesn't allow asterisk in file names, sigh...
2018-03-14 21:58:59 -06:00
Matthew Holt aaec7e469c httpserver: Add {labelN} placeholders for parts of hostnames
For example, {label1} would match "sub" in "sub.example.com" or whatever
value is in the wildcard spot of "*.example.com". Useful for rewrite!
2018-03-14 21:57:25 -06:00
Matthew Holt 6f78cc49d1 tls: Initial transition to ACMEv2 and support automatic wildcard certs
- Using xenolf/lego's likely-temporary acmev2 branch
- Cleaned up vendor folder a little bit (probably more to do)
- Temporarily set default CA URL to v2 staging endpoint
- Refactored user management a bit; updated tests (biggest change is
  how we get the email address, which now requires being able to make
  an ACME client with a User with a private key so that we can get the
  current ToS URL)
- Automatic HTTPS now allows specific wildcard pattern hostnames
- Commented out (but kept) the TLS-SNI code, as the challenge type
  may return in the future in a similar form
2018-03-14 21:44:08 -06:00
Chris Werner Rau 13dfffd203 tls: Change default tls minimum version to 1.2 (#2053) 2018-03-10 08:39:07 -07:00
elcore 5552dcbbc7 startup/shutdown: Remove deprecated startup/shutdown directives (#2033)
* caddy: Remove deprecated startup/shutdown directives

* caddyhttp: Remove deprecated startup/shutdown directives

Users should use 'on startup' and 'on shutdown' instead.
2018-02-21 10:56:09 -07:00
Matthew Holt 37b291f82c tls: Avoid nil pointer deref when parsing corrupt OCSP staple files
Fixes #2041
2018-02-21 10:53:12 -07:00
Matthew Holt d3f338ddab Update for version 0.10.11 2018-02-19 22:24:07 -07:00
Toby Allen 3b66865da5 httpserver: Placeholder for response header fields (#2029)
* Allow Response Headers in logs

* Remove log line

* remove unneeded log import

* Check if rr is nil.  Added test to check

* merge if statements

* remove temp file
2018-02-18 14:21:06 -07:00
Matthew Holt 637b0b47ee basicauth: Make test pass with Go 1.10 2018-02-18 00:13:11 -07: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
Amos Ng 1201492222 vendor: Updated quic-go for QUIC 39+ (#1968)
* Updated lucas-clemente/quic-go for QUIC 39+ support

* Update quic-go to latest
2018-02-16 22:29:53 -07:00
Toby Allen faa5248d1f httpserver: Leave %2f encoded when trimming path in site address Fix #1927 (#2014)
* Trim path prefix using EscapedPath()

* clarify comments

* Added Tests for trimPathPrefix

* Ensure path with trailing slash is properly trimmed

* Updated tests to match prepatch behaviour

* Updated tests to match prepatch behaviour

* call parse on url rather than instance

* add additional tests

* return unmodified url if error.  Additional tests
2018-02-16 14:18:02 -07:00
Matt Holt 986d4ffe3d Merge pull request #2015 from mholt/cert-cache
tls: Restructure and improve certificate management
2018-02-16 12:46:27 -07:00
Matthew Holt a03eba6fbc tls: In HTTP->HTTPS redirects, preserve redir port in some circumstances
Only strip the port from the Location URL value if the port is NOT the
HTTPSPort (before, we compared against DefaultHTTPSPort instead of
HTTPSPort). The HTTPSPort can be changed, but is done so for port
forwarding, since in reality you can't 'change' the standard HTTPS port,
you can only forward it.
2018-02-16 12:36:28 -07:00
Matthew Holt 8db80c4a88 tls: Fix HTTP->HTTPS redirects and HTTP challenge when using custom port 2018-02-16 12:05:34 -07:00
Toby Allen 4704a56a17 Merge branch 'master' into cert-cache 2018-02-15 20:17:46 +00:00
Matthew Holt 896dc6bc69 tls: Try empty name if no matches for getting config during handshake
See discussion on #2015; the initial change had removed this check, and
I can't remember why I removed it or if it was accidental. Anyway, it's
back now.
2018-02-15 08:48:05 -07:00
Jason Daly 6f4cf7eec7 readme: Update minimum version to build from source (#2024)
Re: #2009, 1.9 or newer is needed because of the introduction of `sync.Map`
2018-02-15 08:05:58 -07:00
Matthew Holt be96cc0e65 httpserver: Raise error when adjusted site addresses clash at startup
See discussion on #2015 for how this situation was discovered. For a
Caddyfile like this:

	localhost {
		...
	}
	:2015 {
		...
	}

Running Caddy like this:

	caddy -host localhost

Produces two sites both defined as `localhost:2015` because the flag
changes the default host value to be `localhost`. This should be an
error since the sites are not distinct and it is confusing. It can also
cause issues with TLS handshakes loading the wrong cert, as the linked
discussion shows.
2018-02-15 00:04:31 -07:00
Matthew Holt ef585ed810 tls: Ensure parent dir exists before creating lock file 2018-02-14 13:32:16 -07:00
Matthew Holt 4b2e22289d sigtrap: Ensure cleanup actions happen before too many things go wrong 2018-02-13 13:27:08 -07:00
Matthew Holt f26447e2fb Merge branch 'master' into cert-cache
# Conflicts:
#	sigtrap_posix.go
2018-02-13 13:25:29 -07:00
Matthew Holt 08028714b5 tls: Synchronize renewals between Caddy instances sharing file storage
Also introduce caddy.OnProcessExit which is a list of functions that
run before exiting the process cleanly; these do not count as shutdown
callbacks, so they do not return errors and must execute quickly.
2018-02-13 13:23:09 -07:00
Matthew Holt 2de4950015 Merge branch 'master' of ssh://github.com/mholt/caddy 2018-02-13 09:30:48 -07:00
Matthew Holt d29640699e readme: Update logo image 2018-02-13 09:30:26 -07:00
Etienne Bruines 6a9aea04b1 fastcig: GET requests send along the body (#1975)
Fixes #1961

According to RFC 7231 and RFC 7230, there's
no reason a GET-Request can't have a body
(other than it possibly not being supported
by existing software). It's use is simply not
defined, and is left to the application.
2018-02-11 14:45:45 -07:00
Matthew Holt 592d199315 staticfiles: Prevent path-based open redirects
Not a huge issue, but has security implications if OAuth tokens leaked
2018-02-11 13:30:01 -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 fc2ff9155c tls: Restructure and improve certificate management
- Expose the list of Caddy instances through caddy.Instances()

- Added arbitrary storage to caddy.Instance

- The cache of loaded certificates is no longer global; now scoped
  per-instance, meaning upon reload (like SIGUSR1) the old cert cache
  will be discarded entirely, whereas before, aggressively reloading
  config that added and removed lots of sites would cause unnecessary
  build-up in the cache over time.

- Key certificates in the cache by their SHA-256 hash instead of
  by their names. This means certificates will not be duplicated in
  memory (within each instance), making Caddy much more memory-efficient
  for large-scale deployments with thousands of sites sharing certs.

- Perform name-to-certificate lookups scoped per caddytls.Config instead
  of a single global lookup. This prevents certificates from stepping on
  each other when they overlap in their names.

- Do not allow TLS configurations keyed by the same hostname to be
  different; this now throws an error.

- Updated relevant tests, with a stark awareness that more tests are
  needed.

- Change the NewContext function signature to include an *Instance.

- Strongly recommend (basically require) use of caddytls.NewConfig()
  to create a new *caddytls.Config, to ensure pointers to the instance
  certificate cache are initialized properly.

- Update the TLS-SNI challenge solver (even though TLS-SNI is disabled
  currently on the CA side). Store temporary challenge cert in instance
  cache, but do so directly by the ACME challenge name, not the hash.
  Modified the getCertificate function to check the cache directly for
  a name match if one isn't found otherwise. This will allow any
  caddytls.Config to be able to help solve a TLS-SNI challenge, with one
  extra side-effect that might actually be kind of interesting (and
  useless): clients could send a certificate's hash as the SNI and
  Caddy would be able to serve that certificate for the handshake.

- Do not attempt to match a "default" (random) certificate when SNI
  is present but unrecognized; return no certificate so a TLS alert
  happens instead.

- Store an Instance in the list of instances even while the instance
  is still starting up (this allows access to the cert cache for
  performing renewals at startup, etc). Will be removed from list again
  if instance startup fails.

- Laid groundwork for ACMEv2 and Let's Encrypt wildcard support.

Server type plugins will need to be updated slightly to accommodate
minor adjustments to their API (like passing in an Instance). This
commit includes the changes for the HTTP server.

Certain Caddyfile configurations might error out with this change, if
they configured different TLS settings for the same hostname.

This change trades some complexity for other complexity, but ultimately
this new complexity is more correct and robust than earlier logic.

Fixes #1991
Fixes #1994
Fixes #1303
2018-02-04 00:58:27 -07:00
Toby Allen a50f3a4cfe gitignore: Ignore .bat files (#2013) 2018-02-03 14:48:02 -07:00
magikstm fd3fafa50c Disable PrivateDevices in systemd as it doesn't work for some devices (#1990) 2018-02-03 11:13:23 -07:00
Phillipp Engelke e20779e405 Update README.md (#2004)
Adding the bash command for downloading the caddy.service file from the reposetory. Because it was easy to forget where you find it.
2018-02-02 23:53:40 -07:00
Tw fc6d62286e make eventHooks thread safe (Go 1.9) (#2009)
Signed-off-by: Tw <tw19881113@gmail.com>
2018-02-02 23:52:53 -07:00
Matthew Holt e2997ac974 request_id: Allow reusing ID from header (closes #2012) 2018-02-02 19:59:28 -07:00
Matthew Holt 8f0b44b8a4 Create diagnostics package; persist UUID 2018-02-02 19:15:28 -07:00
Michael Schubert 50ab4fe11e caddy.service: fix typo, s/retrict/restrict/ (#2008) 2018-01-30 07:19:02 -07:00
Matthew Holt 106d62b067 sigtrap: Fix log messages, and ignore SIGHUP (#1993) 2018-01-26 22:24:11 -07:00
Miek Gieben a76222f607 sigtrap: allow graceful shutdown for SIGTERM on posix (#1995)
* shutdown: allow graceful shutdown for SIGTERM on posix

The signal is already trapped; make it do the same thing as SIGQUIT to
be more inline with Unix/Linux shutdown expectations.

Fixes #1993

* Implement comment feedback ideas
2018-01-16 15:55:33 -07:00
Whitestrake e9515425e0 use import to handle globbed values for -conf flag (#1973) 2018-01-16 11:37:49 -07:00
Heri Sim c80c34ef45 proxy: Turn on KeepAlive in QuicConfig of RoundTripper (#1943)
* Turn on KeepAlive in QuicConfig of RoundTripper

* Update reverseproxy.go
2018-01-15 21:00:59 -07:00
Tw 1ba5512015 ResponseBuffer: add missing header writing (#1997)
Signed-off-by: Tw <tw19881113@gmail.com>
2018-01-15 18:32:19 -07:00
Tw 55a564df6d template: add extension filter test and simplify test code (#1996)
Signed-off-by: Tw <tw19881113@gmail.com>
2018-01-15 18:27:55 -07:00
Andreas Ulm 8a326d4dc1 implemented sourcing of default file for sysvinit (#1984)
* implemented source of default file for sysvinit

Signed-off-by: root360-AndreasUlm <andreas.ulm@root360.de>

* added documentation in README

Signed-off-by: root360-AndreasUlm <andreas.ulm@root360.de>

* fixed sourcing command for sh

Signed-off-by: root360-AndreasUlm <andreas.ulm@root360.de>

* implemented source of default file for sysvinit

Signed-off-by: root360-AndreasUlm <andreas.ulm@root360.de>

* added documentation in README

Signed-off-by: root360-AndreasUlm <andreas.ulm@root360.de>

* fixed sourcing command for sh

Signed-off-by: root360-AndreasUlm <andreas.ulm@root360.de>

* implemented DAEMONOPTS overwrite

Signed-off-by: root360-AndreasUlm <andreas.ulm@root360.de>
2018-01-15 18:22:53 -07:00
magikstm d35719daed browse: Correct 'modified' date alignment (#1954)
* Correct browse modified date alignment

* New solution to adjust alignment
2018-01-15 18:18:25 -07:00
detaoin c296d7e7e0 caddymain: fix setCPU silently ignoring small percent values (#1969)
* caddymain: fix setCPU silently ignoring small percent values

the percent value is resolved in a GOMAXPROCS relative number by simple
division, thus rounding down the non-integer quotient. If zero, the call
to runtime.GOMAXPROCS is silently ignored.

We decide here to exceptionally round up the CPU cap in case of percent
values that are too small.

* caddymain: gofmt -s
2018-01-15 18:17:27 -07:00
Sean Lane fc1509eed4 Update README.md (change to ownership command) (#1970)
* Update README.md

I believe the owner and group of the `chown` command here are mixed up. As it was caused a permissions issue, with the service being unable to read the directory.

* Update README.md

* Update README.md

Revert changes back to the original suggested changes
2018-01-15 18:15:17 -07:00
Toby Allen 9619fe224c add basicauth {user} to replacer (#1979) 2018-01-07 14:44:49 +00:00
Toby Allen c0efec52d9 Allow Masking of IP address in Logfile. (#1930)
* First working mask

* IP Mask working with defaults and empty

* add tests for ipmask

* Store Mask as setup, some tidying, cleaner flow

* Prevent mask from running when directive not present

* use custom replacement to store masked ip
2017-12-23 10:52:11 +00:00
magikstm a74320bf4c Add {user} placeholder to CommonLogFormat (#1953) 2017-12-17 09:13:41 +00:00
Craig Peterson 1125a236ea Merge pull request #1921 from mholt/macros
Caddyfile snippets
2017-11-13 12:56:48 -05:00
Craig Peterson 8658e189e1 Merge branch 'master' into macros 2017-11-13 12:45:23 -05:00
Aaron Taylor 9a22cda15d httpserver: give each req context a Replacer that preserves custom values (#1937)
This allows custom replacements to be defined in a way that propagates
throughout all plugins.
2017-11-07 10:10:03 -07:00
insomniac 169ab3acda Check for nil listener before printing address (#1946)
* Checking that a server listener is not nil before printing verbose information

* Improved readability of a loop
2017-11-07 10:08:54 -07:00
Mohammad Gufran 5f39cbef94 caddytls: Extract locker into an interface (#1942) 2017-11-06 09:43:41 -07:00
Mohammad Gufran 63fd264043 proxy: Add SRV support for proxy upstream (#1915)
* Simplify parseUpstream function

* Add SRV support for proxy upstream
2017-11-05 23:01:10 -07:00
Toby Allen 345b312e00 Merge branch 'master' into macros 2017-11-05 21:03:44 +00:00
Tw 5cca9cc18e markdown: only update template when file changed (#1909)
Signed-off-by: Tw <tw19881113@gmail.com>
2017-11-04 17:36:59 +00:00
Toby Allen 9ebc11d775 Merge branch 'master' into macros 2017-11-04 11:10:27 +00:00
Kevin Stock 689591ef01 tls: Add option for backend to approve on-demand cert (#1939)
This adds the ask sub-directive to tls that defines the URL of a backend HTTP service to be queried during the TLS handshake to determine if an on-demand TLS certificate should be acquired for incoming hostnames. When the ask sub-directive is defined, Caddy will query the URL for permission to acquire a cert by making a HTTP GET request to the URL including the requested domain in the query string. If the backend service returns a 2xx response Caddy will acquire a cert. Any other response code (including 3xx redirects) are be considered a rejection and the certificate will not be acquired.
2017-11-03 23:01:30 -06:00
Sayem Chowdhury 2782553231 readme: Update build instructions (#1916)
* Update build instruction

This changes add proper an easy instruction for building.

* Update README.md
2017-11-02 06:11:18 -06:00
Craig Peterson 4ec5522a33 Merge branch 'macros' of github.com:mholt/caddy into macros 2017-10-31 23:56:35 -04:00
Craig Peterson ad2956fd1d snippets now 2017-10-31 23:56:24 -04:00
frk 34a34c565d FreeBSD init: Remove unnecessary daemon -u option (#1924)
The rc.subr framework already takes care of substituting user. So, using
daemon's -u option is double user-substitution and fails if $caddy_user
is non-root.
2017-10-31 10:31:09 -06:00
Arthur Silva 74d4fd3c29 improve error checking (#1938) 2017-10-31 10:19:51 -06:00
Arthur Silva ac1f3bfaaa a few code improvements (#1936)
caddy.go:569: could be simplified

sigtrap_posix.go:87: value of inst is never used

upgrade.go:151: should omit nil check; len() for nil slices is defined as zero
2017-10-31 10:12:05 -06:00
Mohammed Al Sahaf f7a70266ed Implement per-site index (#1906) 2017-10-29 21:13:10 +00:00
elcore fc75527eb5 onevent/startupshutdown: run command once per server block (#1934) 2017-10-23 00:27:44 +02:00
Toby Allen e5d04f9a96 Change log output for startup and shutdown (#1932)
Changes the log output of on startup
2017-10-22 13:43:40 +01:00
Craig Peterson 91a60a8d25 Merge branch 'master' into macros 2017-10-20 10:32:21 -04:00
Craig Peterson 5c9fc3a473 Merge branch 'macros' of github.com:mholt/caddy into macros 2017-10-19 19:55:14 -04:00
Craig Peterson 02ac1f61c4 retrigger build 2017-10-19 19:54:15 -04:00
elcore 59a8ada4a8 Fix CI Tests (#1929) 2017-10-19 11:02:56 -06:00
Craig Peterson 1889049ef3 Merge branch 'master' into macros 2017-10-19 10:34:13 -04:00
Craig Peterson 68a495f144 actually return error on redeclaration 2017-10-19 10:27:10 -04:00
Matthew Holt a2db340378 tls: Final check of OCSP response validity date before stapling 2017-10-16 17:25:55 -06:00
Alex Gaynor c6a2911725 tls: Handle when OCSP responder cert expires before a response it issued (#1922)
* Handle the case of an OCSP responder certificate expiring before an OCSP response it issued

* oops

* doh, gofmt
2017-10-16 17:23:21 -06:00
Matthew Holt 654f26cb91 tls: Evict existing certificates from cache when loading ones from disk 2017-10-16 16:40:43 -06:00
Craig Peterson dd4b3efa47 remove 'macro foo' syntax 2017-10-15 19:10:56 -04:00
Craig Peterson 3a969bc075 add nil check 2017-10-13 11:08:17 -04:00
Craig Peterson 425f61142f initial implementation of caddyfile macros 2017-10-13 11:04:44 -04:00
Wèi Cōngruì 79072828a5 staticfiles: remove mapFSRootOpenErr because Go stdlib has fixed the relevant issue (#1919) 2017-10-13 08:01:30 -06:00
Eugene Dementiev 0548b97701 init: Fix upstart script for Centos6 (and Amazon Linux) (#1914) 2017-10-12 17:02:46 -06:00
Guilherme Bernal 99625ae3f6 on: Allow nonblocking command with no other arguments (#1913) 2017-10-12 10:11:50 -06:00
Matthew Holt c4dfbb9956 Update readme and changelog for v0.10.10 2017-10-08 22:20:05 -06:00
Matthew Holt b0d9c058cc Change CASE_SENSITIVE_PATH default to false
A default of true is risky when protecting assets by matching base path.
It's not obvious that protecting /foo/ will allow /Foo/ through, and if
accessing static files on a case-insensitive file system... that's no
good. So the default is now to be case-INsensitive when matching paths.
2017-10-08 22:19:35 -06:00
Matthew Holt cccfe3b4ef proxy: Allow insecure certificate in QUIC tests 2017-10-05 11:11:48 -06:00
Matthew Holt f71955e89c Grammar improvements 2017-10-04 18:37:11 -06:00
elcore dd44491e13 startupshutdown: gofmt code (#1902) 2017-10-03 07:18:29 -06:00
Mohammad Gufran ac865e8910 fastcgi: Add support for SRV upstreams (#1870) 2017-10-03 07:17:54 -06:00
elcore b7167803f2 startupshutdown: is an alias for 'on' (#1880) 2017-10-01 20:41:45 -06:00
Kevin Stock 97710ced7e Add hook for instance startup (#1888)
Provides a new hook for plugins as a means to provide the current caddy.Instance when starting or restarting.
2017-10-01 20:36:23 -06:00
elcore f878247a18 Implement CertRenewEvent (#1879) 2017-10-01 11:25:30 -06:00
elcore 118cf5f240 Implement 'http.on' plugin and replace UUID lib (#1864)
* Implement 'command' plugin

* Rename 'command' to 'on'

* Split this PR
2017-10-01 11:24:50 -06:00
Matthew Holt f9cba03d25 redir: Do not count multiple rules with if statements as duplicates
This allows you to have multiple redir directives conditioned solely
upon if statements, without regard to path.
2017-09-28 11:41:11 -06:00
Matthew Holt baf6db5b57 Apply Apache license to all .go source files (closes #1865)
I am not a lawyer, but according to the appendix of the license,
these boilerplate notices should be included with every source file.
2017-09-22 23:56:58 -06:00
Matthew Holt e60400a92e caddyfile: Use full, absolute file path in token structs (fixes #1892)
When two Caddyfiles with the same name, but different paths, are
imported, it can cause a weird bug because isNewLine() returned false
when it should return true, since the files are actually different,
but it couldn't know that because only the base name was stored,
not the whole path.
2017-09-22 20:02:48 -06:00
Tw e377eeff50 proxy: websocket proxy exits immediately if backend is shutdown (#1869)
Signed-off-by: Tw <tw19881113@gmail.com>
2017-09-22 18:10:48 -06:00
Matthew Holt 84a2f8e89e Add iOS 11 stable ClientHello to MITM test corpus (issue #1890) 2017-09-22 17:41:47 -06:00
Matthew Holt 64be3e410c websocket: Avoid multiple calls to WriteHeader if Upgrade fails 2017-09-22 17:39:18 -06:00
Matthew Holt 643dac688c Clarify unofficial builds in version string 2017-09-22 17:25:43 -06:00
Daniel van Dorp 0a624f87ff Merge pull request #1884 from timothywlewis/fix-pid-error-in-linux-sysvinit
Fix pid error in linux sysvinit
2017-09-22 16:45:55 +02:00
Tim Lewis fea8f37f9d Fix linux-sysvinit script to prevent missing caddy.log
Create /var/log/caddy.log and chown prior to starting caddy.
Caddy running as DAEMONUSER does not have permission to create the /var/log/caddy.log.
2017-09-18 19:16:15 -04:00
Tim Lewis a808252079 Fix spurious .pid file error in linux-sysvinit
This change eliminates the `[ERROR] Could not write pidfile: open /var/run/caddy.pid: permission denied` from caddy.log.
The start-stop-daemon writes the file as root so the DAEMONUSER that caddy runs as cannot write to the .pid file.
2017-09-18 19:14:56 -04:00
Davor Kapsa 93bcca0ccc travis: add 1.x instead 1.9 go version (#1868) 2017-09-16 09:48:27 -06:00
Fake ID d39b95600a readme: fixed build instructions (#1875) 2017-09-16 09:35:58 -06:00
Matthew Holt 545fa844bb EULA: Remove restriction clause related to sponsors header 2017-09-14 21:45:32 -06:00
Adam Williams b6e10e3cb2 Revert "Implement Caddy-Sponsors HTTP response header" (#1866)
This reverts commit 56453e9664.
2017-09-14 21:42:22 -06:00
Matthew Holt bc56793d3b Update readme and changes for version 0.10.9 2017-09-12 11:02:53 -06:00
Matthew Holt ad973f1d12 Merge branch 'sponsors-header' 2017-09-12 10:53:21 -06:00
Matthew Holt c06941ed52 proxy: Disable QUIC test outside CI environment (see #1782) 2017-09-11 23:34:39 -06:00
Matthew Holt 54c65cb025 templates: Properly propagate response status code (fixes #1841)
Benchmarks with wrk showed no noticeable performance impact
2017-09-11 23:25:41 -06:00
twdkeule 22b835b9f4 proxy: Support QUIC for upstream connections (#1782)
* Proxy can now use QUIC for upstream connections

Add HandshakeTimeout, change h2quic syntax

* Add setup and upstream test

Test QUIC proxy with actual h2quic instance

Use different port fo QUIC test server

Add quic host to CI config

Added testdata to vendor

Revert "Added testdata to vendor"

This reverts commit 959512282deed8623168d090e5ca5e5a7933019c.

* Use local testdata
2017-09-11 19:49:02 -06:00
Matthew Holt 46ae4a6652 tls: Remove expiring certificates from cache and load renewed ones
Renewed certificates would not be reloaded into the cache because their
names conflict with names of certificates already in the cache; this
was intentional when loading new certs to avoid confusion, but is
problematic when renewing, since the old certificate doesn't get
evicted from the cache. (Oops.)

Here, I remedy this situation by explicitly deleting the old cert from
the cache before adding the renewed one back in.
2017-09-11 12:37:42 -06:00
Matthew Holt 56453e9664 Implement Caddy-Sponsors HTTP response header
(See EULA.) Personally-licensed official Caddy builds cannot remove
this header by configuration. The commercially-licensed builds of Caddy
don't have this header.
2017-09-10 19:51:57 -06:00
Matthew Holt 3b144c21d0 Change build program to use new builds package 2017-09-10 14:09:57 -06:00
Matthew Holt 9e156e0940 Update readme/changes for v0.10.8 2017-09-08 11:06:39 -06:00
Matt Holt 65191eb5ae Merge pull request #1861 from mholt/fix1859
httpserver: Fix #1859 by cleaning paths when matching them
2017-09-08 11:04:09 -06:00
Matthew Holt f6d75bb79a httpserver: Fix #1859 by cleaning paths when matching them
Signed-off-by: Matthew Holt <mholt@users.noreply.github.com>
2017-09-08 07:19:52 -06:00
Matthew Holt f069a575cc Add EULA
The End-User License Agreement applies to official Caddy binaries;
the source code is still under the same open source license.
2017-09-06 19:03:53 -06:00
Matt Holt 32bb6a4cde Merge pull request #1856 from twdkeule/fix-index-push
Do not push index file when not in a rule
2017-09-06 06:59:55 -06:00
Fiisio a59bdd08ca fastcgi: use bytes.Contains and strconv.Itoa (#1857) 2017-09-06 06:33:48 -06:00
Thomas De Keulenaer b324a32b61 Do not push index file when not in a rule
+ test
2017-09-04 15:53:41 +02:00
John Chadwick 10484cfad2 fastcgi: Fix SCRIPT_NAME when path in address (#1852)
* Add tests for SCRIPT_NAME

* fastcgi: Include vhost path prefix in SCRIPT_NAME
2017-09-01 22:15:53 -06:00
Matthew Holt 129efde9b0 Support nacl compilation 2017-08-29 16:21:26 -06:00
Mattias Wadman a16a80ca52 Make filename column fill out space (#1848) 2017-08-29 23:04:36 +01:00
Mateusz Gajewski 6d7462ac99 push: Allow pushing multiple resources via Link header (#1798)
* Allow pushing multiple resources via Link header

* Add nopush test case

* Extract Link header parsing to separate function

* Parser regexp-free

* Remove dead code, thx gometalinter

* Redundant condition - won't happen

* Reduce duplication
2017-08-28 19:38:29 -06:00
Matthew Holt c0c7437fa5 caddytls: Fix data race in test (close #1844)
The race was in the test only; not in the production code
2017-08-28 19:21:17 -06:00
Matthew Holt 01f3593fd6 Update test case 2017-08-26 08:11:43 -06:00
Matthew Holt 4cce8c7b6b Rename parse errors to errors during parsing (#1838) 2017-08-26 07:27:59 -06:00
Matthew Holt 0d99751a2f Fix typos in changes file 2017-08-26 07:15:06 -06:00
Matthew Holt 0a31c32fb7 browse: Clarify test skip on Windows and log a message 2017-08-26 07:14:40 -06:00
Matthew Holt 0b4dda0aba Update readme for v0.10.7 2017-08-25 16:54:05 -06:00
Matt Holt c7868affe1 browse: Ignore one Test function on Windows (temporary) (#1839)
* browse: Attempt to fix tests on Windows

* browse: Make tests verbose for debugging

* Moar debugging

* Trying path.Join instead

* browse: Just skip the tests for now

* browse: Remove debug prints
2017-08-25 16:52:44 -06:00
Matthew Holt 74316fe01b Replace build.bash with build.go; limit timestamp inclusion
build.go is (should be) cross-platform compatible.

Timestamps are now excluded from all builds on a clean commit,
in an effort to be byte-for-byte reproducible.
2017-08-25 15:59:36 -06:00
Matthew Holt ef3d63e3e5 Update CI scripts for Go 1.9 2017-08-24 21:02:47 -06:00
Matt Holt 4b1b329edb templates: Execute template loaded by later middlewares (#1649)
* templates: Execute template loaded by later middlewares

This is the beginning of an attempt to make the staticfiles file server
the only middleware that hits the disk and loads content. This may have
unknown implications. But the goal is to reduce duplication without
sacrificing performance. (We now call ServeContent here.)

This change loses about 15% of the req/sec of the old way of doing it,
but this way is arguably more correct since the file server is good at
serving static files; duplicating that logic in every middleware that
needs to hit the disk is not practical.

* httpserver: Introduce ResponseRecorder as per Tw's suggestions

It implements io.ReaderFrom and has some allocation-reducing
optimizations baked into it

* templates: Increase execution speed by ~10-15% after perf regression

By using httpserver.ResponseBuffer, we can reduce allocations and still
get what we want. It's a little tricky but it works so far.
2017-08-24 07:13:53 -06:00
Matt Holt e49474a4f5 Merge pull request #1821 from mholt/ocspfix
tls: Fix OCSP stapling bug when certificate names overlap other certs
2017-08-23 12:26:01 -06:00
Matt Holt c026e2b734 Merge pull request #1825 from thejmazz/systemd-allow-more-threads
Double systemd LimitNPROC to allow more threads
2017-08-23 10:45:14 -06:00
Matthew Holt be36fec7ea vendor: Update quic-go 2017-08-23 10:32:08 -06:00
Matt Holt 49e98a1518 Merge pull request #1833 from mholt/add-forwardproxy
Add forwardproxy to directives' list
2017-08-18 11:43:51 -06:00
Sergey Frolov a7498bee68 Add forwardproxy to directives' list 2017-08-18 12:25:39 -04:00
Julian Mazzitelli 280ae833d4 Set LimitNPROC=512 for systemd 2017-08-14 19:25:08 -04:00
Matt Holt 261547b42c Merge pull request #1823 from klingtnet/systemd-restart-limit-fix
Fix restart restart behaviour of the systemd service
2017-08-13 09:46:26 -06:00
Andreas Linz 53ae9b8521 Increase restart rate limit
The previous setting caused the service to hit a rate-limit when it was
restarted more than 5 times in 24h.
Editing the Caddyfile and restarting the service could also easily
trigger this rate limit.
One could argue that users could simply call `systemctl reset-failed
caddy` to reset the rate-limit counter, but this is counterintuitive
because most users won't know this command and are possibly unaware that
they had hit a rate-limit.

The service is now allowed to restart 10 times in 10 seconds before
hitting a rate limit.
This should be conservative enough to rate limit quickly failing
services and to allow users to edit and test their caddy configuration.

This closes #1718

Remove restart limit settings and use defaults

By default 5 restarts within 10 seconds are allowed without
encountering a restart limit hit, see  `man systemd.unit` for details.

Set Restart to on-abnormal

The table in https://www.freedesktop.org/software/systemd/man/systemd.service.html#Restart=
shows the conditions for which on-abnormal would restart the service.
It will *not* restart the service in the following cases:

- a non-zero exit status, e.g. an invalid Caddyfile
- a zero exit code (or those specified in SuccessExitStatus=) and a clean signal
    clean signals are SIGHUP, SIGINT, SIGTERM or SIGPIPE
    https://github.com/systemd/systemd/blob/3536f49e8fa281539798a7bc5004d73302f39673/src/basic/exit-status.c#L205

The service *will be restarted* in the following cases:

- a unclean signal, e.g. SIGKILL
- on start and watchdog timeout (we don't use those systemd service
constructs explicitly)
2017-08-13 16:38:19 +02:00
Matt Holt 20fbc7303c Merge pull request #1796 from mholt/bugfix_rewrite_1794
Fix for #1794: Fixes issues with IfMatcher and regular expressions.
2017-08-12 15:17:28 -06:00
Matt Holt 6b546389b8 Merge pull request #1815 from wader/browse-abs-recursive-dir-symlink
browse: Support absolute and recursive directory symlinks
2017-08-12 12:19:25 -06:00
Matthew Holt ff56151931 Build tags for Caddy to build on nacl 2017-08-12 12:18:37 -06:00
Mattias Wadman 981f364845 browse: Support absolute and recursive directory symlinks 2017-08-12 19:29:43 +02:00
Matt Holt 5e0896305c SIGUSR2 triggers graceful binary upgrades (spawns new process) (#1814)
* SIGUSR2 triggers graceful binary upgrades (spawns new process)

* Move some functions around, hopefully fixing Windows build

* Clean up a couple file closes and add links to useful debugging thread

* Use two underscores in upgrade env var

To help ensure uniqueness / avoid possible collisions
2017-08-12 11:04:32 -06:00
Mark Severson d2fa8600fc httpserver: Add 'awses' plugin directive (#1818) 2017-08-12 09:28:53 -06:00
Henrique Dias ebce0b7aec httpserver: Add 'jekyll' plugin. (#1817) 2017-08-12 09:28:05 -06:00
Matthew Holt b699a17a1b tls: Fix OCSP stapling bug when certificate names overlap other certs
https://caddy.community/t/random-ocsp-response-errors-for-random-clients/2473?u=matt

Certificates are keyed by name in the cache, optimized for fast lookups
during TLS handshakes using SNI. A more "correct" way that is truly a
1:1 would be to cache certificates by a hash of the leaf's DER bytes,
but this involves an extra index to maintain. So instead of that, we
simply choose to prevent overlap when keying certificates by server
name. This avoids the ambiguity when updating OCSP staples, for instance.
2017-08-12 00:12:22 -06:00
Matthew Holt b5ec462299 internal: Allow use for only X-Accel-Redir (closes #1020)
(allow no arguments of paths to protect)
2017-08-09 10:36:54 -06:00
Matthew Holt 617988844b Enable compilation for Plan 9 (closes #1093) 2017-08-08 23:00:35 -06:00
Dusty Doris 4e52b3fe8a staticfiles: fix handling of filenames that end with index file names (#1812)
* static files ending with an index were redirected improperly

* optimize requestPage
2017-08-07 18:10:47 -06:00
Dhananjay Balan bd67ec99f0 freebsd init: typo in filename. (#1799) 2017-08-04 18:20:00 -06:00
Matthew Holt a7ed0cf69e Avoid panic on QUIC server close (fixes #1805) 2017-08-03 11:20:14 -06:00
Simon Lightfoot d48e51cb78 Changed IfCond to store the condition function and the compiled regular expression.
Updated ifCondition test to deep test all fields.
Changed NewComplexRule to not return a pointer.
Corrected panic detection in formatting.
Fixed failing test cases.
Fixed review bug for test.
Fixes bug caused by Replacer running on the regular expressions in IfMatcher. We also now compile regular expressions up front to detect errors.
Fixes rewrite bugs that come from formatting a rule as a string and failing with nil dereference caused by embedding Regexp pointer in a Rule. Re: Issue #1794
2017-08-03 11:59:30 +01:00
Matt Holt d3e5f9d456 Merge pull request #1800 from mholt/exitcodes
Distinguishable exit codes
2017-08-02 09:32:36 -06:00
Matthew Holt cbb85532a8 Distinguishable exit codes
0: normal or expected exit
1: error before server finished starting
2: double SIGINT (force quit)
3: error stopping with SIGQUIT
4: shutdown callback(s) returned error(s)
2017-08-01 23:41:24 -06:00
Matthew Holt 65bc696b0c Fix force quit using SIGINT
Only the outside function call is executed in a new goroutine when
invoking 'go'. Oops. Force quits (2 SIGINTs) now work again.
2017-07-31 17:43:37 -06:00
Matthew Holt e7f08bff38 Update changes and readme for v0.10.6 2017-07-28 12:21:09 -06:00
Matt Holt 16fa3ecb0f Merge pull request #1789 from abiosoft/fix-atomic-add
fastcgi: Fix for #1788. Align atomic fields to 64-bit word (Go bug)
2017-07-28 11:53:21 -06:00
Abiola Ibrahim dd3f460cf8 Fix for #1788. Rearrange struct fields. 2017-07-28 17:33:40 +01:00
Henrique Dias 36d8d2c7de Fix links on caddy-hugo and caddy-filemanager (#1787) 2017-07-28 09:19:00 -06:00
Matthew Holt c06ff1cb37 Update changes and readme for version 0.10.5 2017-07-27 16:22:39 -06:00
Matthew Holt a48e4ecb5a vendor: Update dependencies 2017-07-27 16:11:56 -06:00
Matthew Holt 74940af624 httpserver: Set default idle timeout of 5 minutes (closes #1733)
Also clarified a comment in SiteConfig
2017-07-27 16:01:47 -06:00
Matt Holt 32ec39cdea Merge pull request #1784 from mholt/trie-fallbacks
Move fallbackHosts to vhostTrie
2017-07-27 15:53:07 -06:00
Sergey Frolov a197c864e8 Move fallbackHosts to vhostTrie 2017-07-27 17:23:13 -04:00
Matt Holt 4991d702fd Merge pull request #1781 from mholt/global-fallback-hosts
httpserver: Add global FallbackHosts for vhost matching
2017-07-25 19:55:14 -06:00
Matt Holt 76a282718d Merge pull request #1779 from mholt/mitm-panic
mitm: Fix out of bounds error when checking software version in UA
2017-07-25 15:35:51 -06:00
Sergey Frolov c8307409c9 Add global FallbackHosts for vhost matching 2017-07-25 16:10:51 -04:00
Matt Holt 1366a44639 Merge pull request #1780 from sergeyfrolov/master
httpserver: Encapsulate WriteSiteNotFound error
2017-07-25 13:33:25 -06:00
Sergey Frolov ea245b5af5 Encapsulate WriteSiteNotFound error 2017-07-25 15:14:23 -04:00
Matthew Holt 10d5422c3e mitm: Fix out of bounds error when checking software version in UA 2017-07-25 13:00:49 -06:00
Matt Holt b63d9fdc68 Merge pull request #1777 from tw4452852/chunked_ws
proxy: fix hang on chunked websocket server
2017-07-25 09:59:42 -06:00
Tw 9b073aad58 proxy: fix hang on chunked websocket server
Signed-off-by: Tw <tw19881113@gmail.com>
2017-07-25 15:12:38 +08:00
Matthew Holt ae7e098240 httpserver: Only enable QUIC for sites with TLS & HTTP2 enabled 2017-07-24 19:05:48 -06:00
Matt Holt 6e0317a703 Merge pull request #1747 from twdkeule/fix-index-push
Pushes for /index.html work when surfing to /
2017-07-24 14:00:27 -06:00
Thomas De Keulenaer 20f76a256e Push resources for indexFiles when surfing to directories
Use httpserver.IndexFile() to determine index files

Test if middleware pushes indexfile when requesting directory

Fix codereview issues

Serve original request first, push later

Revert "Serve original request first, push later"

This reverts commit 2c66f01115747e5665ba7f2d33e2fd551dc31877.
2017-07-24 12:36:07 +02:00
Matt Holt 40b52fb02e Merge pull request #1775 from tw4452852/roller_parse
log,error: fix roller parser issue
2017-07-20 14:19:17 -06:00
Tw 91150bb770 log,error: fix roller parser issue
Signed-off-by: Tw <tw19881113@gmail.com>
2017-07-20 15:21:06 +08:00
Matthew Holt f1dd9f2b79 mitm: Improve detection related to Chrome and Safari on iOS
Include test for iOS 11 beta
2017-07-19 11:16:41 -06:00
Abiola Ibrahim 6aba4a311a fastcgi: Revert persistent connections (#1739)
* Revert fastcgi to emove persistent connections.

* Fix linting errors

* reintroduce timeout tests

* check for non-zero timeout

* ensure resp is not nil
2017-07-18 12:52:53 -06:00
Jaume Martin 56153e0bb3 httpserver: Adding nobots directive (#1767)
* Adding nobots directive

* Moving nobots directive behind log one.

* Move nobots directive to a better position
2017-07-17 12:39:06 -06:00
Matt Holt 905eb70773 Merge pull request #1753 from spacewander/weak_etag_after_gzip
gzip: change ETag to weak etag after gzip
2017-07-13 22:36:01 -06:00
spacewander e2544597a1 gzip: change ETag to weak ETag after gzip
According to https://tools.ietf.org/html/rfc7232#section-2.1
> Likewise, a validator is weak if it is shared by two or more
representations of a given resource at the same time, unless those
representations have identical representation data.  For example, if
the origin server sends the same validator for a representation with
a gzip content coding applied as it does for a representation with no
content coding, then that validator is weak.

Therefore, after gzip, we should change the original etag to weak etag.
2017-07-14 11:48:34 +08:00
Nicolas ba1132214e httpserver: Add nicolasazrak/caddy-cache plugin directive (#1759) 2017-07-13 05:28:00 -06:00
Matthew Holt b987c7893c vendor: Update lego/acme to v0.4.0
Not much changed but it's a clean pull now; easier for future reference.
2017-07-12 20:34:10 -06:00
Ning Xie aebe387f72 basicauth: remove magic number (#1760) 2017-07-12 19:32:24 -06:00
Henrique Dias 0985024670 httpserver: Add webdav plugin directive (#1752) 2017-07-11 09:43:57 -06:00
Richard Bowden 25a596a98f freebsd init: added new functionality and enabled better logging (#1740)
* uses more of the builtin functionality for starting and stopping of the process by using command and command_args along with procname
* removed -f from daemon as this was hiding error message that were sent to stdout on startup, now writing stdout to the logfile directly

for example, this was being hidden:

“Activating privacy features.. [www.domain.com] failed to get certificate: Error presenting token: Could not find the start of authority”

it now shows up in the log

* aded “caddy_env” to allow the setting of environment variables that caddy might need, for example when setting creds for “DNS Challenge”

* added a check to ensure caddy_config_path file exists
2017-07-10 16:20:30 -06:00
Toby Allen acc67eb3b2 Rename directive requestid to request_id (#1757)
* rename requestid request_id

* rename folder

* folder name match package name requestid
2017-07-10 14:47:48 -06:00
Matt Holt 4c700efbbb Merge pull request #1751 from zikes/header_policy
proxy: add Header load balancing policy
2017-07-10 14:44:07 -06:00
Matt Holt 9ad96b33ff Merge pull request #1754 from spacewander/correct_test_message
gzip,mime: show response header instead of the request one in test message
2017-07-08 14:20:43 -06:00
spacewander 387a083255 gzip,mime: show response header instead of the request one in test message 2017-07-08 11:53:34 +08:00
Jason Hutchinson 95366e41c4 add Header proxy policy 2017-07-07 10:37:49 -05:00
Abiola Ibrahim a6ec51b349 Merge pull request #1748 from bananenmannfrau/master
adds unix timestamp placeholder
2017-07-06 07:46:20 +00:00
bananenmannfrau f6a96227c4 adds unix timestamp placeholder 2017-07-05 22:08:07 +02:00
Matthew Holt 56b3ea876b Correct URL to restic plugin in comment 2017-07-05 08:43:55 -06:00
Tw 2d9273f915 Merge pull request #1746 from JoshHarmon/proxy-ci-fix
proxy: Fix CI fail from format token in Error call
2017-07-05 01:23:33 -05:00
Josh Harmon 8bc7b93bc8 proxy: Fix CI fail from format token in Error call
go vet caused a build fail in https://travis-ci.org/mholt/caddy/jobs/248392875:
   upstream_test.go:480::error: possible formatting directive in Error call (vet)

This patch changes the Error call added in commit 078c991574 to
an Errorf call to support the use of the %d token.
2017-07-04 23:03:59 -07:00
Matt Holt 4750699ab0 Merge pull request #1734 from tw4452852/ineffassign
markdown: fix the real ineffectual assignments in test
2017-06-29 08:28:57 -06:00
Tw a4bf6e586d markdown: fix the real ineffectual assignments in test
Signed-off-by: Tw <tw19881113@gmail.com>
2017-06-29 18:30:18 +08:00
Matthew Holt dfa389c9df Update README and CHANGES for 0.10.4 2017-06-28 16:10:30 -06:00
Martin Redmond 078c991574 proxy: custom upstream health check by body string, closes #324 (#1691) 2017-06-28 15:54:29 -06:00
Fernando Álvarez bf7b25482e log, errors: Introduce rotate_compress option (#1731)
* vendor: update Lumberjack dep

* httpserver/roller: introduce rotate_compress directive

This directive will enable gzip compression provided by [Lumberjack](https://github.com/natefinch/lumberjack/pull/43).

The directive `rotate_compress` can be `true` or `false`, being `false` by default.

* httpserver/roller: remove need to set bool with rotate_compress option
2017-06-28 09:06:32 -06:00
Matt Holt 3bc925400b Merge pull request #1682 from tw4452852/markdown
markdown: reload template on each request and fix fake tests
2017-06-27 23:02:59 -06:00
Tw 655e61ab32 markdown: fix ineffectual assignment CI issue
Signed-off-by: Tw <tw19881113@gmail.com>
2017-06-28 09:28:57 +08:00
Matthew Holt 43b56d621b Allow duplicate Server headers when proxying response
See discussion on commit c9b022b5e0

If we overwrite the Server header, it becomes difficult/impossible to
know from the client whether the request was proxied through Caddy.
2017-06-27 12:11:03 -06:00
Matt Holt 7b5efb5d75 Add restic plugin directive (#1730) 2017-06-25 08:26:57 -07:00
Tw 3390862918 markdown: reload template on each request
Signed-off-by: Tw <tw19881113@gmail.com>
2017-06-25 19:31:12 +08:00
Tw 47fc35acc0 markdown: fix fake tests
Signed-off-by: Tw <tw19881113@gmail.com>
2017-06-25 09:09:21 +08:00
Jason Hutchinson d3fc9f7a9b add gopkg plugin (#1725) 2017-06-24 14:58:33 -07:00
Shannon Wynter a63a6ecb04 Add reauth directive (#1716) 2017-06-24 14:42:40 -07:00
Matthew Holt 47e770621c Use comments for notes on issue and PR templates 2017-06-24 14:23:15 -07:00
elcore 7516b4b533 Fix TestListenerAddrEqual (#1727) 2017-06-24 13:55:36 -07:00
Toby Allen 133ed18374 Create request_id directive #1590 (#1711)
* Create request_id directive #1590

* Address Comments

* Fix TestListenerAddrEqual

* requestid: Add some tests

* Address Comments by tobya

* Address Comments
2017-06-24 13:54:35 -07:00
Marcel Ludwig b0ab3d4281 use caddy.AppName instead of fixed string in 'Server' header (#1709) 2017-06-24 11:17:06 -07:00
George Macon f68233a1ba Configure systemd to send SIGQUIT on stop (#1702) 2017-06-24 11:15:13 -07:00
Jason Hutchinson f3721c103c tls: add optional 'ca' tls directive, closes #1689 (#1699) 2017-06-24 11:10:44 -07:00
lbogdan 3e2b1d145a rewrite: treat "if a not_op b" uniformly by negating "op". (#1696) 2017-06-15 16:45:42 -06:00
Matthew Holt f4b6f15e07 staticfiles: Build redirect based on rewritten URL (fixes #1706) 2017-06-07 14:40:17 -06:00
Matthew Holt 95a6237693 mitm: Add missing import 2017-06-07 14:22:55 -06:00
Matthew Holt 0da76e2b76 mitm: Add experimental Tor support for interception detection 2017-06-07 14:20:15 -06:00
Matthew Holt 8051c73cc3 Add sourcegraph links to readme and contributing guide 2017-06-06 23:18:59 -06:00
bamling a368230ba5 caddytls: introduced own ChallengeProvider type to fix imports related to vendor (#1700)
* introduced own ChallengeProvider type, based on acme.ChallengeProvider to avoid vendoring/version mismatches in Caddy plugins; see Caddy issue #1697

* fixed up comments for ChallengeProvider

* moved ChallengeProvider to caddytls/tls.go
2017-06-06 09:23:00 -06:00
Daniel van Dorp 8a058828a3 Merge pull request #1703 from messyidea/patch-1
Update Initscripts
2017-06-04 20:32:58 +02:00
Messyidea ee124a6d3c Update Initscripts
"$(which caddy)" is not work at startup. 
After this change, I can run "insserv -d caddy" to start caddy automatically on boot.
2017-06-04 12:18:33 +08:00
Yang Luo 97a631ec4c httpserver: Register authz directive (#1693) 2017-06-03 09:34:14 -06:00
Henrique Dias cbdd3a4f8e Add ExtraInfo to EventHook (#1692)
* Update plugins.go

* Add extraInfo to eventHook

* Add extraInfo to eventHook

* Update run.go

* Update run.go

* Update run.go
2017-06-03 07:28:16 -06:00
Matthew Holt 6b8e40b3fb browse: Fix symlink indicators for files in folders other than cwd
Related to #1660 and #1667
2017-06-02 17:40:25 -06:00
Jonas Östanbäck 132f2a9cc3 browse: Show symbolic links and target's type properly (#1667)
* Browse: Show symbolic links and targets type properly
 * gofmt

Signed-off-by: Jonas Östanbäck <jonas.ostanback@gmail.com>

* Move symbolic link check in to isSymlinkTargetDir

Signed-off-by: Jonas Östanbäck <jonas.ostanback@gmail.com>

* Revert template change and show sym link folders as normal folders

* browse: Updated icons including symlink indicators
2017-06-01 06:18:07 -06:00
Andrew Steinborn baf269d4e2 gzip: cleaned up writer pool initialization code (#1695) 2017-05-30 18:29:28 -06:00
Pieter Louw 20a047f7e1 httpserver: Add grpc plugin directive (#1694)
* Add grpc plugin directive

* Update plugin.go

Removed whitespace line

* Update plugin.go
2017-05-30 09:57:24 -06:00
Matt Holt 6ab0d8d8d9 Merge pull request #1651 from mholt/vendoring
Vendor all dependencies
2017-05-28 08:22:43 -06:00
Matthew Holt 6fde3632ef Vendor all dependencies (Warning: Huge changeset.)
The vendor/ folder was created with the help of @FiloSottile's gvt and
vendorcheck.

Any dependencies of Caddy plugins outside this repo are not vendored.

We do not remove any unused, vendored packages because vendorcheck -u
only checks using the current build configuration; i.e. packages that
may be imported by files toggled by build tags of other systems.

CI tests have been updated to ignore the vendor/ folder. When Go 1.9 is
released, a few of the go commands should be revised to again use ./...
as it will ignore the vendor folder by default.
2017-05-27 13:30:11 -06:00
Connor S. Parks 474f119702 httpserver: add not_ends_with (#1688)
* de-duplicates code for 'not' ops and replicates 'not' op for ends_with

* fixes incorrect test expectations
2017-05-25 06:01:24 -06:00
Taylor Otwell 33e1560d53 httpserver: Add not_starts_with condition. (#1687)
* Add not_starts_with condition.

This adds the opposite of the starts_with condition, to check if a
given string does not start with another string.

* Correct white space problems
2017-05-24 09:32:53 -06:00
Matthew Holt a5eb552215 mitm: Add a couple more test cases for Firefox 53 2017-05-23 16:18:56 -06:00
Matthew Holt 7fc0940fe6 mitm: Fix false positive for Firefox 55 nightly 2017-05-23 14:49:10 -06:00
Matthew Holt 7323b14580 Minor change to readme/changes 2017-05-19 15:25:16 -06:00
Matthew Holt 1845e5cf52 Update readme and changelog for v0.10.3 2017-05-19 08:35:32 -06:00
Matthew Holt 410ece831f tls: Only require renewed cert at startup 7 days out (issue #1680) 2017-05-19 08:30:01 -06:00
Jonas Östanbäck ebf4279e98 proxy: Add new URI hashing load balancing policy (#1679)
* Add uri policy test cases
 * Add function definition
 * Add uri hashing policy
 * Refactor and extract hostByHashing and use in IP and URI policy
 * Rename to URIHash

Signed-off-by: Jonas Östanbäck <jonas.ostanback@gmail.com>
2017-05-17 10:46:57 -06:00
Andrew Steinborn b0cf3f0d2d tls: Prefer ChaCha20 if AES-NI instruction set is unavailable (#1675)
Fixes #1674
2017-05-17 10:45:17 -06:00
Jonas Östanbäck 8d3f336971 proxy: Correct policy documentation (#1678)
* Correct proxy policy documentation

Signed-off-by: Jonas Östanbäck <jonas.ostanback@gmail.com>

* Change first's select() wording

Signed-off-by: Jonas Östanbäck <jonas.ostanback@gmail.com>
2017-05-17 09:57:57 -06:00
Jonas Östanbäck 05ea5c32be Fix lint warning by renaming MaxBytesExceededErr -> ErrMaxBytesExceeded (#1676)
Signed-off-by: Jonas Östanbäck <jonas.ostanback@gmail.com>
2017-05-17 09:57:11 -06:00
Jonas Östanbäck a3b2a6a296 log: Add check for maximum number of arguments to log directive (#1672)
* Add check for maximum number of arguments to log directive
 * Add failing test case

Signed-off-by: Jonas Östanbäck <jonas.ostanback@gmail.com>

* Change else ifs into switch

Signed-off-by: Jonas Östanbäck <jonas.ostanback@gmail.com>

* Refactor

Signed-off-by: Jonas Östanbäck <jonas.ostanback@gmail.com>

* Typo

Signed-off-by: Jonas Östanbäck <jonas.ostanback@gmail.com>
2017-05-17 09:51:58 -06:00
Lucas Fontes 724829b689 proxy: leftover from #1666 (#1669) 2017-05-14 12:27:19 -06:00
Lucas Fontes 73494ce63a proxy: added 'health_check_port' to upstream (#1666)
* proxy: added 'health_check_port' to upstream

* proxy: `net.JoinHostPort` instead of `fmt.Printf` for upstream checks

* proxy: changing health_check_port type (int->string)

adding tests for invalid port config
2017-05-13 16:49:06 -06:00
Leonard Hecker 5f860d3a9f proxy: Fixed #1502: Proxying of unannounced trailers (#1588) 2017-05-13 10:08:33 -06:00
Matt Holt 6bb84ba19c Merge pull request #1664 from tw4452852/1663-log
log: allow additional prefix/suffix with predefined format
2017-05-11 18:10:49 -06:00
Tw 710f38043e log: allow additional prefix/suffix with predefined format
Signed-off-by: Tw <tw19881113@gmail.com>
2017-05-11 16:15:41 +08:00
Tw 958abcfa4c proxy: synchronize websocket test (#1654)
fix issue #1652

Signed-off-by: Tw <tw19881113@gmail.com>
2017-05-08 21:16:18 -06:00
Thibault Meyer ea24744bbf Add datadog plugin directive (#1655)
Signed-off-by: Thibault Meyer <meyer.thibault@gmail.com>
2017-05-08 11:48:22 -06:00
Matt Holt f06b825f44 Merge pull request #1656 from tw4452852/1587-limits
Introduce `limits` middleware
2017-05-08 11:39:10 -06:00
George Lesica 642aa63a9c markdown: Support Include arguments for Markdown. (#1653)
Previously, the `Include` override used with the markdown plugin did not
provide the optional `args` parameter. This made it impossible to pass
arguments to a template used with that plugin.
2017-05-08 10:32:14 -06:00
Tw ae645ef2e9 Introduce limits middleware
1. Replace original `maxrequestbody` directive.
2. Add request header limit.

fix issue #1587

Signed-off-by: Tw <tw19881113@gmail.com>
2017-05-08 17:18:04 +08:00
Matthew Holt 90efff68e5 dist: Delete old build automation program
We now use the release program to assist in deploying Caddy (it is much
more integrated and automated): https://github.com/caddyserver/releaser

This older automation code can still be found in this gist:
https://gist.github.com/mholt/cb7285f4950cb93f23be0aa6050fb043
2017-05-05 18:12:41 -06:00
Tw e38921f4a5 httpserver: rename context Push action for more general use (#1641)
Signed-off-by: Tw <tw19881113@gmail.com>
2017-05-05 17:36:20 -06:00
Tw 8e7a36de45 ResponseWriterWrapper and HTTPInterfaces (#1644)
Signed-off-by: Tw <tw19881113@gmail.com>
2017-05-05 09:42:06 -06:00
Sebastian Mancke 86d107f641 added 'login' directive for github.com/tarent/loginsrv/caddy (#1648) 2017-05-04 05:17:53 -06:00
Matthew Holt dfebffb1ee Update readme and changes for version 0.10.2 2017-05-02 12:02:28 -06:00
Matthew Holt 59a5afab29 fastcgi: Prepend missing leading slash when matching paths (see #1645)
httpserver: More path matching tests
2017-05-02 11:20:50 -06:00
Matthew Holt d8fb2ddc2d Fix contributing link in readme 2017-05-02 11:02:25 -06:00
Matthew Holt 5e467883b8 httpserver: Base path of "/" matches all paths, even empty ones
Fixes #1645
2017-05-02 09:43:43 -06:00
Matthew Holt 9fbac10a4b Revert "rewrite: Raise error if rewrite path does not begin with / #1610 (#1629)"
This reverts commit e0ed709397.
2017-05-02 09:30:18 -06:00
Matthew Holt 6d9783a267 Update changelog and readme for 0.10.1. 2017-05-01 23:50:58 -06:00
Matt Holt d5371aff22 httpserver/all: Clean up and standardize request URL handling (#1633)
* httpserver/all: Clean up and standardize request URL handling

The HTTP server now always creates a context value on the request which
is a copy of the request's URL struct. It should not be modified by
middlewares, but it is safe to get the value out of the request and make
changes to it locally-scoped. Thus, the value in the context always
stores the original request URL information as it was received. Any
rewrites that happen will be to the request's URL field directly.

The HTTP server no longer cleans /sanitizes the request URL. It made too
many strong assumptions and ended up making a lot of middleware more
complicated, including upstream proxying (and fastcgi). To alleviate
this complexity, we no longer change the request URL. Middlewares are
responsible to access the disk safely by using http.Dir or, if not
actually opening files, they can use httpserver.SafePath().

I'm hoping this will address issues with #1624, #1584, #1582, and others.

* staticfiles: Fix test on Windows

@abiosoft: I still can't figure out exactly what this is for. 😅

* Use (potentially) changed URL for browse redirects, as before

* Use filepath.ToSlash, clean up a couple proxy test cases

* Oops, fix variable name
2017-05-01 23:11:10 -06:00
Matt Holt 5685a16449 Merge pull request #1642 from tw4452852/internal
internal: inherit original ResponseWriter's interfaces
2017-05-01 23:06:45 -06:00
Tw f58653bc13 internal: inherit original ResponseWriter's interfaces
Signed-off-by: Tw <tw19881113@gmail.com>
2017-05-02 10:53:16 +08:00
Toby Allen e0ed709397 rewrite: Raise error if rewrite path does not begin with / #1610 (#1629)
* Raise syntax error if no '/' prefix to rewrite. Added Tests

* fix case where to keyword is used.

* Fixed spelling issue

* Changes to use Errf rather than new Err function

* Remove new RewritePathErr Function
2017-05-01 13:45:40 -06:00
Matt Holt b3dd604904 Merge pull request #1637 from devangels/bugfix1628
Bugfix for issue #1628 where `Caddyfile` is not being hidden correctly
2017-05-01 08:47:44 -06:00
Simon Lightfoot 8f09ed8f0d Bugfix for issue #1628 where Caddyfile is not being hidden correctly on windows.
Added test case to check if Caddyfile is added to HiddenFiles correctly.
2017-05-01 14:21:49 +01:00
Matt Holt 49d79d7ebc Merge pull request #1598 from tw4452852/1589
proxy: recognize client's cancellation
2017-04-30 08:19:03 -06:00
Matt Holt 4c034f6ad1 Merge pull request #1613 from tw4452852/addlink
Context: add Push action
2017-04-30 08:09:53 -06:00
Matt Holt 503c6b392c Merge pull request #1635 from aaronellington/lint-fixes
lint fixes
2017-04-29 23:49:59 -06:00
Tw 0146bb4e49 proxy: recognize client's cancellation
fix issue #1589

Signed-off-by: Tw <tw19881113@gmail.com>
2017-04-30 10:14:19 +08:00
Aaron Ellington 7ee4ea244f lint fixes 2017-04-29 20:53:58 -04:00
Matthew Holt 705cb98865 Remove TODO because Let's Encrypt has fixed issuance/OCSP ordering 2017-04-29 16:49:07 -06:00
Matthew Holt ff45801cda Minor improvements to PR template 2017-04-29 16:49:07 -06:00
Tw 761a32a080 context: add Push action
Signed-off-by: Tw <tw19881113@gmail.com>
2017-04-27 09:51:33 +08:00
AJ ONeal aa7ecb02af update macOS launchd example (#1600)
* update to match same paths as systemd

* update to match new launchd plist

* generated from scratch with LaunchControl, flawless

Had some issues with the previous go and found LaunchControl which made it easy to generate a perfectly correct launchd conf

* Update README.md
2017-04-26 13:44:32 -06:00
Matthew Holt 5d7db89a90 httpserver: Proper HTTP->HTTPS for wildcard sites (fixes #1625) 2017-04-26 12:32:15 -06:00
Francis Lavoie 1bae36ef29 Fix 1592: Allow insecure CA URL on internal networks (#1607)
* Strip brackets in IsInternal if no port, allow loopback for CA URLs

* Fix a mistake

* Improve the trim

* Fix comment
2017-04-26 12:00:49 -06:00
emersion 52fd4f89bf dist/init: Better way to get systemd version (#1612) 2017-04-26 10:45:57 -06:00
Tw cad89a07e0 gzip: pool gzip.Writer to reduce allocation (#1618)
* gzip: add benchmark

Signed-off-by: Tw <tw19881113@gmail.com>

* gzip: pool gzip.Writer to reduce allocation

Signed-off-by: Tw <tw19881113@gmail.com>
2017-04-26 00:23:50 -06:00
Matthew Holt b18527285d Optimizations should be benchmarked or profiled. 2017-04-25 11:05:20 -06:00
Matthew Holt 1deb99c75c Update contributing guide, make PR template, move github assets 2017-04-25 11:01:22 -06:00
Matthew Holt 0775f9123c Change forum links to new domain 2017-04-23 12:51:08 -06:00
Matthew Holt 5fbd63e35d Update README to match new site, branding, and latest version 2017-04-22 17:32:42 -06:00
Matthew Holt f09fff3d8b Remove ineffectual assignment created by reverting 344017d (#1584) 2017-04-21 22:26:38 -06:00
Matthew Holt 0a798aafac mitm, templates, context: Pool buffers to reduce allocations
Also disable some tests on context.Hostname because they're not portable
2017-04-21 19:54:25 -06:00
Matthew Holt f8614b877d Revert 344017dc21 (#1584) 2017-04-21 13:02:15 -06:00
Matt Holt 182e1b4fb2 Merge pull request #1605 from tw4452852/1604
proxy: take original URL path into account when remove prefix ("without")
2017-04-21 12:07:14 -06:00
Tw c684de9a88 proxy: take original URL path into account when remove prefix
fix issue #1604

Signed-off-by: Tw <tw19881113@gmail.com>
2017-04-21 19:35:32 +08:00
Matthew Holt 27785f7993 Update readme and changelog for v0.10 2017-04-20 11:36:40 -06:00
Matt Holt ad4191a07e Merge pull request #1596 from mholt/closinglogs
httpserver: Don't close stdout or stderr when closing logs (fix #1471)
2017-04-20 05:39:42 -06:00
Jonas Östanbäck 91da965a39 Disable warning for insecure CA if located on private network. (#1599)
* Disable warning for insecure CA if located on private network.
 * Add IsPrivateNetwork function
 * Add tests

Signed-off-by: Jonas Östanbäck <jonas.ostanback@gmail.com>

* Add more testcases

Signed-off-by: Jonas Östanbäck <jonas.ostanback@gmail.com>

* Rename IsPrivateNetwork -> IsInternal

Signed-off-by: Jonas Östanbäck <jonas.ostanback@gmail.com>
2017-04-20 05:38:54 -06:00
Matt Holt b37da03989 Merge pull request #1595 from jannickfahlbusch/feature/hostnameInTemplateActions
templates: Add Hostname template action
2017-04-19 16:20:16 -06:00
Jannick Fahlbusch 92af3ee4d8 Add hostname template action
This adds the ability to display the remote hostname
of the visitors IP with template actions.
2017-04-19 09:18:12 +02:00
Matthew Holt 1e8ab1cadf httpserver: Don't close stdout or stderr when closing logs (fix #1471) 2017-04-18 16:01:11 -06:00
Matt Holt 729e4f0239 Merge pull request #1594 from tw4452852/templateFuncsTest
template: add test for custom function
2017-04-18 09:23:06 -06:00
Tw 790c842fad template: add test for custom function
Signed-off-by: Tw <tw19881113@gmail.com>
2017-04-18 22:49:20 +08:00
Matt Holt f28a159b72 Merge pull request #1591 from tw4452852/template_funcs
template: support custom functions
2017-04-18 08:14:00 -06:00
Tw f77a7a805a template: support custom functions
Signed-off-by: Tw <tw19881113@gmail.com>
2017-04-18 16:17:30 +08:00
Matt Holt 236341f78b Merge pull request #1584 from tw4452852/encoded_path
proxy: use untouched URL for concatenating
2017-04-17 23:39:14 -06:00
Matthew Holt ac3bbdbd3f tls: Arrange cipher suites in map in preference order for convenience
Maps are NOT ordered at runtime but I still want the cipher suites
listed in order of preference in the source file for convenience.
2017-04-17 22:11:05 -06:00
Matthew Holt ce2a9cd8f9 push: Reorder before proxy; and allow zero arguments (cf. #1573) 2017-04-17 22:06:17 -06:00
Matthew Holt 4462e3978b httpserver: max_certs now forces On-Demand TLS even if name is known
Original feature request in forum:
https://forum.caddyserver.com/t/caddy-with-specific-hosts-but-on-demand-tls/1704?u=matt

Before, Caddy obtained certificates for every name it could at startup.
And it would only obtain certificates during the handshake for sites
defined with a hostname that didn't qualify at startup (like
"*.example.com" or ":443"). This made sense for most situations, and
helped ensure that certificates were obtained as early and reliably as
possible.

With this change, Caddy will NOT obtain certificates for hostnames it
knows at startup (even if they qualify) if OnDemand is enabled.

But I think this change generalizes well, because a user who specifies
max_certs is deliberately turning on On-Demand TLS, fully aware of
the consequences. It seems dubious to ignore that config when the user
deliberately put it there. We'll see how this goes.
2017-04-17 19:53:15 -06:00
Tw 344017dc21 proxy: use untouched URL for concatenating
Signed-off-by: Tw <tw19881113@gmail.com>
2017-04-18 09:07:38 +08:00
elcore a56a833423 caddyhttp: New index directive for alternate index file names (#1567)
* caddyhttp: Allow to alternate Index

* Move Index directive

* Fix misspelling outside this PR
2017-04-17 11:02:44 -06:00
Matt Holt 6b66b19deb Merge pull request #1583 from tw4452852/1529
log: only allow new roller related options in a block
2017-04-17 10:01:15 -06:00
Francis Lavoie 33257de2e8 proxy: Fix #1574; health check now respects hostname when upstream Host header is configured (#1577)
* Implement adding Host header to health check

* Fix type problems

* Fix duplicate function, Replace args

* Add debugging

* Add debugging

* Add debugging

* Add debugging

* Attempt to set req.Host instead of the header

* Clean up debugging

* Fix missing newline

* Fix spelling

* Add test, refactoring

* Fix with gofmt

* Add error check on NewRequest
2017-04-17 09:58:47 -06:00
Tw 702dec0647 log: only allow new roller related options in a block
fix issue #1529

Signed-off-by: Tw <tw19881113@gmail.com>
2017-04-17 16:55:45 +08:00
Matthew Holt 8d1da68b47 D'oh, commit all changes to file 2017-04-15 22:58:34 -06:00
Matthew Holt 7a7e3d160b context: Use crypto/rand in case method used for crypto purposes 2017-04-15 22:32:41 -06:00
Matthew Holt 5a1243ff42 context: Fix computation for random length of random string 2017-04-15 16:48:44 -06:00
Matthew Holt edf9cd34cc context: RandomString action produces a random string of random length 2017-04-15 16:38:45 -06:00
Matthew Holt f415ea263e browse: Use more standard sort icon over icons 2017-04-14 16:11:26 -06:00
Matthew Holt 3ca419e2cf browse: Correct links when site defined with a path (fixes #1561) 2017-04-14 12:29:26 -06:00
Toby Allen 7d15435361 markdown: Match index file for each extension; fix #1418 (#1559)
* Create list of index files based on extensions and check on a per config
basis

* remove log lines

* fixed tests

* made gofmt suggested change

* Changes made to simplify
2017-04-08 00:47:33 -06:00
Matt Holt e26a855d8b Merge pull request #1546 from wmark/browse-sort-size
browse: fix arbitrary ordering of directories with sort=size
2017-04-07 18:32:02 -06:00
Peer Beckmann c0ce2b1d50 proxy: Respect insecure_skip_verify for health check (#1558)
* Respect the 'insecure_skip_verify' for the health check.

* WIP: Trying to add a test. Non functional.

* Fixing tests.

* Creating better error messages.

* Optimize two more error messages.

* Move the tests into an extra function.
2017-04-03 15:16:31 -06:00
Angel Santiago 59bf71c293 proxy: Cleanly shutdown health checks on restart (#1524)
* Add a shutdown function and context to staticUpstream so that running goroutines can be cancelled. Add a GetShutdownFunc to Upstream interface to expose the shutdown function to the caddy Controller for performing it on restarts.

* Make fakeUpstream implement new Upstream methods.

Implement new Upstream method for fakeWSUpstream as well.

* Rename GetShutdownFunc to Stop(). Add a waitgroup to the staticUpstream for controlling individual object's goroutines. Add the Stop function to OnRestart and OnShutdown. Add tests for checking to see if healthchecks continue hitting a backend server after stop has been called.

* Go back to using a stop channel since the context adds no additional benefit.
Only register stop function for onShutdown since it's called as part of restart.

* Remove assignment to atomic value

* Incrementing WaitGroup outside of goroutine to avoid race condition. Loading atomic values in test.

* Linting: change counter to just use the default zero value instead of setting it

* Clarify Stop method comments, add comments to stop channel and waitgroup and remove out of date comment about handling stopping the proxy. Stop the ticker when the stop signal is sent
2017-04-02 14:58:15 -06:00
Toby Allen 464ade1da7 Add new browse sort - namedirfirst (#1551)
* Revert "browse: sort listing by dir first (#1527)"

 commit 4e1229e7c9.

* Add new browse sort order  namedirfirst. Make namedirfirst default sort
2017-04-02 20:38:14 +01:00
Matthew Holt ce47cf51be Add launch event information to readme 2017-03-31 20:59:59 -06:00
Matt Holt 6be0386716 Merge pull request #1541 from lucafavatella/review-certs-dir
init: Make CADDYPATH equal across init scripts
2017-03-31 12:45:56 -06:00
W-Mark Kubacki 398d9a6bb5 browse: when sorting by size, sort directory section by name
Previously directories have been merely pulled to the front, and then
sorted arbitrarily. That is, their order among themselves depended on
the filesystem implementations. Something opaque to the visitor.

This fixes said inconsistency, and implements the by-size-then-by-name
order I initially intended for this.
2017-03-29 16:40:29 +02:00
Luca Favatella 956266cd79 Make CADDYPATH equal across init scripts
See also https://github.com/mholt/caddy/pull/1272#issue-191256343
2017-03-28 22:04:19 +01:00
Henrique Dias 6cabc9bfe3 Add Event Hook plugins to DescribePlugins function (#1540)
* Add Event Hook plugins to DescribePlugins function

* Update plugins.go

* Update plugins.go
2017-03-28 08:19:29 -06:00
Henrique Dias da674fd599 Introducing Event Hooks Plugins (#1537)
* add Hook

* update hooks

* remove parenthesis

* Update requests
2017-03-27 18:05:59 -06:00
Toby Allen 4e1229e7c9 browse: sort listing by dir first (#1527)
* Default Browse sort by Dir, File

* Ignore temp template files

* Add folder to test

* unneeded chagne
2017-03-24 22:54:16 -06:00
Matthew Holt 5341c85a27 Update issue template to ask for relevant HTTP requests 2017-03-22 18:58:57 -06:00
Matthew Holt fbd6412359 Don't modify request URL values from Caddyfile (fixes #1528)
HTTP redirects can use host-relative URLs. See discussion in #1497.
2017-03-20 21:05:11 -06:00
ericdreeves 36d2027493 browse: Use helper functions in staticfiles to redirect (#1497)
* Use helper functions in staticfiles to redirect.

Previously the browse package invoked staticfiles.Redirect when
redirecting clients who requested a directory but with a Request-URI
that did not contain a trailing '/'. staticfiles.Redirect only used a
relative URI. This change defers the decision of how to format the
Location header value to the helper methods in the staticfiles package.

* Update const URLPathCtxKey in browse package.
2017-03-15 10:17:12 -06:00
Peer Beckmann a148b92381 proxy: Add the first policy (#1513)
* Add the first policy which sends the request to the first available host

* Make the error message clear. As we expect the second not first upstream
host.
2017-03-14 09:57:08 -06:00
Chao Huang 36a62f0915 markdown: backup title should use TrimSuffix, not TrimRight (#1515) 2017-03-13 15:24:03 -06:00
Matthew Holt d85e90a7b4 tls: Only update OCSP staple if field is not nil (fixes #1514) 2017-03-13 09:30:58 -06:00
Toby Allen d5cc10f7aa Added Const for use of CtxKeys (#1511)
* Added Const for CtxKeys

* Move CtxKey Const declarations

* Fixed tests

* fix test
2017-03-13 09:22:46 -06:00
Martin Bertschler 96bfb9f347 staticfiles: add Content-Length header (closes #1479) (#1492)
* staticfiles: add Content-Length header (closes #1479)

* make linter happy, rename "Html" in identifiers to "HTML"
2017-03-12 16:41:49 -06:00
Matt Holt 5e48f0a412 Merge pull request #1510 from zmwangx/systemd-readme-fix-caddy.service-mode
systemd README: set mode of caddy.service to 644 instead of 744
2017-03-12 00:03:24 -07:00
Zhiming Wang 18c93756b4 systemd README: set mode of caddy.service to 644 instead of 744 2017-03-11 22:43:54 -05:00
Toby Allen cfe52084aa Fix issue #1346 {path} logging {uri} and add {rewrite_uri} placeholder (#1481)
* Fixed issue with {path} actually {uri}

* Test added for path rewrite

* add in uri_escaped

* added rewrite_uri and test

* fix broken test.  Just checks for existance of rewrite header

* gitignore

* Use context to store uri value

* ignore .vscode

* tidy up, removal of comments and invalidated tests

* Remove commented out code.

* added comment as requested by lint

* fixed spelling mistake

* clarified code with variable name

* added context for uri and test

* added TODO comment to move consts
2017-03-11 14:59:47 -07:00
Matthew Holt 6aa0e30af3 basicauth: Don't remove Authorization header on good auth (fixes #1508) 2017-03-10 16:45:51 -07:00
Leonard Hecker 5a41e8bc1a proxy: Fixed #1484, websockets with h2 disabled (#1488)
* Fixed #1484

Fixed a nil pointer runtime error in newConnHijackerTransport,
where the access to the TLSClientConfig did not check for nil values.

* Minor improvement to UseInsecureTransport

This prevents overwriting a possibly preexisting TLSClientConfig,
even though only a single field should be changed.
2017-03-10 10:41:37 -07:00
Nathan Caza 9e4eeb4fb7 Add proxyprotocol directive and listener middleware plugin type (#1349)
* add support for listener middleware

* add proxyprotocol directive

* make caddy.Listener interface required

* Remove tcpKeepAliveListener wrapper from Serve()
This is now done in the Listen() function, along with other potential middleware.
2017-03-09 22:31:37 -07:00
ssut c62b6b9f1a fastcgi: Fix for missing content-length header when using QUIC (#1501)
* Fix for missing content-length header when using QUIC

If request.ContentLength is set then it will be used instead of getting
it from request.Header map since quic-go(lucas-clemente/quic-go@bb24be8)
will not store (and pass) the Content-Length header using its header
map.

This fixes a potential issue where FastCGI POST requests body empty when
QUIC is enabled. (#1370)

* Change the data type for fastcgi contentLength to int64

quic-go uses int64 for contentLength

* Fix an error for undeclared variable

* Fix test for fcgiclient

the data type for contentLength
2017-03-09 19:36:04 -07:00
Matt Holt 52584f7f23 Merge pull request #1499 from mholt/issue1394
Log certificate location
2017-03-09 17:45:06 -07:00
Matt Holt 2be0dc40f0 templates: Merge pull request #1482 from crvv/master
templates: Set MIME type
2017-03-09 17:26:08 -07:00
Kurt Jung e3e62a952d basicauth: Ability to customize realm (#1491)
* Support realms with basic authentication

* Add test for default basicauth directive in which realm is not specified

* Correct typo: missing space

* Remove 'path' subdirective
2017-03-09 13:20:14 -07:00
Matthew Holt 6bc3e7536e tls: Command line flags to disable HTTP and TLS-SNI challenges
This could have just as easily been a tls directive property in the
Caddyfile, but I figure if these challenges are being disabled, it's
because of port availability or process privileges, both of which would
affect all sites served by this process. The names of the flag are long
but descriptive.

I've never needed this but I hear of quite a few people who say they
need this ability, so here it is.
2017-03-08 00:06:49 -07:00
Matt Holt df9d062a8f Merge pull request #1500 from mholt/customports
httpserver: Flags to customize HTTP and HTTPS ports (including for ACME challenges)
2017-03-07 11:31:47 -07:00
Matt Holt eafbf0b218 Merge pull request #1474 from jtyr/jtyr-local_ip
Adding ServerIP context
2017-03-07 08:55:40 -07:00
Jiri Tyr 73d52490d0 Adding support for ServerIP context 2017-03-07 11:59:26 +00:00
crvv 4a095590b1 templates: Set right response Content-Type
If use gzip and templates at the same time, the response body will
be gzipped data. And in this case, the Content-Type header won't be
set by Caddy code. Then Go http package will set "Content-Type" to
wrong value "application/x-gzip" which is determined by response body.
So the header Contenty-Type should be set in templates middleware.
2017-03-07 11:11:52 +08:00
Matthew Holt c8514ad7b7 Avoid panic if reloading before server is started
See: https://forum.caddyserver.com/t/reloading-template-files-as-they-change/1483/3?u=matt

The server takes a moment to start; if USR1 is received before the
instance is saved, it would panic because no instances have been saved.
Instead, we just ignore the signal since no config has finished loading.
2017-03-06 19:20:26 -07:00
Matthew Holt e3f2d96a5e httpserver: Flags to customize HTTP and HTTPS ports (incl. for ACME)
This commit removes _almost_ all instances of hard-coded ports 80 and
443 strings, and now allows the user to define what the HTTP and HTTPS
ports are by the -http-port and -https-ports flags.

(One instance of "80" is still hard-coded in tls.go because it cannot
import httpserver to get access to the HTTP port variable. I don't
suspect this will be a problem in practice, but one workaround would be
to define an exported variable in the caddytls package and let the
httpserver package set it as well as its own HTTPPort variable.)

The port numbers required by the ACME challenges HTTP-01 and TLS-SNI-01
are hard-coded into the spec as ports 80 and 443 for good reasons,
but the big question is whether they necessarily need to be the HTTP
and HTTPS ports. Although the answer is probably no, they chose those
ports for convenience and widest compatibility/deployability. So this
commit also assumes that the "HTTP port" is necessarily the same port
on which to serve the HTTP-01 challenge, and the "HTTPS port" is
necessarily the same one on which to serve the TLS-SNI-01 challenge. In
other words, changing the HTTP and HTTPS ports also changes the ports
the challenges will be served on.

If you change the HTTP and HTTPS ports, you are responsible for
configuring your system to forward ports 80 and 443 properly.

Closes #918 and closes #1293. Also related: #468.
2017-03-06 18:18:49 -07:00
Toby Allen bcddfb2daa Log certificate location 2017-03-06 21:56:24 +00:00
Samuel BERTHE 75ccc05d84 Request placeholders: extract query argument (#1478)
* feat(request placeholders): adds {?arg}

* test(request placeholders): test query argument extractor {?arg}
2017-03-02 23:25:28 -07:00
ericdreeves 0a0d2cc1cf Use RequestURI when redirecting to canonical path. (#1331)
* Use RequestURI when redirecting to canonical path.

Caddy may trim a request's URL path when it starts with the path that's
associated with the virtual host. This change uses the path from the request's
RequestURI when performing a redirect.

Fix issue #1327.

* Rename redirurl to redirURL.

* Redirect to the full URL.

The scheme and host from the virtual host's site configuration is used
in order to redirect to the full URL.

* Add comment and remove redundant check.

* Store the original URL path in request context.

By storing the original URL path as a value in the request context,
middlewares can access both it and the sanitized path. The default
default FileServer handler will use the original URL on redirects.

* Replace contextKey type with CtxKey.

In addition to moving the CtxKey definition to the caddy package, this
change updates the CtxKey references in the httpserver, fastcgi, and
basicauth packages.

* httpserver: Fix reference to CtxKey
2017-02-28 05:54:12 -07:00
Matthew Holt 50749b4e84 httpserver: Improve MITM tests for Chrome on iOS, BlueCoat connections 2017-02-27 18:40:40 -07:00
Matthew Holt 06873175bf httpserver: Add user agent to test case (closes #1454) 2017-02-22 14:29:19 -07:00
Matthew Holt f49e0c9b56 httpserver: Disable default timeouts (closes #1464)
Timeouts are important for mitigating slowloris, yes. But after a number
of complaints and seeing that default timeouts are a sore point of
confusion, we're disabling them now. However, the code that sets
default timeouts remains intact; the defaults are just the zero value.

While Caddy aims to be secure by default, Caddy also aims to serve a
worldwide audience. Even my own internet here in Utah is poor at times,
with bad WiFi signal, causing some connections to take over 10s to
be established. Many use the Internet while commuting on slower
connection speeds. Latency across country borders is another concern.

As such, disabling default timeouts will serve a greater population of
users than enabling them, as slowloris is easy to mitigate and does
not seem to be reported often (I've only seen it once). It's also very
difficult sometimes to distinguish slowloris from genuine slow networks.
That decision is best left to the site owner for now.
2017-02-22 08:52:08 -07:00
Matthew Holt ccdc28631a httpserver: Remove unused ReadTimeout from tlsHelloListener 2017-02-21 21:51:07 -07:00
Matthew Holt a2c410b8e1 Add some MITM test cases 2017-02-21 10:07:12 -07:00
Matt Holt 73794f2a2c tls: Refactor internals related to TLS configurations (#1466)
* tls: Refactor TLS config innards with a few minor syntax changes

muststaple -> must_staple
"http2 off" -> "alpn" with list of ALPN values

* Fix typo

* Fix QUIC handler

* Inline struct field assignments
2017-02-21 09:49:22 -07:00
Matt Holt 4b877eebc4 Revert "Revert removed method" (#1465) 2017-02-20 08:47:56 -07:00
Toby Allen c4842e0fc1 Merge pull request #1462 from mholt/fix_1461
Revert removed method IncrNest
2017-02-20 15:20:10 +00:00
Mateusz Gajewski ff8c430ff0 Revert removed method 2017-02-20 13:43:42 +01:00
Mateusz Gajewski 1262ae92e9 Disable TLS completely if there is no listener with tls enabled (#1456)
* Disable TLS completely if there is no listener with tls enabled

* Format code
2017-02-19 08:09:35 -07:00
Rick Beton 6083871088 Revised fileserver Accept-Encoding and ETag (#1435)
* Revised fileserver Accept-Encoding and ETag

* calculateEtag improved following microbenchmarking
2017-02-18 15:52:50 -07:00
Mateusz Gajewski ce3580bf91 Push down headers from client (#1453)
* Push down headers from client

* Push first, serve later

* After review fixes
2017-02-18 15:50:36 -07:00
Matthew Holt 9720da5bc8 proxy: Fix race in test 2017-02-18 15:42:11 -07:00
Mateusz Gajewski 286d8d1e89 tls: Per-site TLS configs using GetClientConfig, including http2 switch (#1389)
* Remove manual TLS clone method

* WiP tls

* Use GetClientConfig for tls.Config

* gofmt -s -w

* GetConfig

* Handshake

* Removed comment

* Disable HTTP2 on demand

* Remove junk

* Remove http2 enable (no-op)
2017-02-18 15:26:23 -07:00
Kurt Jung 977a3c3226 basicauth: Store name of authenticated user (#1426)
* Store name of authenticated user in basicauth for use by upstream middleware such as fastcgi and cgi.

* Use request context to transfer name of authorized user from basicauth to upstream middleware. Test retrieval of name from context.

* Remove development code that was inadvertently left in place

* Use keys of type httpserver.CtxKey to access Context values
2017-02-17 15:37:58 -07:00
Matt Holt 82cbd7a96b Detect HTTPS interception (#1430)
* WIP: Implement HTTPS interception detection by Durumeric, et. al.

Special thanks to @FiloSottile for guidance with the custom listener.

* Add {{.IsMITM}} context action and {mitm} placeholder

* Improve MITM detection heuristics for Firefox and Edge

* Add tests for MITM detection heuristics

* Improve Safari heuristics for interception detection

* Read ClientHello during first Read() instead of during Accept()

As far as I can tell, reading the ClientHello during Accept() prevents
new connections from being accepted during the read. Since Read() should
be called in its own goroutine, this keeps Accept() non-blocking.

* Clean up MITM detection handler; make possible to close connection

* Use standard lib cipher suite values when possible

* Improve Edge heuristics and test cases

* Refactor MITM checking logic; add some debug statements for now

* Fix bug in MITM heuristic tests and actual heuristic code

* Fix gofmt

* Remove debug statements; preparing for merge
2017-02-17 14:07:57 -07:00
Mateusz Gajewski cdf7cf5c3f HTTP/2 push support (golang 1.8) (#1215)
* WIP

* HTTP2/Push for golang 1.8

* Push plugin completed for review

* Correct build tag

* Move push plugin position

* Add build tags to tests

* Gofmt that code

* Add header/method validations

* Load push plugin

* Fixes for wrapping writers

* Push after delivering file

* Fixes, review changes

* Remove build tags, support new syntax

* Fix spelling

* gofmt -s -w .

* Gogland time

* Add interface guards

* gofmt

* After review fixes
2017-02-17 09:25:22 -07:00
elcore 579007822f Add support for ChaCha20-Poly1305 (#1443) 2017-02-16 22:16:29 -07:00
Matt Holt e50de809a5 Merge pull request #1378 from tw4452852/1362
proxy: handle encoded path in URL
2017-02-16 21:22:11 -07:00
Tw c37481cc7b proxy: handle encoded path in URL
fix issue #1362

Signed-off-by: Tw <tw19881113@gmail.com>
2017-02-17 09:41:00 +08:00
elcore 91ff734327 Implement curve X25519 (Golang 1.8) (#1376)
* Implement curve X25519

* caddytls: Added a default curves list

* caddytls: Improve tests
2017-02-16 17:19:58 -07:00
Matt Holt 524dcee9f6 Merge pull request #1373 from mholt/go18shutdown
Replace our old faithful gracefulListener with Go 1.8's Shutdown()
2017-02-16 16:56:05 -07:00
Matt Holt 0cc48e849c Merge pull request #1374 from mholt/go18timeouts
Set Go 1.8's ReadHeaderTimeout and IdleTimeout
2017-02-16 16:51:00 -07:00
Matt Holt 58b2edd229 Merge pull request #1354 from mholt/keyrotationfix
Set session ticket keys properly (fixed in Go 1.8)
2017-02-16 16:43:02 -07:00
Matt Holt 6271abb22a Update CI to use Go 1.8 (#1444) 2017-02-16 16:37:12 -07:00
Toby Allen 58053fce48 Merge branch 'master' into go18shutdown 2017-02-16 22:46:11 +00:00
Alex Harrington 55bded68c2 fixing panic when root is symlink (#1429)
* fixing panic when root is symlink
checking root path is a symlink before os.Stat which panics

* fixing formatting

* adding test to verify symlink root path check

* fixing typo
2017-02-15 22:02:51 -07:00
Augusto Roman dc3efc939c Add request placeholder support for querying request cookies. (#1392)
* Add request placeholder support for querying request cookies.

This adds the ability to query the request cookies for placeholders
using the syntax "@cookiename".

For example, this would allow rewriting based on a cookie:
  rewrite {
    if @version is 'dev'
    to /dev/index.html
  }

* Switch cookie special char from @ to :

* Switch special char for cookies from : to ~
2017-02-15 21:59:24 -07:00
Matt Holt bdb61f4a1d Merge pull request #1409 from mastercactapus/not_a_directory
return 404 for "not a directory" errors
2017-02-15 18:34:01 -07:00
Matt Holt 1183d91c7b Merge pull request #1365 from tw4452852/1297
redirect: determine the FromScheme at runtime (#1297)
2017-02-15 17:59:29 -07:00
Augusto Roman 463c9d9dd2 Fix data race for max connection limiting in proxy directive. (#1438)
* Fix data race for max connection limiting in proxy directive.

The Conns and Unhealthy fields are updated concurrently across all active
requests.  Because of this, they must use atomic operations for reads and
writes.

Prior to this change, Conns was incremented atomically, but read unsafely.
Unhealthly was updated & read unsafely.  The new test
TestReverseProxyMaxConnLimit exposes this race when run with -race.

Switching to atomic operations makes the race detector happy.

* oops, remove leftover dead code.
2017-02-15 08:09:42 -07:00
Matt Holt 1bd9e9e590 Merge pull request #1436 from rohanpai/master
Added Sourcegraph badge to README
2017-02-14 16:32:55 -07:00
Rohan Pai b650a26727 Added Sourcegraph badge to README 2017-02-14 14:32:12 -08:00
Matt Holt 943ed931db Merge pull request #1425 from jung-kurt/cgi
httpserver: Register cgi plugin
2017-02-14 06:59:32 -07:00
Toby Allen 2417d70bcb Merge pull request #1431 from mholt/issue1388c
Fix for #1388 starting with no Caddyfile
2017-02-14 10:23:46 +00:00
Toby Allen 1a7612071a remove whitespace 2017-02-13 21:28:19 +00:00
Toby Allen 5072d70f38 Fix for #1388 dont attempt to hide Caddyfile if non existant 2017-02-13 21:22:19 +00:00
Kurt b210101f45 Register cgi plugin 2017-02-11 09:38:25 -05:00
Nathan Caza 18edf5864e add fix from golang/go 2017-02-10 21:02:00 -06:00
Julian V. Modesto ce7d3db1be Roll all logs by default (#1379)
* Use new subdirectives and flatten rolling config

* Set default rotate config

* Set default rolling config (hopefully) errwhere

* Make private

* Flatten errors directive and remove c.IncrNest()

* Don't skip first error log roller subdirective we see

* Remove hadBlock

* Try lumberjack import

* Unname import
2017-02-08 09:23:33 -07:00
Mateusz Gajewski f32eed1912 Feature #1246 - Remote syslog (#1301)
* Remote syslog

* golint

* Initialize mutex
2017-02-08 08:02:09 -07:00
Matt Holt cdb79a60f2 Merge pull request #1410 from JRaspass/patch-1
Replace magic number 308 with http.StatusPermanentRedirect
2017-02-07 17:10:05 -07:00
James Raspass 7419573266 Replace magic number 308 with http.StatusPermanentRedirect 2017-02-07 23:55:36 +00:00
Matt Holt d8f92baee2 Merge pull request #1404 from mholt/combinedrollers
Create only one log roller per file across whole process (fixes #1363)
2017-02-06 20:08:50 -07:00
Toby Allen 9e9298ee5d Added additional - to common log file format (#1399) 2017-02-04 15:29:29 -07:00
Matt Holt dc6c986b3f Merge pull request #1381 from tw4452852/958
httpserver: support QUIC reload
2017-01-28 09:40:14 -07:00
Tw 65cb966d38 httpserver: support QUIC reload
fix issue #958

Signed-off-by: Tw <tw19881113@gmail.com>
2017-01-28 19:41:24 +08:00
Matthew Holt d264a2cf0a Set Go 1.8's ReadHeaderTimeout and IdleTimeout 2017-01-24 20:09:03 -07:00
Matthew Holt 139a3cfb13 Replace our old faithful gracefulListener with Go 1.8's Shutdown() 2017-01-24 20:05:53 -07:00
Matthew Holt 04da9c7374 Create only one log roller per file across whole process (fixes #1363) 2017-01-24 19:16:54 -07:00
Matthew Holt 16250da3f0 errors: Fix low risk race condition at server close
See issue #1371 for more information.
2017-01-24 19:09:44 -07:00
Matthew Holt 45a0e4cf49 log: Fix race when stopping server
High improbability of being an actual problem. Logs are safe for
concurrent use, but os.Files are apparently not... Fixes #1371.
2017-01-24 18:48:19 -07:00
Matthew Holt e14a62f188 pprof: Set proper Content-Type header
The standard lib pprof library doesn't set its own Content-Type header
properly. If pprof is used with gzip, the index endpoint will be
interpreted as a .gz file; so we force its hand and set the header.
2017-01-24 16:55:43 -07:00
Matthew Holt 94e382ef0a Version 0.9.5 2017-01-24 08:29:01 -07:00
Matt Holt d8d339740b New 'timeouts' directive to configure timeouts; default timeouts enabled (#1368) 2017-01-24 08:15:25 -07:00
Matthew Holt 205aee6662 Godoc comment; report -validate results to stdout too 2017-01-23 23:48:12 -07:00
Matthew Holt 62fea30e87 browse: Sanitize file names and links in default template
Thanks to Kevin Froman (@beardog108) for the responsible heads up.
2017-01-23 22:37:46 -07:00
Toby Allen bbee961415 Introduce new Replacer fields {rewrite_path}, {rewrite_path_escaped}; issue #1185 (#1364)
* Fix #1185

* Return normal path if no rewrite has happened

* Revert change, not required

* Updated tests
2017-01-23 22:15:27 -07:00
Matthew Holt 59e6ceb518 Minor test cleanup 2017-01-23 22:06:55 -07:00
Matthew Holt 82929b122a Ensure active Caddyfile, if in site, is hidden no matter the cwd 2017-01-23 22:06:29 -07:00
Tw 38c76647c9 proxy: use a new context for the outgoing request (#1358)
* proxy: use a new context for the outgoing request

fix issue #1345

Signed-off-by: Tw <tw19881113@gmail.com>

* proxy: add test for canceling the request

Signed-off-by: Tw <tw19881113@gmail.com>
2017-01-23 18:03:42 -07:00
Matt Holt 696b46f075 Merge pull request #1356 from mholt/fix_hijack
proxy: Fixed #1352: invalid use of the HTTP hijacker
2017-01-23 09:40:47 -07:00
Peer Beckmann e5ef285e59 Generate meta elements from prelude items description and keywords (#1335)
* Generate meta elements from useful front matters.
Limited to the default template and specific elements.

* Rerun gofmt

* Add "keywords" and remove "language" to/from the list of meta tags.

* Add a simple positive list test for the meta tag generation.

* Move the meta tag list to a var at the begin of the file.
Seperate the Meta tags from the other front matters:
	- Don't override user settings with name `meta`
	- Cleaner Code.

* Remove the uneccessary `[:]` in the []Bytes to String casting.
@mholt was right ;)

* One minor refinement. Combining two statements.
2017-01-22 19:16:38 -07:00
Matthew Holt 11adb2e5a7 tls: Always stop and report cert renewal error if operator is present 2017-01-21 15:14:04 -07:00
Matt Holt 9369b81498 Merge pull request #1366 from mholt/tls-sni-renew-fix
tls: Fix background certificate renewals that use TLS-SNI challenge
2017-01-21 15:05:22 -07:00
Matthew Holt 0e34c7c970 tls: Fix background certificate renewals that use TLS-SNI challenge
The loop which performs renewals in the background obtains a read lock
on the certificate cache map, so that it can be safely iterated. Before
this fix, it would obtain the renewals in the read lock. This has been
fine, except that the TLS-SNI challenge, when invoked after Caddy has
already started, requires adding a certificate to the cache. Doing this
requires an exclusive write lock. But it cannot obtain a write lock
because a read lock is obtained higher in the stack, while the loop
iterates. In other words, it's a deadlock.

I was able to reproduce this issue consistently locally, after jumping
through many hoops to force a renewal in a short time that bypasses
Let's Encrypt's authz caching. I was also able to verify that by queuing
renewals (like we do deletions and OCSP updates), lock contention is
relieved and the deadlock is avoided.

This only affects background renewals where the TLS-SNI(-01) challenge
are used. Users report seeing strange errors in the logs after this
happens ("tls: client offered an unsupported, maximum protocol version
of 301"), but I was not able to reproduce these locally. I was also not
able to reproduce the leak of sockets which are left in CLOSE_WAIT.
I am not sure if those are symptoms of running in production on Linux
and are related to this bug, or not.

Either way, this is an important fix. I do not yet know the ripple
effects this will have on other symptoms we've been chasing. But it
definitely resolves a deadlock during renewals.
2017-01-21 14:39:36 -07:00
Tw eeb23a2469 redirect: determine the FromScheme at runtime (#1297)
Signed-off-by: Tw <tw19881113@gmail.com>
2017-01-21 09:53:44 +08:00
Tw ecf852ea43 proxy: fix TestReverseProxy failure on go tip (#1360)
Because of this commit(6e36811c37399d60cbce587b7c48e611009c5aec) on go tip,
it will probe the request's body to determine whether to use chunked transfer
encoding which trailers depend on it.
So we just offer a non empty body to make trailers work.

fix issue #1359

Signed-off-by: Tw <tw19881113@gmail.com>
2017-01-18 15:34:25 -07:00
Mike Pastore 6bac558c98 Add root option to fastcgi directive (#1337) 2017-01-17 10:34:17 -07:00
Leonard Hecker ae10122f7e proxy: Fixed #1352: invalid use of the HTTP hijacker 2017-01-17 15:55:11 +01:00
Matthew Holt c6ba43f888 Set session ticket keys properly (fixed in Go 1.8) 2017-01-15 09:30:02 -07:00
M-A 8464020f7c Add {whenISO} to record timestamp in ISO 8601 format in UTC. (#1353)
* Add {whenISO} to record timestamp in ISO 8601 format in UTC.

ISO 8601 is the standard time format and is easy to parse.

This change assumes users desiring ISO 8016 generally prefer UTC for simplicity.
This results in {whenISO} to be significantly shorter than {when}:
{when}    = "02/Jan/2006:15:04:05 +0000"
{whenISO} = "2006-01-02T15:04:12Z"

Add unit test to verify both, as there was no unit test for {when}.

* Rename {whenISO} to {when_iso}
2017-01-14 15:54:27 -07:00
Henrique Dias 0155b0c5fb Add StartupHooks to Plugins (#1330)
* Update run.go

* Update plugins.go

* Update plugins.go

* Update run.go

* typo

* Update plugins.go

* Update plugins.go

* Requested changes by @mholt
2017-01-14 07:25:57 -07:00
Toby Allen 21d92d6873 Add a cli parameter to -validate a Caddyfile. Issue #1328 (#1344)
* Allow -validate flag to validate caddyfile and return

* Ensure logging without -log flag

* Changes to validate seperatly to Starup func

* Removed change to Start signature.  Created function to ValidateCaddyfile

* comment and tidyup

* ValidateandExecuteDirectives with justValidate option

* remove debugging code

* Tidy up comments

* additional parameter added to calls to mustLogFataf

* ValidateAndExecuteDirectives needs to only return err
2017-01-13 23:42:00 -07:00
Matt Holt 696792781a Merge pull request #1314 from mholt/unbuffered_proxy
proxy: Unbuffered request optimization
2017-01-11 13:02:25 -07:00
Leonard Hecker 601838ac96 proxy: Added TestReverseProxyLargeBody test case
This test ensures that the optimizations in 8048e9c are actually effective.
2017-01-11 19:38:52 +01:00
Leonard Hecker 8048e9c3bc proxy: Added unbuffered request optimization
If only one upstream is defined we don't need to buffer the body.
Instead we directly stream the body to the upstream host,
which reduces memory usage as well as latency.
Furthermore this enables different kinds of HTTP streaming
applications like gRPC for instance.
2017-01-11 19:38:14 +01:00
Matt Holt fab3b5bf19 Merge pull request #1343 from bengadbois/add_misspell_travis
Add misspell to travis build
2017-01-10 17:46:01 -07:00
Ben Gadbois c7c34266da Add misspell to travis build
Replace test text with real words so misspell doesn't throw errors
2017-01-10 15:39:00 -08:00
Matt Holt d6a35381e9 Merge pull request #1342 from bengadbois/fix_misspellings
Fix small misspellings
2017-01-10 15:09:06 -07:00
Ben Gadbois eee9d00255 Fix small misspellings 2017-01-10 13:09:24 -08:00
Matt Holt 6a84d9392e Merge pull request #1334 from tw4452852/1329
proxy: refactor TestUpstreamHeadersUpdate and TestDownstreamHeadersUpdate
2017-01-08 19:29:38 -07:00
Tw 633567744d proxy: refactor TestUpstreamHeadersUpdate and TestDownstreamHeadersUpdate
Signed-off-by: Tw <tw19881113@gmail.com>
2017-01-09 09:07:04 +08:00
Matt Holt c3523305f0 Merge pull request #1325 from mholt/authheader
basicauth: Remove Authorization header on successful authz (issue #1324)
2017-01-07 19:41:55 -07:00
Matthew Holt 3f770603bc browse: Simple filter textbox for default template
Typing in this box will filter the list of items by name.
2017-01-03 23:59:04 -07:00
Matthew Holt 54acb9b2de basicauth: Remove Authorization header on successful authz (issue #1324)
If a site owner protects a path with basicauth, no need
to use the Authorization header elsewhere upstream, especially since it
contains credentials.

If this breaks anyone, it means they're double-dipping. It's usually
good practice to clear out credentials as soon as they're not needed
anymore. (Note that we only clear credentials after they're used,
they stay for any other reason.)
2017-01-03 17:47:27 -07:00
Gregor Noczinski 8b9c9efdba Fix position of "filter" in directives (#1323)
* Fix #2 (Replacement doesn't happen - https://github.com/echocat/caddy-filter/issues/2) bug of caddy-filter

* Fixed gofmt issue.

* Remove comment of reason why we do a reorder
2017-01-03 09:03:13 -07:00
Matthew Holt a1a8d0f655 Merge branch 'master' of github.com:mholt/caddy 2017-01-01 10:27:58 -07:00
Matthew Holt 5d813a1b58 Close connection on automatic HTTP->HTTPS redirects 2017-01-01 10:27:53 -07:00
Sawood Alam 04bee0f36d Implementing custom PathClean function to allow masking, closes #1298 (#1317)
* Added path cleanup functions with masking to preserve certain patterns + unit tests, #1298

* Use custom PathClean function instead of path.Clean to apply masks to preserve protocol separator in the path

* Indentation corrected in the test data map to pass the lint

* Fixing ineffassign of a temporary string variable

* Improved variable naming and documentation

* Improved variable naming

* Added benchmarks and improved variable naming in tests

* Removed unnecessary value capture when iterating over a map for keys

* A typo correction
2016-12-31 20:29:14 -07:00
Matt Holt 7cbbb01f94 Merge pull request #1309 from lhecker/master
Fixed #1292 and resulting issues from #1300
2016-12-31 19:51:29 -07:00
Matthew Holt 466efb7e67 Post init script guidelines 2016-12-30 11:56:37 -07:00
Matthew Holt d98a7aad0f Replace "magic" quotes with regular ones 2016-12-30 11:56:12 -07:00
Leonard Hecker 4babe4b201 proxy: Added support for HTTP trailers 2016-12-30 18:34:26 +01:00
Leonard Hecker 533039e6d8 proxy: Removed leftover restriction to HTTP/1.1 2016-12-29 16:07:22 +01:00
Leonard Hecker b857265f9c proxy: Fixed support for TLS verification of WebSocket connections 2016-12-28 17:38:54 +01:00
Leonard Hecker 153d4a5ac6 proxy: Improved handling of bufferPool 2016-12-28 17:17:52 +01:00
Mateusz Gajewski d5fe4928f2 Remove pre 0.9 code (#1304)
* Remove pre 0.9 code

* Unused import

* gofmtw
2016-12-27 15:53:16 -07:00
Leonard Hecker 20483c23f8 Added end-to-end test case for #1292 2016-12-26 20:53:18 +01:00
Leonard Hecker 9f9ad21aaa Fixed #1292: Failure to proxy WebSockets over HTTPS
This issue was caused by connHijackerTransport trying to record HTTP
response headers by "hijacking" the Read() method of the plain net.Conn.
This does not simply work over TLS though since this will record the TLS
handshake and encrypted data instead of the actual content.
This commit fixes the problem by providing an alternative transport.DialTLS
which correctly hijacks the overlying tls.Conn instead.
2016-12-26 20:52:36 +01:00
Leonard Hecker 53635ba538 Fixed panic due to 0-length buffers being passed to io.CopyBuffer 2016-12-26 20:42:00 +01:00
Leonard Hecker 6352c9054a Fixed proxy not respecting the -http2 flag 2016-12-26 20:40:44 +01:00
Matthew Holt e641d2fd65 Set listenHost to localhost if empty; fixes test on Windows 2016-12-23 10:28:00 -07:00
Matthew Holt 1da70d3ba1 ACME challenge proxy now accounts for ListenHost (bind); fixes #1296 2016-12-23 09:40:03 -07:00
Matthew Holt 3198200479 Re-align atomic struct field (fixes #1306) 2016-12-22 15:02:33 -07:00
Matthew Holt 7dc1dc1c78 Version 0.9.4 2016-12-21 13:47:30 -07:00
Leonard Hecker a3aa414ff3 Fixed HTTP/2 support for the proxy middleware (#1300)
* Fixed HTTP/2 support for the proxy middleware

http.Transport instances whose TLSClientConfig, Dial, or DialTLS field
is non-nil will be configured without HTTP/2 support by default.

This commit adds the proper calls to http2.ConfigureTransport()
everywhere a http.Transport is created and thus fixes HTTP/2 in the
proxy middleware whenever insecure_skip_verify or keepalive is provided.

* Added HTTP/2 support check to TestReverseProxyInsecureSkipVerify
2016-12-21 12:44:07 -07:00
Mateusz Gajewski 54c63002cc Feature #1282 - Support serving statically compressed .gz and .br files (#1289)
* Feature #1282 - Support pre-gzipped files

* Fix broken test cases

* Support brotli encoding as well

* Fix for #1276 - support integers and floats as metadata in markdown (#1278)

* Fix for #1276

* Use strconv.Format

* Use map[string]interface{} as variables

* One more file

* Always run all tests before commit

* Get rid of DocFlags

* Fix syntax in caddy.conf

* Update to Go 1.7.4

* Add send_timeout property to fastcgi directive.

* Convert rwc field on FCGIClient from type io.ReadWriteCloser to net.Conn.
* Return HTTP 504 to the client when a timeout occurs.
* In Handler.ServeHTTP(), close the connection before returning an HTTP
502/504.
* Refactor tests and add coverage.

* Return HTTP 504 when FastCGI connect times out.

* test: add unit test for #1283 (#1288)

* After review fixes

* Limit the number of restarts with systemd

* Prevent fd leak

* Prevent fd leak

* Refactor loops

* gofmt
2016-12-19 09:51:09 -07:00
Mateusz Gajewski c555e95366 Fix for issue #1287 - don't list hidden files in directory listing (#1290)
* Fix for issue #1287 - hide hidden files

* Reuse IsHidden

* Fix failing tests
2016-12-17 11:30:08 -07:00
Matthew Holt 466269fd10 Limit the number of restarts with systemd 2016-12-12 21:58:35 -07:00
Guiheux Steven 8653b70c32 test: add unit test for #1283 (#1288) 2016-12-07 18:59:02 -07:00
Matt Holt e363491a28 Merge pull request #1284 from mholt/fastcgi-send-timeout
Add send_timeout property to fastcgi directive
2016-12-07 18:58:05 -07:00
Matt Holt b0685a64be Merge pull request #1286 from jbub/goupgrade
Update to Go 1.7.4
2016-12-04 13:04:20 -07:00
jbub ff98aa3dd0 Update to Go 1.7.4 2016-12-04 18:22:22 +01:00
Matt Holt c212365a3a Merge pull request #1285 from lbischof/patch-1
Fix syntax in caddy.conf
2016-12-04 07:42:55 -07:00
Lorenz Bischof fea0d5ac3a Fix syntax in caddy.conf 2016-12-04 12:52:34 +01:00
ericdreeves 9f16ac84a0 Return HTTP 504 when FastCGI connect times out. 2016-12-03 16:31:29 -06:00
ericdreeves 5874fbeb7e Add send_timeout property to fastcgi directive.
* Convert rwc field on FCGIClient from type io.ReadWriteCloser to net.Conn.
* Return HTTP 504 to the client when a timeout occurs.
* In Handler.ServeHTTP(), close the connection before returning an HTTP
502/504.
* Refactor tests and add coverage.
2016-12-03 16:15:41 -06:00
Mateusz Gajewski 17e7e6076a Fix for #1276 - support integers and floats as metadata in markdown (#1278)
* Fix for #1276

* Use strconv.Format

* Use map[string]interface{} as variables

* One more file

* Always run all tests before commit

* Get rid of DocFlags
2016-12-02 23:35:33 -07:00
Mateusz Gajewski 9e98d6cd52 Fix for #1164 - allow only one header per line (#1280)
* Fix for #1164 - allow only one header per line

* Include original reporter case
2016-11-29 21:24:12 -07:00
ericdreeves 76f12f475b Merge pull request #1274 from mholt/fastcgi-timeout-defaults
Fix read timeout and add default timeout values.
2016-11-29 19:13:12 -06:00
Mateusz Gajewski 32fa0ce6a0 Merge branch 'master' into fastcgi-timeout-defaults 2016-11-29 19:06:43 +01:00
Matthew Holt 80eb45fcfb tls: Improve flaky test depending on CPU scheduling (I think) 2016-11-28 23:37:22 -07:00
Matthew Holt 36f8759a7b Ensure some tests remove temporary directories they created 2016-11-28 22:26:54 -07:00
Matt Holt e2917784d0 Merge pull request #1275 from mholt/fup-1273
Increase code coverage (caddy.go)
2016-11-27 05:35:54 -07:00
Eldin Hadzic fec840a861 Increase code coverage 2016-11-27 08:55:17 +00:00
elcore d0bf3e1647 Fix network listener address comparison, fixes #1258 (#1273)
* Fix issue #1258

* address comments

* add a test

* address comments 2
2016-11-26 23:44:15 -07:00
ericdreeves b8722d9af3 Fix read timeout and add default timeout values.
By setting the read deadline in streamReader.Read(), the deadline was
extended by the read timeout on each subsequent call. To avoid this, the
deadline is set in FCGIClient.Request(), before the first read occurs.

See #1094.
2016-11-25 10:30:51 -06:00
Geno 7dc23b18ae Init switch from HOME to Caddy (#1272)
* INIT-systemd use CADDYPATH instatt of HOME

* INIT-upstart use CADDYPATH instatt of HOME

* INIT-upstart use CADDYPATH instatt of HOME

* INIT-upstart use CADDYPATH instatt of HOME
2016-11-23 14:12:19 -07:00
Matthew Holt 9d398adf5d dist: Give more slack to numProcs test (was failing on Travis CI) 2016-11-20 21:50:46 -07:00
Matthew Holt 22a266a259 templates: Add arguments to .Include 2016-11-20 21:40:06 -07:00
ericdreeves 5a6b765673 Add connect_timeout and read_timeout to fastcgi. (#1257) 2016-11-19 09:05:29 -07:00
Matt Holt 8acf043297 Merge pull request #1268 from mholt/fix-files-test
Sort the resulting slice before the comparison.
2016-11-19 08:47:53 -07:00
ericdreeves 98c17bcdf2 Sort the resulting slice before the comparison. 2016-11-19 08:37:36 -06:00
Matt Holt 8c392a02c8 Merge pull request #1267 from DenBeke/launchd
launchd service file for Mac
2016-11-18 15:00:09 -07:00
MathiasB 30337ac33f launchd service file for Mac 2016-11-18 17:58:40 +01:00
Gyula Voros b783caaaed Filter empty headers (#1239)
* Filter empty headers

Some web servers (e.g. Jetty 9.3) don’t like HTTP headers with empty values. This commit filters header replacements with zero length.

* Extend tests to verify removal of empty headers

* Handle add-header case

* Change - Use short variable assignment
2016-11-16 21:41:53 -07:00
Mateusz Gajewski c972ea39c8 Fastcgi upstreams (#1264)
* Make fastcgi load balanceable too

* Address one more corner case - invalid configuration fastcgi /

* After review fixes

* Simplify conditions

* Error message

* New fastcgi syntax

* golint will be happy

* Change syntax
2016-11-16 21:29:43 -07:00
Tw 12fd349916 Merge pull request #1232 from mholt/fix-1229
proxy: record request Body for retry (fixes #1229)
2016-11-08 19:24:38 +08:00
Benny Ng dd4c4d7eb6 proxy: record request Body for retry (fixes #1229) 2016-11-04 19:15:36 +08:00
Ngo The Trung 0cdaaba4b8 Add maxrequestbody directive (#1163) 2016-11-04 08:25:49 +08:00
Mateusz Gajewski 63f749112b Use http.Header instead of custom type (#1214)
* Use http.Header

* This initialization was just stupid
2016-11-03 12:24:26 -06:00
Matt Holt e19a007b38 Merge pull request #1238 from tw4452852/1234
proxy: make sure value is optional when removing a header
2016-11-03 12:13:39 -06:00
Tw e85ba0d4db proxy: make value is optional when removing a header
fix issue #1234

Signed-off-by: Tw <tw19881113@gmail.com>
2016-11-03 22:50:51 +00:00
Matthew Holt b89cbe18e2 Move header up above errors in directive order (fix #1183) 2016-11-02 08:13:58 -06:00
Matthew Holt 14500d8204 header: Implement Flusher and CloseNotifier 2016-11-02 08:13:58 -06:00
Tw a2900e46f4 header: only register deletion operation (#1212)
fix issue #1183

Signed-off-by: Tw <tw19881113@gmail.com>
2016-11-01 22:08:02 -06:00
ericdreeves 08c17c7c31 Add Files action to template context. (closes #1198) (#1226)
* Add Files action to template context. (#1198)

* Fixes to testFiles().

- Set os.ModePerm on directories created during test.
- Use filepath.Join() to create directory path.
- Use Fatalf, not Fatal.

* Make additional fixes to test cases.

* Fix test cases to use correct path format.

Dir.Open() in net/http requires '/'-separated paths while
filepath.Join() may produce paths with different separator.

* Remove directory created by test at end of loop.

* Close the FileSystem before returning.

* Initialize names slice to the number of entries.

Also, do not call os.RemoveAll() unless the path to the directory
is a valid one.
2016-11-01 22:04:53 -06:00
Matthew Holt 49cb225cbd Ensure user always sees fatal errors at startup 2016-10-31 21:45:12 -06:00
elcore 53e117802f Add support for OCSP Must-Staple for Let's Encrypt certs (#1221)
* Fix Caddytls

* Let the user decide

* Address comments
2016-10-29 08:44:49 -06:00
Matthew Holt 23f89f30e9 Add sourcegraph link to readme 2016-10-27 19:27:39 -06:00
Matt Holt ff32ade1d8 Merge pull request #1216 from chris-bandce/patch-1
Punctuation
2016-10-26 11:23:00 -06:00
chris-bandce 3ce9075d3d Punctuation 2016-10-26 16:27:35 +01:00
Matt Holt f2e999aab2 Merge pull request #1213 from mholt/simplify_code
Remove dead code, do struct alignment, simplify code
2016-10-26 00:05:08 -06:00
Mateusz Gajewski 8cc3416bbc Remove dead code, do struct alignment, simplify code 2016-10-25 19:19:54 +02:00
Toby Allen c4d64a418b Log site info output at start. Fix for #1205 (#1210)
* Log Site start to -log. Fix for #1205

* Removed Comment
2016-10-25 09:31:21 -06:00
Matt Holt f3108bb7bf Merge pull request #1207 from tw4452852/1206
proxy: set request's body to nil explicitly
2016-10-25 09:30:10 -06:00
Mateusz Gajewski c2853ea64b Use proper Request (#1197)
* Use proper Request

* Fixes
2016-10-25 09:28:53 -06:00
Matt Holt 561069fdb6 Merge pull request #1211 from mholt/LogStartupCmd
Log Startup and Shutdown Commands
2016-10-24 14:26:54 -06:00
Toby Allen 66a07773ab Log Startup and Shutdown Commands 2016-10-24 20:48:36 +01:00
Tw a1dd6f0b34 proxy: set request's body to nil explicitly
fix issue #1206

Signed-off-by: Tw <tw19881113@gmail.com>
2016-10-24 11:10:12 +08:00
Henrique Dias 2b9bbc5236 Moving File Manager higher in the directive list (#1199)
* Moving File Manager higher in the directive list

See hacdias/caddy-filemanager#35.

* gofmt
2016-10-20 14:52:43 -06:00
Matt Holt 2f5e840ea9 Merge pull request #1193 from mholt/Go-1.7.3
Update to Go 1.7.3
2016-10-19 18:00:00 -06:00
Eldin Hadzic ab3cc8f961 Update to Go 1.7.3 2016-10-19 21:26:04 +00:00
Matt Holt efb1c54e13 Merge pull request #1189 from rndmh3ro/more_upstart_scripts
add separate upstart scripts for centos 6 and ubuntu 12.04
2016-10-19 08:13:44 -06:00
Matt Holt 3c1957a612 Merge pull request #1190 from tw4452852/1188
errors: don't join the absolute file path
2016-10-17 20:48:45 -06:00
Tw 2bd6fd0aea errors: don't join the absolute file path
fix issue #1188

Signed-off-by: Tw <tw19881113@gmail.com>
2016-10-18 09:55:50 +08:00
Sebastian Gumprich f1342e37ed add separate upstart scripts for centos 6 and ubuntu 12.04 2016-10-17 22:04:26 +02:00
Toby Allen 94af37087b Fix for fastcgi deletion of Caddy-Rewrite-Original-URI header #1153 (#1184)
* Very simple fix for #1153

* Prevent  Caddy-Rewrite-Original-URI being added as  an HTTP ENV variable passed to FastCGI

part of fix for #1153

* Changes to Markdown to fix travis CI build.

#1955.2

* Revert "Changes to Markdown to fix travis CI build."

This reverts commit 4a01888839.

* fail fast and fmt changes

* Create test for existance of Caddy-Rewrite-Original-URI header value #1153

* updated test comment

* const moved outside function so available to tests
2016-10-16 12:11:52 -06:00
Matthew Holt 5fcfdab6c7 Reorder basicauth directive; it should also protect redirects 2016-10-15 09:41:03 -06:00
Gregor Noczinski 016384abef * Added directive "filter" to whitelist for support of github.com/echocat/caddy-filter (#1167) 2016-10-15 09:31:22 -06:00
Matt Holt 036633b64a Merge pull request #1174 from tw4452852/1173
header: implement http.Hijacker for responseWriterWrapper
2016-10-13 23:09:21 -06:00
Matt Holt 550b1170bd Merge branch 'master' into 1173 2016-10-13 22:56:19 -06:00
Matt Holt d44016b937 Merge pull request #1178 from tw4452852/1177
proxy: preserve path trailing slash if it was there
2016-10-11 18:26:51 +02:00
Tw 4baca884c5 proxy: preserve path trailing slash if it was there
fix issue #1177

Signed-off-by: Tw <tw19881113@gmail.com>
2016-10-11 17:06:59 +08:00
Tw d0455c7b9c add more descriptive errors
Signed-off-by: Tw <tw19881113@gmail.com>
2016-10-11 10:34:51 +08:00
Tw e5d33e73f3 header: implement http.Hijacker for responseWriterWrapper
fix issue #1173

Signed-off-by: Tw <tw19881113@gmail.com>
2016-10-11 08:53:47 +08:00
Matt Holt 9ced4b17e5 Merge pull request #1175 from tw4452852/fix_archiver
dist: adapt to archiver's refactor
2016-10-10 20:41:00 +02:00
Tw b48bda4a6d dist: adapt to archiver's refactor
Signed-off-by: Tw <tw19881113@gmail.com>
2016-10-10 16:39:27 +08:00
Matt Holt 5d9989405a Merge pull request #1165 from aishraj/master
Have milliseconds as the latency log time unit
2016-10-08 22:48:48 +02:00
Aish Raj Dahal 733f622f7a Add new placeholder for latency in milliseconds 2016-10-05 21:06:15 -07:00
Matt Holt 20a54d0e07 Merge pull request #1169 from bengadbois/patch-1
Adding Go Report Card badge
2016-10-05 15:38:27 -06:00
Ben Gadbois b5a07d43fa Adding Go Report Card badge 2016-10-05 20:48:43 +02:00
elcore 8561f42786 CurvePreferences (Test) (#1161)
We need consistency in code style!!!
2016-10-04 01:15:23 +02:00
elcore e1ea58b7c4 Customize curve preferences, closes #1117 (#1159)
* Feature Request: #1117

* The order of the curves matter
2016-10-03 10:52:45 -06:00
Matt Holt e9ce45ce61 Merge pull request #1158 from tw4452852/1154
proxy: handle 'without' option in encoded form of URL path
2016-10-02 13:39:55 -06:00
Tw cc638c7faa proxy: handle 'without' option in encoded form of URL path
fix issue #1154

Signed-off-by: Tw <tw19881113@gmail.com>
2016-10-02 19:32:14 +00:00
Matthew Holt b766dab9fa tls: Reorder some logic to avoid subtle, undocumented behavior
By calling SetTLSAddress, the acme package reset the challenge provider
to the default one instead of keeping the custom one we specified before
with SetChallengeProvider. Yikes. This means that Caddy would try to
open a listener on port 443 even though we should have been handling it
with our provider, causing the challenge to fail, since usually port 443
is in use.

So this change just reorders the calls so that our provider takes
precedence.

cf. https://github.com/xenolf/lego/pull/292
2016-09-28 18:29:46 -06:00
Matthew Holt c885edda24 Version 0.9.3 2016-09-28 12:43:28 -06:00
Peer Beckmann bb7787d2ee Remove the eager check in the browse middleware (#1144)
* Remove the eager check in the browse middleware, whether the root directory exists.
Caddy will start and throw a 404-error until the directory will be created.

* Add the complimentary test.
 - Tests the startup of the browse middleware if the site root is inexistent and browse is pointing to the site root.

* Some minor stylistic tweaks.
2016-09-28 12:23:44 -06:00
Matt Holt 8620581f95 Merge pull request #1145 from tw4452852/header_placeholder
replacer: evaluate header placeholder when replacing
2016-09-28 10:42:41 -06:00
Tw 99a6b2db67 replacer: evaluate header placeholder when replacing
fix issue #1137

Signed-off-by: Tw <tw19881113@gmail.com>
2016-09-28 19:32:16 +00:00
Matt Holt 8944332e13 Merge pull request #1143 from mholt/1136-fix
Fix #1136 - IP hash policy no longer changes host pool
2016-09-28 08:07:58 -06:00
Kris Hamoud be1c57acfe 1136 fix
logic change
2016-09-28 04:09:46 -07:00
Matt Holt b06b3981cf Merge pull request #1140 from tw4452852/defer_header
header: defer header operations
2016-09-27 19:02:28 -06:00
Sebastian Schmittner 8cb4e90852 Add fix and tests for FastCGI persistent connections (#1134)
* keep fastcgi connection open

* poor mans serialisation to make up for the lack of demuxing

* pointing includes to echse's repo

* Revert "pointing includes to echse's repo"

This reverts commit 281daad8d4.

* switch for persistent fcgi connections on/off added

* fixing ineffectual assignments

* camel case instead of _

* only activate persistent sockets on windows (and some naming conventions/cleanup)

* gitfm import sorting

* Revert "fixing ineffectual assignments"

This reverts commit 79760344e7.

# Conflicts:
#	caddyhttp/staticfiles/fileserver.go

* added another mutex and deleting map entries. thx to mholts QA comments!

* thinking about it, this RW lock was not a good idea here

* thread safety

* I keep learning about mutexs in go

* some cosmetics

* adding persistant fastcgi connections switch to directive

* Support for configurable connection pool.

* ensure positive integer pool size config

* abisofts pool fix + nicer logging for the fastcgi_test

* abisoft wants to have dialer comparison in _test instead of next to struct

* Do not put dead connections back into pool

* Fix fastcgi header error

* Do not put dead connections back into pool

* some code style improvements from the discussion in https://github.com/mholt/caddy/pull/1134

* abisofts naming convention
2016-09-27 18:12:22 -06:00
Matt Holt 871d11af00 Merge pull request #1135 from mholt/proxyerrs
proxy: Improve failover logic and retries
2016-09-27 17:53:44 -06:00
Matthew Holt 6397a85e50 proxy: Only wait 250ms between backend tries 2016-09-27 17:49:00 -06:00
Tw d0ddfc849d header: defer header operations
fix issue #1131

Signed-off-by: Tw <tw19881113@gmail.com>
2016-09-27 15:35:13 +08:00
Matthew Holt 617012c3fb Use time.Since() for readability 2016-09-24 21:27:57 -06:00
Matt Holt 4adbcd2565 Merge pull request #1125 from hlidotbe/master
Add expires directive
2016-09-24 21:11:30 -06:00
Matt Holt d01bcd591c Merge pull request #1112 from tw4452852/proxy_header
proxy: don't append predefined headers
2016-09-24 21:02:19 -06:00
Tw c9b022b5e0 proxy: don't append some predefined headers
fix issue #1086

Signed-off-by: Tw <tw19881113@gmail.com>
2016-09-25 09:24:27 +00:00
Matthew Holt a661007a55 proxy: Fix retry logic for when no hosts are available 2016-09-24 16:30:40 -06:00
Matthew Holt 0c0142c8cc Delete tryDuration, now unused 2016-09-24 16:05:33 -06:00
Matthew Holt 37f05e450f proxy: Add try_duration and try_interval; by default don't retry 2016-09-24 16:03:22 -06:00
Matthew Holt 9b9a77a160 proxy: Improved error reporting
We now report the actual error message rather than a generic one
2016-09-24 14:22:13 -06:00
Tw 4670d13c8c proxy: fix checking error in TestDownstreamHeadersUpdate and TestUpstreamHeadersUpdate
Signed-off-by: Tw <tw19881113@gmail.com>
2016-09-24 19:28:42 +00:00
Matthew Holt 9077cce126 Add tests for case insensitivity of keys and saving contexts 2016-09-24 13:24:33 -06:00
Matthew Holt 76d9d695be Remove use of proxy_header in test 2016-09-24 12:27:16 -06:00
Matthew Holt a4d70262aa Use strings.Contains instead of IndexOf for readability 2016-09-24 12:09:28 -06:00
Hugues Lismonde 79f2deee42 Add expires directive 2016-09-24 08:10:32 +02:00
Abiola Ibrahim bac54de9eb Fastcgi persistent fix (#1129)
* Support for configurable connection pool.

* ensure positive integer pool size config
2016-09-23 23:29:23 -06:00
Josh Aas 3f83eccfbd improvements for Linux systemd integration (#1127)
* Remove unnecessary config options from systemd service so it will work with earlier versions of systemd. Simplify the systemd service instructions and make them more complete.

* Minor systemd README improvements.

* Add back some of the optional systemd 229 stuff but commented out for compat.

* A bunch of updates to the README for linux systemd.
2016-09-23 16:50:39 -06:00
Matthew Holt d60a26ae30 import: No need to error for no matches if using glob pattern 2016-09-22 11:07:37 -06:00
Matthew Holt bbf954cbf2 Fix case sensitivity in site addresses 2016-09-20 22:44:05 -06:00
Matthew Holt 73916ccc30 Version 0.9.2 2016-09-20 16:33:50 -06:00
Matthew Holt fcad474064 Move prometheus directive higher in list (closes #1119) 2016-09-20 14:29:32 -06:00
Lars Wiegman 4449d3dcd9 Add the multipass plugin to the directives (#1120)
* Add the multipass plugin to the directives

* Fix gofmt
2016-09-20 09:06:28 -06:00
Matthew Holt c4a177bd3b Fix lint warnings and update CI tests to Go 1.7.1 2016-09-19 17:25:21 -06:00
Matthew Holt 8ecd543519 Refactor and improve TLS storage code (related to locking) 2016-09-19 17:24:34 -06:00
Matt Holt 7af499c28b Merge pull request #1115 from tw4452852/import
parser: fix broken absolute path imports (recursive case)
2016-09-19 16:28:55 -06:00
Tw 64fd281f5b parser: fix broken absolute path imports
fix issue #1026

Signed-off-by: Tw <tw19881113@gmail.com>
2016-09-18 10:16:24 +08:00
Matthew Holt bedad34b25 Clean up some significant portions of the TLS management code 2016-09-14 22:30:49 -06:00
Matt Holt 0e7635c54b Merge pull request #1102 from coopernurse/awslambda-ext-plugin
awslambda external plugin
2016-09-13 16:47:10 -06:00
James Cooper 40a3a6b24f Add awslambda to plugin.go 2016-09-10 07:52:04 -07:00
Sebastian Schmittner 09a1f02971 persistent fastcgi connections (#1087)
* keep fastcgi connection open

* poor mans serialisation to make up for the lack of demuxing

* pointing includes to echse's repo

* Revert "pointing includes to echse's repo"

This reverts commit 281daad8d4.

* switch for persistent fcgi connections on/off added

* fixing ineffectual assignments

* camel case instead of _

* only activate persistent sockets on windows (and some naming conventions/cleanup)

* gitfm import sorting

* Revert "fixing ineffectual assignments"

This reverts commit 79760344e7.

# Conflicts:
#	caddyhttp/staticfiles/fileserver.go

* added another mutex and deleting map entries. thx to mholts QA comments!

* thinking about it, this RW lock was not a good idea here

* thread safety

* I keep learning about mutexs in go

* some cosmetics
2016-09-10 06:47:47 -06:00
David Prandzioch 8e54d5cecb Updated FreeBSD init script (#1098)
* Updated FreeBSD init script to allow the server to stop properly

* Fixed FreeBSD init script permissions

* Updated FreeBSD init script to allow the server to stop properly
2016-09-08 21:02:28 -06:00
Matthew Holt 7ef405f9b2 Satisfy gofmt 2016-09-08 20:32:21 -06:00
Matthew Holt 11bf28f783 Weird, git didn't commit this closing curly brace 2016-09-08 18:54:36 -06:00
Matthew Holt 98bba33861 Lower-case server name for good measure
This already happens in the getCertificate function, but doing it here
guarantees case insensitivity across the board for this handshake.
2016-09-08 18:50:04 -06:00
Matthew Holt abdf13ea30 Improve TLS storage provider errors
We renamed caddytls.ErrStorageNotFound to caddytls.ErrNotExist to more
closely mirror the os package. We changed it to an interface wrapper
so that the custom error message can be preserved. Returning only "data
not found" was useless in debugging because we couldn't know the
concrete value of the error (like what it was trying to load).

Users can do a type assertion to determine if the error value is a "not
found" error instead of doing an equality check.
2016-09-08 18:50:04 -06:00
Matthew Holt a251831feb Fix bug renewing certs affecting Caddyfiles using wildcard addresses
A Caddyfile using *.example.com as its site address would be subject to
this bug at renewal time, as it would use the literal "*.example.com"
value instead of the name being passed in to obtain a certificate.
This change fixes the LoadSite call so that it looks in the proper
directory for the certificate resources.
2016-09-08 18:50:04 -06:00
Matt Holt 1ea96def31 Merge pull request #1091 from mikepulaski/master
Caddyfiles read from STDIN now have server types associated with them.
2016-09-05 13:14:35 -06:00
Mike Pulaski 635714fe38 Caddyfiles read from STDIN now have server types associated with them. Fixes #1090. 2016-09-05 18:34:32 +02:00
Matthew Holt 5f135a27d5 Eliminate ineffectual assignments
Most of these were fixed by handling errors that were previously
unhandled (oops).
2016-09-05 10:30:46 -06:00
Matthew Holt 45a3d0b526 Fix misspellings 2016-09-05 10:20:34 -06:00
Sebastian a122304196 Support for lego's timeout option (#1041)
* Support for lego's timeout option

* Changed IntVar to DurationVar for catimeout

* Modified catimeout according to mholt's comments
2016-08-30 13:38:44 -06:00
Matthew Holt 14a6e4b4ed More minor text fixes 2016-08-30 13:37:35 -06:00
Matt Holt 72e4ba8b5b Merge pull request #1079 from ijt/master
Use naoina/toml for MIT license. Make proxy_test work in any directory.
2016-08-30 08:10:57 -06:00
Issac Trotts 1991083322 Fix tests to not make long unix domain socketpaths
Some tests were running into this issue:
https://github.com/golang/go/issues/6895

Putting the sockets into temp dirs fixes the problem.
2016-08-29 18:09:46 -07:00
Issac Trotts 7ba804353c Use naoina/toml instead of BurntSushi/toml 2016-08-29 17:55:44 -07:00
Abiola Ibrahim ac933f1685 Merge pull request #1042 from mholt/status-middleware
Add 'status' middleware instead of 'status' directive for 'rewrite' middleware
2016-08-29 16:41:27 +01:00
Volodymyr Galkin 20ee457cae Add 'status' middleware instead of 'status' directive for 'rewrite' middleware 2016-08-29 17:17:23 +03:00
Matthew Holt 34a99598f7 Ignore conflicting settings if TLS disabled (fixes #1075) 2016-08-26 16:18:08 -06:00
Matthew Holt 191ec27c26 Clarify godoc for HTTP handler signature 2016-08-25 17:13:49 -06:00
Matthew Holt b1ae8a71f1 More tests for TLS configuration 2016-08-25 17:13:27 -06:00
Matt Holt d4f4fcdb4c Merge pull request #1070 from FiloSottile/master
Actually set tls.Config.PreferServerCipherSuites
2016-08-25 16:33:12 -06:00
Filippo Valsorda ef58536711 Actually set tls.Config.PreferServerCipherSuites
It was set by default on the caddy-internal config object, and even
checked for conflicts, but it was never actually reflected on the
tls.Config.

This will have user-visible changes: a client that prefers, say, AES-CBC
but also supports AES-GCM would have used AES-CBC befor this, and will
use AES-GCM after.

This is desirable and important behavior, because if for example the
server wanted to support 3DES, but *only if it was strictly necessary*,
it would have had no way of doing so with PreferServerCipherSuites
false, as the client preference would have won.
2016-08-25 18:28:51 +01:00
Matthew Holt 17709a7d3f Defer loading directives until needed (fix for previous commit)
This change is still experimental.
2016-08-25 00:15:18 -06:00
Matthew Holt 5a691fbaf5 httpserver: Added function to register directive at runtime (dev only)
This function should not be used outside of development. It destroys the
absolute ordering and guarantees of correctness. Multiple uses of it
may work fine, but maybe not if they overlap, causing non-deterministic
builds which is bad. However, this can be convenient when developing
a plugin by calling it from an init() function, since you don't have
to modify the Caddy source code just to try your plugin.
2016-08-24 23:12:41 -06:00
Matt Holt fd3008459e Merge pull request #1068 from tw4452852/multiple_log_test
log: add multiple log entry test
2016-08-24 22:40:32 -06:00
Tw e7af23e1e6 log: add multiple log entry test
Signed-off-by: Tw <tw19881113@gmail.com>
2016-08-25 11:21:08 +08:00
Matt Holt 536daf36be Merge pull request #1060 from tw4452852/multiple_log
log: support multiple log entries under one path scope
2016-08-23 22:55:17 -06:00
Tw 5e0f4083c4 log: support multiple log entries under one path scope
fix issue #1044

Signed-off-by: Tw <tw19881113@gmail.com>
2016-08-24 12:48:51 +08:00
Matthew Holt c3e0733406 Only move storage if actually starting a server (closes #1067) 2016-08-23 22:32:52 -06:00
Matt Holt 70cbfdc585 Merge pull request #1064 from stp-ip/quic-protocol-headers
Keep quic protocol headers only between one hop
2016-08-23 16:46:43 -06:00
Michael Grosser 3dc98c8ce3 Keep quic protocol headers only between one hop
Removing quic protocol headers from being persisted during proxy requests.
Not removing them could lead to the client attempting to connect to the wrong port.
This makes the quic headers consistent with other protocol headers.
2016-08-23 22:05:56 +00:00
Matthew Holt 151d0baa94 Minor text fixes ;) 2016-08-23 15:47:23 -06:00
Matt Holt 9d947713ff Merge pull request #1059 from PalmStoneGames/master
Add plugin capabilities for tls storage.
2016-08-23 15:31:20 -06:00
Luna Duclos 1dfe1e5ada Add plugin capabilities for tls storage.
To use a plugged in storage, specify "storage storage_name" in the tls block of the Caddyfile, by default, file storage will be used
2016-08-23 23:00:20 +02:00
Matthew Holt 628920e20e Improve logic related to error handling on SiteExists call
No need to check if SiteExists if the config is not managed or the name
does not even qualify.
2016-08-23 14:51:07 -06:00
Matt Holt 15d25f1ca4 Merge pull request #1062 from nemothekid/fix/ws-connection-close
Proxy: Set MaxIdleConnsPerHost to -1 to prevent Idle Conns
2016-08-23 10:39:16 -06:00
Nimi Wariboko Jr 2ef8905966 Proxy: Instead of setting DisableKeepAlives, set MaxIdleConnsPerHost to -1 to prevent net/http from pooling the connections. DisableKeepAlives causes net/http to send a Connection: Closed header which is bad. Fixes #1056 2016-08-22 18:00:37 -07:00
Matt Holt fdad616df7 Merge pull request #1049 from tw4452852/log_body
capture request body normally
2016-08-22 18:49:17 -06:00
Tw 590862a962 replacer: capture request body normally
fix issue #1015

Signed-off-by: Tw <tw19881113@gmail.com>
2016-08-23 08:20:49 +08:00
Tw 40c09d6789 replacer: code refactor
Signed-off-by: Tw <tw19881113@gmail.com>
2016-08-23 08:20:49 +08:00
Tw bba1059ef9 log: add log request body test
Signed-off-by: Tw <tw19881113@gmail.com>
2016-08-23 08:20:49 +08:00
Matt Holt 1d3212a598 Merge pull request #1046 from PalmStoneGames/master
Add error parameter to storage.SiteExists()
2016-08-20 16:52:18 -06:00
Matthew Holt c75ee0000e Fix edge case in stapling; do not allow certs without any names 2016-08-19 13:42:48 -06:00
Matt Holt 8cdc65edd2 Merge pull request #1048 from miekg/missingbits
Implement missing bits for an external servertype
2016-08-19 11:48:07 -06:00
Miek Gieben a609fa5f56 Implement missing bits for an external servertype
Make ServerListeners public and add two helper methods to get access
to the address they listen on. This is useful for tests (among other
things)

Also make DefaultConfigFile a var so it can be overridden by server
types.
2016-08-19 00:19:45 +00:00
Luna Duclos 78341a3a9a Add error parameter to storage.SiteExists() 2016-08-18 18:38:33 +02:00
Matthew Holt fdc62d015f log: Create log file directory before creating log file 2016-08-18 07:35:55 -06:00
Matthew Holt e8e55955f4 Report error when loading the lexer 2016-08-17 17:17:26 -06:00
Matthew Holt 8b8afd72d7 Support env vars in import; make sure it's a file 2016-08-17 17:03:21 -06:00
Matthew Holt c5524b0bab Report errors when loading Caddyfile 2016-08-17 17:02:01 -06:00
Matthew Holt c5aa5843d9 Version 0.9.1 2016-08-17 14:09:04 -06:00
Matthew Holt 745ae6ff2f Travis CI improvements 2016-08-15 23:05:46 -06:00
Matthew Holt 432a2d23a7 Use Go 1.7 for CI 2016-08-15 22:57:18 -06:00
Matt Holt 83345062d7 Merge pull request #1037 from devangels/template_env_1
Fix for invalid environment variable names used on windows that start…
2016-08-15 15:41:26 -06:00
Simon Lightfoot f372f5fce7 Fix for invalid environment variable names used on windows that start with an equals symbol. Even though this contradicts the Microsoft docs. 2016-08-15 20:42:00 +01:00
Matthew Holt 454b1e3939 Honor bind directive for ACME challenges
Fixes https://forum.caddyserver.com/t/basic-caddy-installation-failing-on-automatic-https/472?u=matt
2016-08-15 12:08:51 -06:00
Simon Lightfoot 45ac11088e Added support for environment variables to 'templates' module. (#1035)
* * Added support for environment variables to 'templates' module.

* Fixed flaw in test caused by environment variable ordering during testing on CI.

* Updated some local variables to camel-case.

* Reverted changes to replacer as environment variables are processed elsewhere.

* Removed PrintEnv functionality in favour of documenting using template range.
2016-08-15 11:15:58 -06:00
Matt Holt eb3bbc409f Merge pull request #1036 from mholt/fix-errors-setup-test
Fix error which lead to skipping tests in 'errors.TestErrorsParse'
2016-08-15 10:09:46 -06:00
Volodymyr Galkin b830667a25 Fix error which lead to skipping tests in 'errors.TestErrorsParse' 2016-08-15 16:44:34 +03:00
Matt Holt ba5aeab19d Merge pull request #1032 from mholt/errors-config-check-duplicate-status-codes
Check for duplicate status code entries in 'errors' directive
2016-08-12 09:28:51 -06:00
Volodymyr Galkin 441a8f5eff Check for duplicate status code entries in 'errors' directive 2016-08-12 16:47:00 +03:00
Matt Holt 4f6500c95b Merge pull request #1028 from evvvvr/wildcard-error-page-752
Add support for default (wildcard) error page
2016-08-11 23:33:44 -06:00
Carter 7dd385f6b4 Merge pull request #1023 from mholt/log-request-body
Now logging the request body
2016-08-11 22:32:32 -04:00
Matt Holt ac0dd303be Merge branch 'master' into log-request-body 2016-08-11 17:36:09 -06:00
Carter 676202a31e Fixed styling and byte count 2016-08-11 19:08:49 -04:00
Matthew Holt c8a99d2f81 Don't use X-Forwarded-For for {remote} placeholder (closes #1025) 2016-08-11 16:54:17 -06:00
Carter 8e8e2f596d Merge branch 'master' into log-request-body 2016-08-11 18:08:19 -04:00
Volodymyr Galkin f7003bee3f Add support for default (wildcard) error page 2016-08-11 15:51:15 +03:00
Carter 532ab661c7 Fully read and close the request body 2016-08-11 07:03:14 -04:00
Matthew Holt 68be4a9161 Don't prompt for email when user is not there to provide one
Also don't bother showing stdout output in same situation
2016-08-10 23:46:04 -06:00
Matthew Holt 46bc0d5c4e Whoops, finishing up the last commit properly
Need to add the name to namesObtaining each time we use the ACME client.
2016-08-10 23:44:43 -06:00
Matthew Holt 8e75ae2495 Only consume HTTP challenge for names we are solving for (closes #549)
If another ACME client is trying to solve a challenge for a name not
being served by Caddy on the same machine where Caddy is running, the
HTTP challenge will be consumed by Caddy rather than allowing the owner
to use the Caddyfile to proxy the challenge.

With this change, we only consume requests for HTTP challenges for
hostnames that we recognize. Before doing the challenge, we add the
name to a set, and when seeing if we should proxy the challenge, we
first check the path of course to see if it is an HTTP challenge;
if it is, we then check that set to see if the hostname is in the
set. Only if it is, do we consume it.

Otherwise, the request is treated like any other, allowing the owner
to configure a proxy for such requests to another ACME client.
2016-08-10 22:13:06 -06:00
Carter d56ac28bec Using a LimitReader and fixed test and log format. 2016-08-10 22:43:26 -04:00
Carter 3fd8218f67 refactor and added test 2016-08-10 11:04:37 -04:00
Carter d06c15cae6 Set the request body to a new ReadCloser 2016-08-10 10:36:16 -04:00
Carter 59b1e8b0bc Now logging the request body
Logging the request body if the Content-Type is application/json or
application/xml
2016-08-10 10:04:57 -04:00
Daniel van Dorp dbd76f7a57 dist/init/linux-sysvinit: process @weingart's feedback (#1008)
* dist/init/linux-sysvinit: use kill -0 to test process status

* dist/init/linux-sysvinit: use service (as root) instead of /etc/init.d/
2016-08-09 22:29:13 -06:00
Matthew Holt e081d8b5c2 Maintainence routine deletes old (expired) OCSP staple files 2016-08-09 16:46:51 -06:00
Matthew Holt 8eefeb6788 Begin improved OCSP stapling by persisting staple to disk 2016-08-09 16:12:22 -06:00
Abiola Ibrahim 5fb3c504c9 Merge pull request #1017 from shawnps/patch-2
fix typo
2016-08-09 09:18:41 +01:00
Shawn Smith 0f04f2fd44 fix typo 2016-08-09 14:57:17 +09:00
Matthew Holt ce8b1dfe94 Warn upon use of proxy_header 2016-08-08 13:48:13 -06:00
elcore 4b3c532573 Use P384 for TestUser (privateKey) (#1009) 2016-08-08 11:13:10 -06:00
Carter 4d76ccb1c4 Rounding the latency in certain scenarios (#1005)
* Rounding the latency in certain scenarios

* run gofmt
2016-08-08 10:14:53 -06:00
Simon Lightfoot de7bf4f241 Enable downloading of protected content. See issue #979 (#980)
* Fix for stripping of 'Content-Disposition' and other headers from 'X-Accel-Redirect' redirect scripts.

* Added test case for header manipulation of redirect response.
2016-08-07 23:16:33 -06:00
Stavros Korokithakis 681c95a749 Add default "Restricted" realm to HTTP Basic auth (#1007)
* Add default "Restricted" realm to HTTP Basic auth

* Add tests for the Basic auth realm
2016-08-07 07:50:36 -06:00
elcore e5a8927635 Allow just one TLS Protocol (Caddyfile) (#1004)
* Allow just one TLS Protocol

* Fix typo
2016-08-06 15:00:54 -06:00
Matthew Holt 2019eec5a5 Fix lint warnings; group methods for same type together 2016-08-06 14:46:52 -06:00
Matthew Holt 33d1033928 Add link to clean code guidelines for reference 2016-08-06 14:43:31 -06:00
Matthew Holt 0d8b95334f Use Let's Encrypt's permalink to subscriber agreement 2016-08-06 14:42:00 -06:00
Matthew Holt ee615371a8 Export staticfiles.Redirect for convenience in preserving query string 2016-08-06 14:40:58 -06:00
Nimi Wariboko Jr 4c6082df64 Merge pull request #987 from nemothekid/proxy/single-webconn
Proxy: Single WebSocket connection
2016-08-05 16:59:38 -07:00
Nimi Wariboko Jr 8898066455 Merge branch 'master' into proxy/single-webconn 2016-08-05 16:57:54 -07:00
Nimi Wariboko Jr fffc1bed73 Merge pull request #984 from nemothekid/proxy/keepalive-directive
Proxy: Add keepalive directive to proxy to set MaxIdleConnsPerHost on transport
2016-08-05 16:57:44 -07:00
Nimi Wariboko Jr 824ec6cb95 Merge branch 'master' into proxy/keepalive-directive 2016-08-05 16:20:37 -07:00
Nimi Wariboko Jr 5b5e365295 Instead of treating 0 is a default value, use http.DefaultMaxIdleConnsPerHost 2016-08-05 15:41:32 -07:00
Matt Holt c6c221b8db Merge pull request #996 from tw4452852/host_header
proxy: add Host header checking
2016-08-05 16:20:06 -06:00
Daniel van Dorp 985049e0c2 Merge pull request #1003 from mholt/sysvinit-fix-setcap
dist/init/linux-sysvinit: execute setcap directly
2016-08-05 16:49:24 +02:00
Daniel van Dorp 3a4f8e8d0c dist/init/linux-sysvinit: execute setcap directly
`$(which setcap)` might evaluate to nothing,
and this way the error thrown will be more clear.
If setcap is not available on Debian/Ubuntu,
you can install the package `libcap2-bin`
2016-08-05 16:33:47 +02:00
Daniel van Dorp f3a3bf6204 dist/init/linux-sysvinit: improve legacy compatibility (#1002)
* dist/init/linux-sysvinit: pass --oknodo for --start as well

* dist/init/linux-sysvinit: manually rm PIDFILE

Since start-stop-daemon --remove-pidfile is new and not present
everywhere.
2016-08-05 08:15:32 -06:00
Daniel van Dorp 81a3101efe Merge pull request #1001 from mholt/sysvinit-typo
dist/init/linux-sysvinit: fix minor typo in DAEMONOPTS
2016-08-05 14:13:33 +02:00
Daniel van Dorp 22a4b6cde2 dist/init/linux-sysvinit: fix minor typo in DAEMONOPTS 2016-08-05 14:04:30 +02:00
Tw 94c63e42d6 proxy: add Host header checking
fix issue #993

Signed-off-by: Tw <tw19881113@gmail.com>
2016-08-04 13:07:20 +08:00
s7v7nislands c110b27ef5 improve rlimit usage (#982)
* improve rlimit usage

* fix windows build

* fix code style
2016-08-02 21:01:36 -06:00
Nimi Wariboko Jr 6e9439d22e Proxy: Fix data race in test. 2016-08-02 12:39:15 -07:00
Nimi Wariboko Jr f4cdf53761 Proxy: Fix transport defn; cleanup connection. 2016-08-02 12:31:17 -07:00
Matt Holt 89f5b646c3 Merge pull request #978 from krishamoud/master
added ip_hash load balancing
2016-08-02 11:25:52 -06:00
Matthew Holt a24e361761 Enable cgo for CI tests so race detector can run 2016-08-02 10:59:16 -06:00
Matthew Holt 5ac04b91bb Add -race to CI tests; use Go 1.6.3 2016-08-02 10:55:38 -06:00
elcore 1b1aecb1e6 Merge pull request #989 from tw4452852/tls_race
tls: fix TestStandaloneTLSTicketKeyRotation data race
2016-08-02 14:03:14 +02:00
Tw 3d43c5b697 tls: fix TestStandaloneTLSTicketKeyRotation data race
==================
WARNING: DATA RACE
Write at 0x00c42049d300 by goroutine 26:
  github.com/mholt/caddy/caddytls.standaloneTLSTicketKeyRotation()
      /home/tw/golib/src/github.com/mholt/caddy/caddytls/crypto.go:230 +0x698

Previous read at 0x00c42049d300 by goroutine 25:
  github.com/mholt/caddy/caddytls.TestStandaloneTLSTicketKeyRotation()
      /home/tw/golib/src/github.com/mholt/caddy/caddytls/crypto_test.go:113 +0x413
  testing.tRunner()
      /home/tw/goroot/src/testing/testing.go:610 +0xc9

Goroutine 26 (running) created at:
  github.com/mholt/caddy/caddytls.TestStandaloneTLSTicketKeyRotation()
      /home/tw/golib/src/github.com/mholt/caddy/caddytls/crypto_test.go:101 +0x2a4
  testing.tRunner()
      /home/tw/goroot/src/testing/testing.go:610 +0xc9

Goroutine 25 (running) created at:
  testing.(*T).Run()
      /home/tw/goroot/src/testing/testing.go:646 +0x52f
  testing.RunTests.func1()
      /home/tw/goroot/src/testing/testing.go:793 +0xb9
  testing.tRunner()
      /home/tw/goroot/src/testing/testing.go:610 +0xc9
  testing.RunTests()
      /home/tw/goroot/src/testing/testing.go:799 +0x4b5
  testing.(*M).Run()
      /home/tw/goroot/src/testing/testing.go:743 +0x12f
  github.com/mholt/caddy/caddytls.TestMain()
      /home/tw/golib/src/github.com/mholt/caddy/caddytls/setup_test.go:27 +0x133
  main.main()
      github.com/mholt/caddy/caddytls/_test/_testmain.go:116 +0x1b1
==================
==================
WARNING: DATA RACE
Write at 0x00c4204aa6c0 by goroutine 26:
  github.com/mholt/caddy/caddytls.TestStandaloneTLSTicketKeyRotation.func2()
      /home/tw/golib/src/github.com/mholt/caddy/caddytls/crypto_test.go:93 +0x56
  github.com/mholt/caddy/caddytls.standaloneTLSTicketKeyRotation()
      /home/tw/golib/src/github.com/mholt/caddy/caddytls/crypto.go:233 +0x638

Previous read at 0x00c4204aa6c0 by goroutine 25:
  github.com/mholt/caddy/caddytls.TestStandaloneTLSTicketKeyRotation()
      /home/tw/golib/src/github.com/mholt/caddy/caddytls/crypto_test.go:108 +0x391
  testing.tRunner()
      /home/tw/goroot/src/testing/testing.go:610 +0xc9

Goroutine 26 (running) created at:
  github.com/mholt/caddy/caddytls.TestStandaloneTLSTicketKeyRotation()
      /home/tw/golib/src/github.com/mholt/caddy/caddytls/crypto_test.go:101 +0x2a4
  testing.tRunner()
      /home/tw/goroot/src/testing/testing.go:610 +0xc9

Goroutine 25 (running) created at:
  testing.(*T).Run()
      /home/tw/goroot/src/testing/testing.go:646 +0x52f
  testing.RunTests.func1()
      /home/tw/goroot/src/testing/testing.go:793 +0xb9
  testing.tRunner()
      /home/tw/goroot/src/testing/testing.go:610 +0xc9
  testing.RunTests()
      /home/tw/goroot/src/testing/testing.go:799 +0x4b5
  testing.(*M).Run()
      /home/tw/goroot/src/testing/testing.go:743 +0x12f
  github.com/mholt/caddy/caddytls.TestMain()
      /home/tw/golib/src/github.com/mholt/caddy/caddytls/setup_test.go:27 +0x133
  main.main()
      github.com/mholt/caddy/caddytls/_test/_testmain.go:116 +0x1b1
==================

Signed-off-by: Tw <tw19881113@gmail.com>
2016-08-02 15:28:12 +08:00
Nimi Wariboko Jr d534a2139f Proxy: When connecting to websocket backend, reuse the connection isntead of starting a new one. 2016-08-01 19:11:31 -07:00
Eric Drechsel c4e65df262 Proxy: Add a failing test which replicates #763
2 websocket connections are made instead of one
2016-08-01 19:09:02 -07:00
Kris Hamoud 88d3dcae42 added ip_hash load balancing
updated tests

fixed comment format

fixed formatting, minor logic fix

added newline to EOF

updated logic, fixed tests

added comment

updated formatting

updated test output

fixed typo
2016-08-01 18:50:53 -07:00
Nimi Wariboko Jr db4cd8ee2d Proxy: Add keepalive directive to proxy to set MaxIdleConnsPerHost on transport. Fixes #938 2016-08-01 15:54:07 -07:00
Matt Holt da5b3cfc50 Merge pull request #976 from wjkohnen/h2
Re-enable HTTP/2 for Go 1.7.
2016-08-01 15:06:44 -06:00
Matt Holt 372c77da3a Merge pull request #983 from djvdorp/sysvinit
dist/init/linux-sysvinit: caddy for SysVinit
2016-08-01 13:34:07 -06:00
Daniel van Dorp 251c38bfb2 dist/init/linux-sysvinit: caddy for SysVinit
In addition to `linux-upstart` and `linux-systemd`, I think this one
might be very useful too.

The script is based on [this script](https://git.devuan.org/fredg/stuffs/blob/master/caddy/init.d/caddy)
by @fredg, found via [Installation du serveur Caddy sous Devuan &middot; Frédéric Galusik](http://galusik.xyz/installation-caddy-server-devuan/#démon:d7570338f345f168f3c50f22e7f8c47c).
I have modified it into an extended version myself, since I had the need for this.
2016-08-01 20:51:22 +02:00
Matt Holt ba1bee2b8f Merge pull request #981 from tw4452852/redir
redir: loading block arguments before parsing matcher
2016-08-01 12:36:06 -06:00
Tw b64894c31e redir: loading block arguments before parsing matcher
fix issue #977

Signed-off-by: Tw <tw19881113@gmail.com>
2016-08-01 14:38:18 +08:00
Wolfgang Johannes Kohnen d88dd74dec Re-enable HTTP/2 for Go 1.7.
* Since Go 1.7 HTTP/2 support is enabled only if TLSConfig.NextProtos
   includes the string "h2".
 * see mholt/caddy#975
2016-07-30 18:18:53 +00:00
Matt Holt 7157bdc79d Merge pull request #970 from ponychicken/reload
Specify the reload signal in the upstart script
2016-07-29 15:24:27 -06:00
Leo 72af3f8256 Specify the reload signal in the upstart script 2016-07-29 22:56:25 +02:00
Matthew Holt c8daaba4be Update link to SA 1.1.1 (and other minor tweaks) 2016-07-28 11:11:14 -06:00
Matthew Holt af48bbd234 Scope TLS max_certs to site config instead of global 2016-07-28 11:08:18 -06:00
Matthew Holt 1e1e69b90f Discard byte order mark (fix #962) 2016-07-27 12:48:49 -06:00
Matt Holt cf1b355d30 Merge pull request #960 from phifty/patch-1
Change position of locale directive
2016-07-25 08:53:54 -06:00
Philipp Brüll 1dd413bd69 Change position of locale directive
First, great job on the 0.9 release! It seems caddy's path lead into a bright future. Thanks also for including the locale plugin.

Trying it, I've figured out, that there might be a problem with the order of the directives. In the typical use case, the result of the locale detection might be used in the `rewrite` and `log` plugin. If I'm not mistaken, it makes sense to put the `locale` directive before those.
2016-07-24 11:55:25 +02:00
Matt Holt 1bbad72ff1 Merge pull request #956 from xuqingfeng/ratelimit
Register ratelimit
2016-07-23 10:39:16 -06:00
xuqingfeng b2aed643f4 Register ratelimit 2016-07-23 10:50:07 +08:00
Viacheslav Chimishuk 62e8c4b76b Use authentification credentials from proxy's configuration as a default. (#951) 2016-07-22 11:33:50 -06:00
Matthew Holt 6490ff6224 Adjust proxy headers properly (fixes #916) 2016-07-21 18:18:01 -06:00
Matthew Holt 57710e8b0d Revert "Merge pull request #931 from pedronasser/master"
This reverts commit 9ea0591951, reversing
changes made to 2125ae5f99.
2016-07-21 13:31:43 -06:00
Matt Holt 4678471fe0 Merge pull request #952 from abiosoft/condition-patch
minor condition keyword check refactor
2016-07-21 12:03:33 -06:00
Matt Holt d746b95906 Merge pull request #950 from tw4452852/proxy_parse
proxy: fix hyphen issue when parsing target
2016-07-21 12:01:10 -06:00
Abiola Ibrahim 3c8b2b5954 minor condition keyword check refactor 2016-07-21 15:42:38 +01:00
Matt Holt cf3ce49104 Merge pull request #949 from gsquire/headers-update
HTTP Headers
2016-07-21 00:28:29 -06:00
Tw ca3d23bc70 proxy: fix hyphen issue when parsing target
fix issue #948

Signed-off-by: Tw <tw19881113@gmail.com>
2016-07-21 13:56:35 +08:00
Garrett Squire e7c842215e Allow multiple values for an HTTP header and
add a test to ensure this works.
2016-07-20 22:17:13 -07:00
Tw beae16f07c Proxy performance (#946)
* proxy: add benchmark

Signed-off-by: Tw <tw19881113@gmail.com>

* replacer: prepare lazily

update issue#939

benchmark            old ns/op     new ns/op     delta
BenchmarkProxy-4     83865         72544         -13.50%

Signed-off-by: Tw <tw19881113@gmail.com>

* proxy: use buffer pool to avoid temporary allocation

Signed-off-by: Tw <tw19881113@gmail.com>
2016-07-20 19:06:14 -06:00
Matthew Holt 1240690973 Avoid deadlock (fixes #941) 2016-07-19 12:05:40 -06:00
Matthew Holt b35d19d78e Set protocol version properly (fixes #943) 2016-07-19 11:48:44 -06:00
Matt Holt cf4e0c9c9c Merge pull request #940 from mmlkrx/update-readme-contributing-section
Update readme contributing section
2016-07-19 07:50:31 -06:00
Matthias Loker ac97cf426f Update readme contributing section 2016-07-19 15:41:54 +02:00
Matthew Holt f28af63732 Version 0.9 2016-07-18 21:50:45 -06:00
Matthew Holt 38c2463416 Fix ACME asset migration when renaming folders 2016-07-18 21:50:27 -06:00
Matthew Holt df018ea64a Properly handle path-only addresses (also fix godoc typos) 2016-07-18 18:45:20 -06:00
Matthew Holt 4ff46ad447 Refactor Server into TCPServer and UDPServer 2016-07-18 16:28:26 -06:00
Matthew Holt 59c6513b31 Clarify some godoc 2016-07-18 16:21:19 -06:00
Matthew Holt aede4ccbce Small changes; mostly comments 2016-07-18 14:32:28 -06:00
Miek Gieben 9315738dab Allow for UDP servers (#935)
* Allow for UDP servers

Extend the Server interface with ServePacket and ListenPacket - this is
in the same vein as the net package.

Plumb the packetconn through the start and restart phases.

Rename RestartPair to RestartTriple as it now also contains a Packet.
Not that these can now be nil, so we need to check for that when
restarting.

* Update the documentation
2016-07-18 14:24:09 -06:00
Matthew Holt 502a8979a8 Propagate DNS provider plugins to caddy package so -plugins shows them 2016-07-15 21:29:06 -06:00
Pedro Nasser d6110f8e9e Merge pull request #932 from pedronasser/fix-import
fix: import should always be relative to current file directory
2016-07-14 23:30:28 -03:00
Pedro Nasser d7698ecf13 fix: import should always be relative to current file directory 2016-07-14 21:48:56 -03:00
Pedro Nasser 9ea0591951 Merge pull request #931 from pedronasser/master
fix transparent host header #916
2016-07-14 21:46:24 -03:00
Pedro Nasser ffafb2eca8 Merge branch 'master' of github.com:pedronasser/caddy 2016-07-14 18:17:05 -03:00
Pedro Nasser 6bb1e0c674 improve transparent mode 2016-07-14 18:16:58 -03:00
Pedro Nasser 6f37e9d31b Merge branch 'master' into master 2016-07-14 15:48:46 -03:00
Pedro Nasser b58872925a fixed transparent host and added test case 2016-07-14 15:43:06 -03:00
Pedro Nasser 8d7136fc06 fix transparent host header #916 2016-07-14 15:30:00 -03:00
Pedro Nasser 2125ae5f99 import should get absolute path before glob (#929)
* import should get absolute path before glob

* fix test: import should get absolute path before glob

* try to fix test on windows

* use complete path as the dispenser filename

* fix caddyfile test
2016-07-13 10:58:42 -06:00
Matthew Holt 3fd3feeffe Add Ext action to template context (closes #844) 2016-07-11 08:37:19 -06:00
Matthew Holt 62622eb853 proxy: 'transparent' also sets X-Forwarded-For (closes #924) 2016-07-09 17:33:40 -06:00
Abiola Ibrahim 87c389f73d Proposal: Middleware Config (#908)
* Prototype middleware Config

* Refactors

* Minor refactors
2016-07-08 18:12:52 -06:00
Abiola Ibrahim cf03c9a6c8 Merge pull request #928 from abiosoft/master
discard remaining args for if block in redir.
2016-07-08 19:52:19 +01:00
Abiola Ibrahim 48abb41135 discard remaining args in if block 2016-07-08 19:39:31 +01:00
Pedro Nasser 7eb4bb8e1c Merge pull request #927 from pedronasser/master
fix rewrite if problem (skip remaining args)
2016-07-08 14:52:45 -03:00
Pedro Nasser 39e55072d7 fix rewrite if problem (skip remaining args) 2016-07-08 14:11:15 -03:00
Chad Retz 88a2811e2a Pluggable TLS Storage (#913)
* Initial concept for pluggable storage (sans tests and docs)

* Add TLS storage docs, test harness, and minor clean up from code review

* Fix issue with caddymain's temporary moveStorage

* Formatting improvement on struct array literal by removing struct name

* Pluggable storage changes:

* Change storage interface to persist all site or user data in one call
* Add lock/unlock calls for renewal and cert obtaining

* Key fields on composite literals
2016-07-08 07:32:31 -06:00
Matthew Holt 065eeb42c3 Move rewrite and ext middlewares to before gzip (fixes #914) 2016-07-06 00:04:53 -06:00
Matt Holt d4b10b69a7 Merge pull request #920 from ianwalter/master
Changing refs from /usr/bin to /usr/local/bin
2016-07-05 23:32:01 -06:00
Matthew Holt f77264b776 Improve basicauth password comparison
Thanks to @jaredfolkins for the feedback
2016-07-05 12:49:25 -06:00
Ian Walter ad2ed5b0ae Changing refs from /usr/bin to /usr/local/bin 2016-07-05 13:39:04 -04:00
Matthew Holt fdb6d64f9d Add locale plugin directive, update changelog 2016-07-02 18:07:24 -06:00
Matthew Holt 227664336e Misc. changes: {hostonly} placeholder, self_signed port fix 2016-07-02 14:11:17 -06:00
Matt Holt 32329a473d Merge pull request #909 from evermax/master
Test that the host header forwarding on proxy
2016-06-29 18:53:15 -06:00
Maxime e5bf8cab24 Test that the host header forwarding on the proxy middleware 2016-06-29 16:52:31 -07:00
Matt Holt 6db4771aa8 Merge pull request #907 from abiosoft/fastcgi_env_placeholder
Support for placeholders in fastcgi env vars.
2016-06-29 07:09:37 -06:00
Abiola Ibrahim b1cd0bfeff Support for placeholders in fastcgi env vars. 2016-06-29 13:41:52 +01:00
Matthew Holt 2e84fe4504 Replace auto-HTTPS info message and move a method to proper file 2016-06-28 23:01:06 -06:00
Matthew Holt d2be213e10 Import paths now relative to Caddyfile (closes #867)
This is inconsistent with the other directives, but import is a special
case and frankly the behavior of import shouldn't change depending
on the directory from which you run caddy. Breaking change but I think
it's for the better, and best to do it now before 1.0.
2016-06-28 22:39:29 -06:00
Matthew Holt a1bc94e409 Working on a fix for proxy related to setting Host header (cf. #874)
Also see
https://forum.caddyserver.com/t/caddy-0-9-beta-version-available-updated-beta-2/146/29?u=matt
which has another account of strange proxy behavior; I think this
resolves that.
2016-06-28 18:40:07 -06:00
Matthew Holt 80dd95a495 Change outreq.Host instead of r.Host (possibly related to #874)
Also a few little formatting changes and comments.
2016-06-28 18:19:35 -06:00
Matthew Holt 5a45719227 Don't change port when TLS is managed manually 2016-06-28 18:16:10 -06:00
Viacheslav Biriukov 345ece3850 add multi proxy supprot based on urls 2016-06-28 16:35:35 -06:00
Matthew Holt 2b44a7d052 Link to instructions if storage migration fails (#902) 2016-06-27 15:13:01 -06:00
Matthew Holt 58085edc16 Don't treat localhost specially when assigning bind address
If we listen on 127.0.0.1:80 for `localhost` but :80 for everything else,
then a hostname in the hosts file that resolves to 127.0.0.1 will be
served on :80 (unless the bind directive is used) but the OS will use
the socket listening at 127.0.0.1:80, thus giving a "No such site" error
even though the site is there, but it's on the other listener at :80.

Two ways to fix this: 1) Leave as-is and require the user to set "bind
127.0.0.1" in their Caddyfile for all sites that are resolved in the
hosts file, or 2) Take out this special case and let localhost sites
listen on :80 (unless the user changes that with the bind directive, of
course). Having localhost bind to any interface is a little annoying
(unsettling?) but probably best in the long run.

https://forum.caddyserver.com/t/wildcard-virtual-domains-with-wildcard-roots/221/9?u=matt
2016-06-27 13:14:28 -06:00
Matt Holt 6f05faa670 Merge pull request #900 from hacdias/patch-2
Add filemanager directive
2016-06-27 08:32:54 -06:00
Matt Holt eddb6f0a79 Merge pull request #905 from nesl247/hotfix/go-get-url
Fix go get url
2016-06-27 08:32:27 -06:00
Harrison Heck 70b75d1433 Fix go get url
Fixes #904
2016-06-26 14:40:13 -04:00
Matthew Holt 15fa5cf2da OnFirstStartup and OnFinalShutdown callbacks added
OnStartup and OnShutdown callbacks now run as part of restarts, too.
The startup and shutdown directives only run their commands NOT as part
of restarts, as before. Some middleware that use OnStartup may need to
switch to OnFirstStartup and implement OnFinalShutdown to do any cleanup
as needed.
2016-06-23 18:02:12 -06:00
Henrique Dias e74678ed43 Change hugo and filemanager order 2016-06-23 16:55:56 +01:00
Henrique Dias d84c823855 Add filemanager directive 2016-06-23 16:03:16 +01:00
Matthew Holt b49f65d5de 0.9 beta 2 2016-06-22 07:09:27 -06:00
Abiola Ibrahim fd8fe24bcb Merge pull request #899 from abiosoft/master
Minor refactor for rewrite.
2016-06-22 05:49:46 +01:00
Abiola Ibrahim 281603895b Minor refactor. 2016-06-22 05:36:29 +01:00
Matthew Holt fbad4e15c2 Fix browse template row hover effect for first row 2016-06-21 19:22:31 -06:00
Matthew Holt ab301fec00 Fix typo, formatting 2016-06-21 19:22:31 -06:00
Pedro Nasser deec149891 fix for new rewrite test case and add table test to replacer (#890)
* rewrite: fix new case
- added new test case and solution
- fix test case on rewrite_test

* replacer: change to table test
2016-06-21 18:44:16 -06:00
Abiola Ibrahim 9ca87cd139 Merge pull request #898 from abiosoft/master
Fix missed if_op refactor. Ensure with tests.
2016-06-21 20:52:51 +01:00
Abiola Ibrahim cad9b3f62f Fix missed if_op refactor. Ensure with tests. 2016-06-21 20:41:09 +01:00
Matthew Holt e585a74115 Revamped readme 2016-06-21 11:28:38 -06:00
Abiola Ibrahim d9b6563d88 Condition upgrades (if, if_op) for rewrite, redir (#889)
* checkpoint

* Added RequestMatcher interface. Extract 'if' condition into a RequestMatcher.

* Added tests for IfMatcher

* Minor refactors

* Refactors

* Use if_op

* conform with new 0.9 beta function changes.
2016-06-21 08:59:29 -06:00
Matthew Holt 0a3f68f0d7 Fix test on Windows (with 1 CPU) 2016-06-21 00:23:18 -06:00
Matthew Holt e625c7c051 Every package has a test 2016-06-21 00:11:55 -06:00
Matthew Holt 937654d1e0 Set host and port on address if specified via flag (fixes #888)
Also fixed a few typos and renamed caddyfile.ServerBlocks() to
caddyfile.Parse().
2016-06-20 18:25:42 -06:00
Matt Holt 33aba7eb91 Merge pull request #894 from RobbieMcKinstry/master
Refactoring to remove lint warnings
2016-06-20 18:02:27 -06:00
Robbie McKinstry d252d40681 Refactoring to remove lint 2016-06-20 19:11:29 -04:00
Mateusz Gajewski 81c4ea6be7 Add support for Alt-Svc headers (#892) 2016-06-20 13:50:25 -06:00
Matthew Holt 1fdc46e571 Fix tests after controller refactor
The search-and-replace was a little too aggressive and I accidentally
ran tests recursively in a subdirectory instead of repo's top folder.
2016-06-20 12:29:19 -06:00
Matthew Holt a798e0c951 Refactor how caddy.Context is stored and used
- Server types no longer need to store their own contexts; they are
  stored on the caddy.Instance, which means each context will be
  properly GC'ed when the instance is stopped. Server types should use
  type assertions to convert from caddy.Context to their concrete
  context type when they need to use it.
- Pass the entire context into httpserver.GetConfig instead of only the
  Key field.
- caddy.NewTestController now requires a server type string so it can
  create a controller with the proper concrete context associated with
  that server type.

Tests still need more attention so that we can test the proper creation
of startup functions, etc.
2016-06-20 11:59:23 -06:00
David Dyke 07b7c99965 Add timeout to health_check (#887)
* Add timeout to http get on health_check

* Add new test and up the timeout

* Tests for change to default timeout

* Only call http client once and make options more inline with current caddy directives
2016-06-20 09:49:21 -06:00
Matt Holt 807617965a Merge pull request #891 from andrewhamon/policy-cleanup
Refactor and clean up policy code
2016-06-20 09:27:48 -06:00
Andrew Hamon a50462974c Refactor and clean up policy code
This commit shouldn't change any behavior. It is simply a cleanup of
the different proxy policies. It also adds some comments explaining the
sampling method used, since on first inspection it might not appear to
be a uniformly random selection.
2016-06-18 15:41:18 -05:00
Pedro Nasser 6fe5c1a69f Merge pull request #885 from pedronasser/master
Fix rewrite bug with URL query + test case (#884)
2016-06-16 11:30:34 -03:00
Pedro Nasser 54355d8fb3 replace strings.Split for SplitN 2016-06-16 10:03:31 -03:00
Pedro Nasser e486c9c6e7 fix rewrite bug with url query + test case 2016-06-15 19:55:02 -03:00
Matt Holt 0f1e5bcebf Merge pull request #876 from hacdias/patch-1
Add minify directive
2016-06-14 15:44:04 -06:00
Andrew Hamon fee4890e94 Balance round robin evenly when some hosts are down (#880)
* Balance round robin evenly when some hosts are down

Before, when load balancing across multiple hosts, if a host went down
then the next host in line would be sent a double share of requests.
This is because the round robin counter was only incremented once per
request, regardless of the health of the selection. If current
selection was unhealthy then the policy would advance to the next host,
but this would not be reflected in the policy counter. To fix this, the
counter is now incremented for every attempted host.

This commit adds a test case that identifies the issue, and a fix.

* Make robin counter private

* Use a mutex to sync round robin selection
2016-06-14 15:43:06 -06:00
David Dyke b14baf7e20 Add proxy preset: transparent (#881)
* Add reverse_proxy preset

* Update to 'transparent' preset instead of 'reverse_proxy'
2016-06-14 12:03:30 -06:00
Matthew Holt 2b06edccd3 Use challenge domain for tls-sni solver
Matches the new upstream function signature and fixes previously broken
behavior; new solver code confirmed to work during restarts
2016-06-13 17:48:59 -06:00
Henrique Dias 492d5aa37f Merge branch 'master' into patch-1 2016-06-10 07:34:42 +01:00
Henrique Dias 1e4a4109a7 Update plugin.go 2016-06-10 07:31:07 +01:00
Matthew Holt daa4de572e Ensure certificate has a non-nil config when caching (fixes #875)
Also we change the scheme of the site's address if TLS is enabled and
no other scheme is explicitly set; this makes it appear as "https" when
we print it; otherwise it would show "http" when TLS is turned on
implicitly, and that is confusing/incorrect.
2016-06-09 19:12:11 -06:00
Henrique Dias 83451ea2a0 Update plugin.go 2016-06-09 16:06:50 +01:00
Henrique Dias 06fed0db17 Add minify directive 2016-06-09 15:14:46 +01:00
Matt Holt 98cf26377c Merge pull request #873 from mholt/fix-restart
Fix restart on USR1 not loading new Caddyfile
2016-06-08 17:40:16 -06:00
Benny Ng ff82057131 Fix restart on USR1 not loading new Caddyfile 2016-06-09 07:12:01 +08:00
Matthew Holt 6492592b4a Update change list, fix build script; version 0.9 beta 1 2016-06-07 14:33:06 -06:00
Gustavo Chaín 6c847d0723 New {request} placeholder to log entire requests (sans body) (#871)
Add a {request} placeholder to the replacer.

Closes #858.
2016-06-07 11:06:24 -06:00
Matt Holt 01e05afa0c Merge pull request #870 from mholt/close-idle-conn
Close idle connections after graceful shutdown timeout
2016-06-07 09:38:58 -06:00
Matthew Holt e7fc26e3fb Improved godoc, added two missing directives, update change log 2016-06-07 09:27:14 -06:00
Benny Ng 37ae21001d Close idle connections after graceful shutdown timeout 2016-06-07 18:32:27 +08:00
Benny Ng b23eec4fac Update go get path for Caddy binary (#869) 2016-06-07 12:09:52 +08:00
Matthew Holt 727ef24306 Improve godoc for plugin facilities 2016-06-06 18:27:54 -06:00
Matthew Holt d3860f95f5 Make RegisterPlugin() more consistent, having name as first argument 2016-06-06 15:31:03 -06:00
Matt Holt 9b4134b287 Merge pull request #866 from mholt/0.9-wip
Merge 0.9 into master (warning: huge diff)
2016-06-06 07:39:16 -06:00
Leo Koppelkamm ddff08392a Make upstart script more fault tolerant and easier to debug (#824)
* Make Upstart script more fault tolerant and easier to debug

* update readme
2016-06-06 07:32:27 -06:00
Matthew Holt 71c14fa16e Make sure Root is set for all new SiteConfigs
This situation typically only arises in tests...
2016-06-05 23:34:16 -06:00
Matthew Holt ff22fbd79a Migrate existing add-on names; set default root in SiteConfig 2016-06-05 23:24:34 -06:00
Matthew Holt a762dde145 Migrate remaining middleware packages 2016-06-05 22:39:23 -06:00
Matthew Holt 416af05a00 Migrating more middleware packages 2016-06-05 21:51:56 -06:00
Matthew Holt 2f92443de7 More tests, several fixes and improvements; export caddyfile.Token
We now sneakily chain in the errors directive if gzip is present but
not errors. This change fixes #616.
2016-06-04 22:50:23 -06:00
Matthew Holt 49fdc6a20a Add errors middleware; export httpserver.WriteTextResponse 2016-06-04 22:48:27 -06:00
Matthew Holt ac4fa2c3a9 Rewrote Caddy from the ground up; initial commit of 0.9 branch
These changes span work from the last ~4 months in an effort to make
Caddy more extensible, reduce the coupling between its components, and
lay a more robust foundation of code going forward into 1.0. A bunch of
new features have been added, too, with even higher future potential.

The most significant design change is an overall inversion of
dependencies. Instead of the caddy package knowing about the server
and the notion of middleware and config, the caddy package exposes an
interface that other components plug into. This does introduce more
indirection when reading the code, but every piece is very modular and
pluggable. Even the HTTP server is pluggable.

The caddy package has been moved to the top level, and main has been
pushed into a subfolder called caddy. The actual logic of the main
file has been pushed even further into caddy/caddymain/run.go so that
custom builds of Caddy can be 'go get'able.

The HTTPS logic was surgically separated into two parts to divide the
TLS-specific code and the HTTPS-specific code. The caddytls package can
now be used by any type of server that needs TLS, not just HTTP. I also
added the ability to customize nearly every aspect of TLS at the site
level rather than all sites sharing the same TLS configuration. Not all
of this flexibility is exposed in the Caddyfile yet, but it may be in
the future. Caddy can also generate self-signed certificates in memory
for the convenience of a developer working on localhost who wants HTTPS.
And Caddy now supports the DNS challenge, assuming at least one DNS
provider is plugged in.

Dozens, if not hundreds, of other minor changes swept through the code
base as I literally started from an empty main function, copying over
functions or files as needed, then adjusting them to fit in the new
design. Most tests have been restored and adapted to the new API,
but more work is needed there.

A lot of what was "impossible" before is now possible, or can be made
possible with minimal disruption of the code. For example, it's fairly
easy to make plugins hook into another part of the code via callbacks.
Plugins can do more than just be directives; we now have plugins that
customize how the Caddyfile is loaded (useful when you need to get your
configuration from a remote store).

Site addresses no longer need be just a host and port. They can have a
path, allowing you to scope a configuration to a specific path. There is
no inheretance, however; each site configuration is distinct.

Thanks to amazing work by Lucas Clemente, this commit adds experimental
QUIC support. Turn it on using the -quic flag; your browser may have
to be configured to enable it.

Almost everything is here, but you will notice that most of the middle-
ware are missing. After those are transferred over, we'll be ready for
beta tests.

I'm very excited to get this out. Thanks for everyone's help and
patience these last few months. I hope you like it!!
2016-06-04 17:00:29 -06:00
Matt Holt e1a6b60736 Merge pull request #861 from marc-gr/upstream
Add upstream directive
2016-06-03 17:50:33 -06:00
Marc Guasch 9b5ad487d7 Add several upstreams test case 2016-06-04 01:07:53 +02:00
Marc Guasch d291b76721 Gofmt fixes 2016-06-03 22:48:24 +02:00
Marc Guasch 881da313dd Add tests for upstream directive 2016-06-03 21:36:15 +02:00
Marc Guasch 1bdbf9d6ba Add parseUpstream method 2016-06-03 21:36:15 +02:00
Matt Holt 2536ea74d9 Merge pull request #859 from jupiter/fix/issue-643
Fix for cleaned URL.Path
2016-06-02 16:43:34 -06:00
Pieter Raubenheimer 9acfec5418 Fix cleaned URL path in proxy 2016-06-02 22:08:10 +01:00
Abiola Ibrahim a0e6eb3ba9 Merge pull request #855 from abiosoft/proxy-host-patch
Fix for #854
2016-05-29 08:11:27 +01:00
Abiola Ibrahim 6e6d9e7e9e Fix for #854 2016-05-29 07:55:40 +01:00
Matthew Holt 238250e7e6 Recommend forum in issue template 2016-05-27 10:16:11 -06:00
Matthew Holt 324ec15890 Link to forum 2016-05-27 09:15:06 -06:00
Matthew Holt c64361a753 Use latest Go version (1.6.2) for CI builds 2016-05-24 18:37:44 -06:00
Matt Holt 6d9dcb1729 Merge pull request #846 from nesl247/fix-845
Strip [] from IPv6 addresses for FastCGI.
2016-05-24 13:56:55 -06:00
Harrison Heck da97ac7c63 Strip [] from IPv6 addresses for FastCGI.
Fixes #845.
2016-05-24 15:19:50 -04:00
Matt Holt 374d0a3f09 Merge pull request #840 from mholt/more-systemd
systemd, README: Edit to account for the recent spike in reports
2016-05-21 14:44:58 -06:00
W-Mark Kubacki bee9c50a71 systemd, README: needs to be version 229 or later, and how to display logs
We have had three operators within a few days which ran into the same cause
and had not been able to figure out what went wrong.

addresses #833, #822
2016-05-21 00:54:44 +02:00
Matthew Holt bac29cc20a Change image http -> https 2016-05-15 08:26:20 -04:00
Matt Holt e6b1028da9 Merge pull request #823 from ponychicken/patch-1
fix typo
2016-05-14 17:20:19 -04:00
Leo Koppelkamm 4c62397ff8 fix typo 2016-05-14 16:39:00 +02:00
W. Mark Kubacki e516aebc08 Merge pull request #817 from mholt/systemd-service-file
Provides some more guidelines to operators on how to avoid running Caddy as root.

Introduces an user www-data, which really is a placeholder. Such an user with the same UID/GID combination is created on the most popular Linux distribution. I trust any operator can spot the difference to his/her distro and adjust the unit file.

User nobody is not used here to avoid two easy pitfalls: Such an user should not be able to access private keys (for TLS), and should not write private keys (we would do that with Letsencrypt).
2016-05-12 15:15:32 +02:00
W-Mark Kubacki da8ae9e511 systemd: Run caddy with even less privileges and more confined
The exemplary unit file for systemd is intentionally redundant at times, for
example dropping privileges which an unprivileged user "www-data" did not have
in the first place: To aid as fallback in case the file gets copied and an
operator setting UID to 0 (which reportedly happened in the past).
2016-05-12 15:11:43 +02:00
W-Mark Kubacki d377c79a5d systemd, README: Edit for clarity 2016-05-12 15:08:06 +02:00
W. Mark Kubacki 389a6eb344 Merge pull request #811 from mholt/fix-browse-2
browse: Decorate external links with: noopener noreferrer
2016-05-07 20:19:22 +02:00
W-Mark Kubacki 85d793ce88 browse: Decorate external links with: noopener noreferrer
Setting these on external links prefents the target from manipulating this
page by "window.opener" with some widely deployed browsers.
2016-05-07 20:06:55 +02:00
Matt Holt 1f29c52151 Merge pull request #807 from mholt/graceful-inproc-restart
Restart gracefully with in-process restart
2016-05-07 08:58:18 -06:00
Benny Ng 9705f34970 Restart gracefully for in-process restart 2016-05-07 13:39:47 +08:00
Matt Holt db21b0319d Merge pull request #804 from kennwhite/kennwhite-readme-patch
Adding note on the rationale for Go 1.6
2016-05-05 08:29:14 -06:00
kennwhite 25b934824f Adding note on the rationale for Go 1.6 2016-05-05 10:18:00 -04:00
Matt Holt c23c6d9cb4 Merge pull request #762 from weingart/md_changes
Markdown changes
2016-05-04 23:29:48 -06:00
Tobias Weingartner 9697c47e21 Merge branch 'master' into md_changes 2016-05-03 19:14:35 -07:00
Achim Vedam 39030d9e1b browse: delete duplicate </a>-closing tags in default template 2016-05-03 20:10:38 +02:00
Tobias Weingartner 61c7a51bfa Errant commented import. 2016-05-01 16:50:25 -07:00
Tobias Weingartner 45e783c3f9 Fix headers and unexport plaintext renderer. 2016-05-01 16:45:24 -07:00
Tobias Weingartner 2bccc1466e Fixup and address most of the feedback. 2016-04-30 20:09:25 -07:00
Tobias Weingartner a3af232dc5 Use http.MethodGet instead of "GET". 2016-04-30 17:06:46 -07:00
Tobias Weingartner 04089c533b Return errors. 2016-04-30 17:04:44 -07:00
Tobias Weingartner e0bc426050 Initial re-add of markdown summary functionality. 2016-04-30 17:01:31 -07:00
Tobias Weingartner bd2a33dd14 Europeans know time. :) 2016-04-30 14:21:30 -07:00
Tobias Weingartner 20dfaab703 Nuke unused function. 2016-04-30 14:16:19 -07:00
Tobias Weingartner 249c9a17f5 Make default template more readable/clean. 2016-04-30 14:07:42 -07:00
Tobias Weingartner c431a07af5 Merge branch 'master' into md_changes 2016-04-30 13:33:47 -07:00
William Bezuidenhout e2234497b7 proxy: Add, remove, or replace upstream and downstream headers (closes #666) (PR #788)
* Overwrite proxy headers based on directive

Headers of the request sent by the proxy upstream can now be modified in
the following way:

Prefix header with `+`: Header will be added if it doesn't exist
otherwise, the values will be merge
Prefix header with `-': Header will be removed
No prefix: Header will be replaced with given value

* Add missing formating directive reported by go vet

* Overwrite up/down stream proxy headers

Add Up/DownStreamHeaders to UpstreamHost

Split `proxy_header` option in `proxy` directive into `header_upstream`
and `header_downstream`. By splitting into two, it makes it clear in
what direction the given headers must be applied.

`proxy_header` can still be used (to maintain backward compatability)
but its assumed to be `header_upstream`

Response headers received by the reverse proxy from the upstream host
are updated according the `header_downstream` rules.
The update occurs through a func given to the reverse proxy, which is
applied once a response is received.

Headers (for upstream and downstream) can now be modified in
the following way:

Prefix header with `+`: Header will be added if it doesn't exist
otherwise, the values will be merge
Prefix header with `-': Header will be removed
No prefix: Header will be replaced with given value

Updated branch with changes from master

* minor refactor to make intent clearer

* Make Up/Down stream headers naming consistent

* Fix error descriptions to be more clear

* Fix lint issue
2016-04-30 13:41:30 -06:00
Matthew Holt 96425f0f40 Fix vet failing on Go tip 2016-04-30 11:13:04 -06:00
Matthew Holt d05dac8d2e Little bit of housekeeping 2016-04-27 12:52:00 -06:00
Matt Holt 81e26970a3 proxy: Move handling of headers around to prevent memory usage spikes (#784)
* Move handling of headers around to prevent memory use spikes

While debugging #782, I noticed that using http2 and max_fails=0,
X-Forwarded-For grew infinitely when an upstream request failed after
refreshing the test page. This change ensures that headers are only
set once per request rather than appending in a time-terminated loop.

* Refactor some code into its own function
2016-04-27 12:50:18 -06:00
W. Mark Kubacki f561dc0bc1 browse: Serve a datetime format the IE11 and Firefox understand
Another new safeguard is that we check whether the datetime has been read
correctly. If not then the listing will not be localized.

closes #793
2016-04-27 19:24:42 +02:00
Matthew Holt 21382702d2 Update readme version, 0.8.3 2016-04-26 09:28:05 -06:00
Matthew Holt e97649493b Update change log; version 0.8.3 2016-04-26 08:36:59 -06:00
Matthew Holt 19d6f666aa Fix build script with default git repo path (cwd) 2016-04-25 22:10:16 -06:00
W. Mark Kubacki 6f5cff5393 tls: Prevent Go stdlib from overwriting our very first tls ticket key (#785)
[1] https://github.com/golang/go/blob/57e459e02b4b01567f92542f92cd9afde209e193/src/crypto/tls/common.go#L424
[2] https://github.com/golang/go/blob/57e459e02b4b01567f92542f92cd9afde209e193/src/crypto/tls/common.go#L392-L407

[2] has overwritten the first tls ticket key on round N=0, that has previously
been written using [1].

Go's stdlib does not use c.sessionTicketKeys≥1 as indicator if those values had
already been set; initializing that lone SessionTicketKey does the job for for
now.
    If c.serverInit() were called in round N+1 all existing tls ticket keys
would be overwritten (in round N<4 except the very first one, of course).
As member variables of tls.Config are read-only by then, we cannot keep
updating SessionTicketKey as well.

This has been escalated to Go's authors with golang/go#15421 here:
https://github.com/golang/go/issues/15421

Thanks to Matthew Holt for the initial report!
2016-04-23 17:49:48 +02:00
Matthew Holt 5c96ee1d9c Fix bug in renewing default certificate 2016-04-22 10:14:56 -06:00
W. Mark Kubacki 3c578dfbc1 Catch whitespace code style violations locally (#774)
Those settings enforce convergence on common coding style with respect to whitespace.

Do not use tabs to indent with shell scripts because those tabs most often
serve the function of triggering command completion. Which could end a
command before it is pasted completely.
  Traditionally indentation is two spaces here, not four.

Other rules will catch stray whitespace at the end of lines or files, which,
once committed, would annoy the next developer because his editor would strip
them from lines he did not intended to modify in the first place.
2016-04-20 16:56:57 +02:00
Abiola Ibrahim a093aea797 Merge pull request #773 from eliasp/patch-1
Typ (creatation → creation)
2016-04-20 14:52:14 +01:00
Elias Probst 9f1762873a Typ (creatation → creation) 2016-04-20 13:08:31 +02:00
Matt Holt c3417a0757 Merge pull request #772 from mholt/fix-browse
Make Browse Great Again ★★★
2016-04-19 10:16:08 -06:00
W-Mark Kubacki 72bc6932b0 browse: Jail the root directory using http.Dir() 2016-04-19 03:48:27 +02:00
W-Mark Kubacki a41e3d2515 browse: Use JavaScript to localize the shown datetime
The datetime format is whatever the user sets in his/her browser or OS.

This converts timezones.

Tested with Chrome 50 and Internet Explorer 11.
2016-04-19 03:48:27 +02:00
W-Mark Kubacki 7f35600b28 browse: Emit datetime in UTC instead of the server's timezone
Makes sure the view is the same no matter where a site is hosted.
2016-04-19 00:56:47 +02:00
W-Mark Kubacki cc6aa6b54b browse: Remove whitespace from template's output, annotate output
Fixes a surplus — next to "go up".

Identifies the preamble as the table's summary.

Emits filesizes in bytes, which can be consumed by any browser-side scripts
or utilized in sorting when the table is copy-and-pasted into a spreadsheet
software.

Uses <time> along with proper datetime representation, which a browser could
utilize to display the datetime rendered according to the requestor's locale.
2016-04-19 00:20:44 +02:00
W-Mark Kubacki 239f6825f7 browse: When sorting by size, offset directories
Assigns negative sizes to directories in order to have them listed reliably
before any zero-sized files. That order is what most users expect when
sorting by size.

As side effect directories will appear before files on all filesystem
implementations. To give an example: before this change directories had a size
of 4 KiB when using Linux with ext4 or tmpfs, and with ZFS a size resembling
an estimation of the number of leaves within said directory.
2016-04-19 00:20:44 +02:00
W-Mark Kubacki 1d38d113f8 browse: Move predicate 'limit' to ServeListing
This keeps the interface of all available formatters honest,
and allows for truncated listings all formats.
2016-04-19 00:20:44 +02:00
W-Mark Kubacki 6908136092 browse: Split ServeHTTP into small specialized functions 2016-04-19 00:20:44 +02:00
Matt Holt da016f8d5a Merge pull request #760 from Burmudar/hostname-placeholder
Add hostname placeholder. Headers use replacer
2016-04-18 15:54:31 -06:00
W. Mark Kubacki 2f2d357fb6 browse: Fix known bugs (#770)
* browse: Catch the case of a directory disappearing before having been read

* browse: Revert to old pass-through behaviour

PROPFIND is a request for an alternate view on a directory's contents, which
response is indeed not implemented but ideally allowed to ask for.
OPTIONS would ideally return (at least) what methods the requestor could use,
which is an allowed request method, too.

This addresses #767.
2016-04-18 11:42:36 -06:00
Matthew Holt 924b53eb3c Minor changes 2016-04-18 09:43:28 -06:00
Tobias Weingartner 2b51be7fd7 Better error message. (#768)
* Fix PrivateKeyBytes to error out and fail tests on error.

* Better error message.
2016-04-17 15:15:14 -06:00
Tobias Weingartner 376e1090a3 Fix PrivateKeyBytes to error out and fail tests on error. 2016-04-17 14:17:42 -06:00
Tobias Weingartner dd4de698cf Better search for config.
Handle LastModifiedHeader better.
Handle HEAD/GET.
2016-04-17 10:58:51 -07:00
elcore a682100c5e Merge pull request #765 from mholt/elcore-error-ecdsa
Error if we are unable to marshal the ECDSA private key
2016-04-17 18:40:02 +02:00
elcore aba3d37c88 Error if we are unable to marshal the ECDSA private key 2016-04-17 18:19:41 +02:00
W. Mark Kubacki 0890e330e2 Merge pull request #759 from mholt/paths-cleanup
Tell usage of 'path' from 'filepath' and fix *path checking
2016-04-17 14:16:57 +02:00
Tobias Weingartner 19a85d08c6 Merge branch 'master' into md_changes 2016-04-16 19:43:50 -07:00
Tobias Weingartner 5a0d373fcd Missed fixing setup. 2016-04-16 17:13:13 -07:00
Tobias Weingartner ecf91f525f Fix gofmt -s and ineffassign. 2016-04-16 17:07:06 -07:00
Tobias Weingartner b541c717ca Add ability to markdown a directory with a template. 2016-04-16 16:50:45 -07:00
W-Mark Kubacki c05c5163e2 browse: Don't leak Cookies to sessions in HTTP from HTTPS 2016-04-17 01:16:17 +02:00
W-Mark Kubacki 3513b6f2f7 fileserver: When out of filedescriptors, spread retry attempts 2016-04-17 01:16:17 +02:00
W-Mark Kubacki 4a6121f989 Tell usage of 'path' from 'filepath' and fix *path checking 2016-04-17 01:16:16 +02:00
Tobias Weingartner e652d12cfc Move Metadata load into NewMetadata function. 2016-04-16 15:36:52 -07:00
Tobias Weingartner b97a7909d8 Nuke more redundant things. 2016-04-16 15:18:17 -07:00
Tobias Weingartner 7c9867917a Fixup tests and move metadata into own package 2016-04-16 14:45:32 -07:00
William Bezuidenhout a762bec06d Add hostname placeholder. Header uses replacer
On matched header rules, replacer is used to replace any placeholders
defined in header rules iex. X-Backend {hostname} where {hostname} will
be replaced by the hostname key present in the replacer

hostname key added to replacer. The value is determined by the output of
`os.Hostname()`
2016-04-16 19:06:39 +02:00
Matthew Holt b75016e646 Fix lint warning 2016-04-15 15:13:44 -06:00
W. Mark Kubacki ddf4b1fd3b Merge pull request #757 from mholt/extend-tls-client-auth
Extend tls client auth
2016-04-15 22:57:45 +02:00
W-Mark Kubacki 69c2d78f69 Support configuring less restrictive TLS client auth requirements
Caddyfile parameter "clients" of "tls" henceforth accepts a special
first modifier. It is one of, and effects:

 * request         = tls.RequestClientCert
 * require         = tls.RequireAnyClientCert
 * verify_if_given = tls.VerifyClientCertIfGiven
 * (none)          = tls.RequireAndVerifyClientCert

The use-case for this is as follows: A middleware would serve items to the
public, but if a certificate were given the middleware would permit file
manipulation.

And, in a different plugin such as a forum or blog, not verifying a client
cert would be nice for registration: said blog would subsequently only
compare the SPKI of a client certificate.
2016-04-15 22:21:55 +02:00
W-Mark Kubacki f31875dfde Move sanitization of URL.Path to Server
No need to have this in every plugin. And, even in flat filesystems
filenames with dots and slashes are best avoided.
2016-04-15 21:02:36 +02:00
W-Mark Kubacki 4e98cc3005 browse: Return HTTP errors on unhandled HTTP methods
For example, a HTTP POST should not be answered with StatusOK,
and a response to HTTP OPTIONS should not carry any contents.
2016-04-15 18:22:51 +02:00
Matt Holt d3a77ce3c3 Use binExt 2016-04-13 15:21:18 -06:00
Tobias Weingartner 48d294a695 Better default template. 2016-04-12 18:53:15 -07:00
W. Mark Kubacki b149a86bc2 server: Rotate TLS ticket "keys" (#742) 2016-04-12 10:09:45 -06:00
Matt Holt ac80f6edc3 Merge pull request #744 from tboerger/feature/breadcrumb
browse: Dropped LinkedPath and updated browse template
2016-04-12 10:01:01 -06:00
Thomas Boerger ef95173827 Dropped LinkedPath and updated browse template
As discussed with @mholt I have dropped the old LinkedPath function and
replaced it within the browse template with the new BreadcrumbMap
function. Visually it looks exactly the same as before, now the template
functionality is just more powerful.

Signed-off-by: Thomas Boerger <tboerger@suse.de>
2016-04-12 17:18:21 +02:00
Matt Holt 36a3e204b6 Merge pull request #740 from tboerger/feature/browse-breadcrumb
Added breadcrumb map function to browse
2016-04-11 14:08:18 -06:00
Thomas Boerger e0b63d92f4 Added breadcrumb map function to browse
In order to being able to really build a custom template for the browse
directive I have added another function to build even custom breadcrumb
paths. The other function `LinkedPath` is not that easy styleable as
this map function. That way we are able to build the breadcrumb path
matching different CSS frameworks like Bootstrap.

Signed-off-by: Thomas Boerger <thomas@webhippie.de>
2016-04-11 21:47:44 +02:00
Thomas Boerger 004a7f84ef Make test case less dependent on exact error string (#741) 2016-04-11 13:08:30 -06:00
Matt Holt ed8a48e7f1 Merge pull request #739 from tboerger/feature/browse-go-up
Added "go up" link to browse template
2016-04-11 12:28:13 -06:00
Thomas Boerger c86c26a056 Added "go up" link to browse template
In order to have directly a link within the browse listing I have added
a link to the top of the table to get one level up in the tree. Added
that after a chat with @mholt.

Signed-off-by: Thomas Boerger <thomas@webhippie.de>
2016-04-11 20:10:41 +02:00
Matt Holt 0a7ca64f53 gzipResponseWriter should also be a Flusher
To be consistent with 3faad41b43 and c64cf218b0
2016-04-11 00:24:26 -06:00
Matt Holt c4e2cf96e7 Merge pull request #737 from tw4452852/closeNotifier
http.CloseNotifier implementation for http.ResponseWriter wrapper
2016-04-11 00:18:06 -06:00
Tobias Weingartner 42b7d57421 Fix more tests, and fix template parsing. 2016-04-10 23:17:01 -07:00
Tw c64cf218b0 http.CloseNotifier implementation for http.ResponseWriter wrapper
Signed-off-by: Tw <tw19881113@gmail.com>
2016-04-11 14:10:33 +08:00
Tobias Weingartner 027f697fdf Revamp markdown processing.
Nuke pre-generation.  This may come back in the form of a more general
caching layer at some later stage.

Nuke index generation.  This should likely be rethought and re-implemented.
2016-04-10 17:47:55 -07:00
Matt Holt 6a7b777f14 panic if not a Flusher
Caddy recovers panics that occur in the middleware stack so this is not a risk to process termination. This way is also preferable to hiding the error. See https://github.com/mholt/caddy/commit/3faad41b437c48cea37863123fab425169bc0c6e#commitcomment-17035158
2016-04-09 22:11:36 -06:00
Matthew Holt 67b137175e Replaced automate.sh with Go program 2016-04-09 10:02:16 -06:00
Matthew Holt dfa3b8645d Who uses 32-bit Mac anyway. :P 2016-04-09 00:40:37 -06:00
Matthew Holt 2dca50dee8 Rewrite automate.sh as Go program; add init folder to release archives
Easier parallelism and more control over platforms we build for, but
more importantly, we can do parallel builds using the build script which
properly embeds version information into the binaries. We also produce
the archive files ourselves and in parallel rather than using external
tar and zip commands.
2016-04-09 00:21:55 -06:00
Matthew Holt 3faad41b43 middleware: ResponseRecorder now is an http.Flusher (fixes #677)
Flush every 250ms. This should keep latency somewhat low but there is
still buffering; maybe in the future we can make this configurable.
2016-04-09 00:17:15 -06:00
Matthew Holt c21ff8343c Apparently vet ships with Go now 2016-04-06 17:29:53 -06:00
Matt Holt 2072eec11f Merge pull request #731 from wjkohnen/host-caseinsensitive
Handle host names case insensitively.
2016-04-06 15:56:34 -06:00
Wolfgang Johannes Kohnen 497ebb9ccb Handle host names case insensitively.
RFC 3986 3.2.2: The host subcomponent is case-insensitive.
2016-04-06 21:36:46 +00:00
Benny Ng e4e773c9ea Merge pull request #730 from tpng/restart-refactor
Extract restartInProc to its own file
2016-04-06 11:51:44 +08:00
Benny Ng 32e63e6b94 Extract restartInProc to its own file 2016-04-06 11:40:39 +08:00
Matthew Holt 86ccafbe58 Update changes
Also testing commit signing again, different email this time.
2016-04-05 19:04:08 -06:00
Matthew Holt 3ef78d3db3 Allow git repo as argument to build script
This will help build server perform builds with copy of repo in a folder
outside the original git repo folder. Also testing signed commits on
GitHub.
2016-04-05 18:57:50 -06:00
elcore 9ec1c17846 Merge pull request #727 from mholt/rename-keytype-ec-to-p
Rename EC256,EC384 to P256,P384
2016-04-05 19:59:37 +02:00
Eldin Hadzic 7ababfc909 Rename EC256,EC384 to P256,P384 2016-04-05 13:37:39 +00:00
Benny Ng 31062dd6c2 Merge pull request #725 from tpng/reload-validate-caddyfile
revert to old Caddyfile on failed in-process restart
2016-04-05 20:07:48 +08:00
Benny Ng b952fd8f8f revert to old Caddyfile on failed in-process restart 2016-04-05 19:57:52 +08:00
Benny Ng 28e0bfbbbe update README to include Docker
update README to include Docker container zzrot/alpine-caddy
2016-04-05 17:24:33 +08:00
Sean Kilgarriff 30ce73e8fb updated README to include ZZROT's docker container that runs caddy on Alpine Linux inside of a Docker Container. 2016-04-05 03:11:55 -04:00
elcore 987a5f98c4 Merge pull request #723 from mholt/fix-#721
Fix for #721
2016-04-05 04:54:49 +02:00
Eldin Hadzic 859a93d296 Fix for #721 2016-04-04 23:59:40 +00:00
Matt Holt a14fce0b1e Merge pull request #707 from jupiter/caching-headers
Add Etag header
2016-04-03 15:01:33 -06:00
Matt Holt 25b567b301 Merge pull request #715 from mholt/elcore-faster-tests
https: Faster tests
2016-04-03 14:33:03 -06:00
Matt Holt 3d066789d3 Merge pull request #713 from 5an1ty/patch-1
proxy: Allow mixed case Upgrade headers (WebSocket fix)
2016-04-03 14:30:32 -06:00
Pieter Raubenheimer 93d982a5a4 Improve readability and fully comply with RFC7232 2016-04-03 20:14:10 +01:00
Pieter Raubenheimer 572b9e4d67 Merge branch 'master' into caching-headers 2016-04-03 19:59:25 +01:00
elcore 2a82f7b520 Faster tests 2016-04-03 20:06:21 +02:00
Ruben Callewaert 1a9f700287 Allow mixed case Upgrade headers
Caddy expects websocket to be completely lowercase.
Some applications send websocket upgrade headers like the following:
`Upgrade: WebSocket`

This change allows all variations of websocket.
2016-04-03 17:48:53 +02:00
Matt Holt 462128cd80 Merge pull request #706 from elcore/patch-3
tls: Customize key type with key_type
2016-04-02 13:40:49 -06:00
elcore cf69d190a2 A new feature for the "tls" directive 2016-04-02 21:15:18 +02:00
Pieter Raubenheimer 3441cdef64 Add Etag header 2016-04-02 08:51:08 +01:00
Matt Holt 8a2f2f8d37 Merge pull request #705 from DenBeke/upstart-conf
dist/init: caddy.conf for upstart
2016-03-31 08:25:48 -06:00
MathiasB 86854dca89 dist/init: caddy.conf for upstart 2016-03-31 14:36:44 +02:00
Matt Holt b3a5b725db Merge pull request #696 from mholt/templateUtils
templates: Adding some useful utility functions
2016-03-28 21:17:11 -06:00
Craig Peterson f28d8b8601 Adding some useful utility functions for templates 2016-03-28 20:53:51 -06:00
Matt Holt 5989eb0635 Merge pull request #699 from eiszfuchs/socket-url
proxy: fix req.URL.Path for unix sockets
2016-03-28 14:31:53 -06:00
eiszfuchs cbd9b814b9 fixed req.URL.Path for unix: sockets 2016-03-28 22:18:14 +02:00
Matthew Holt 32dbbfd64c Better way of checking gofmt 2016-03-28 10:52:48 -06:00
Matt Holt 3395f6c775 Merge pull request #698 from buddhamagnet/master
Correct unused assignments
2016-03-26 17:28:46 -06:00
buddhamagnet 61cf8b79bc drop unused md variable 2016-03-26 21:14:54 +00:00
buddhamagnet bb6764fd22 drop unused len variable 2016-03-26 21:14:22 +00:00
buddhamagnet c981b08b23 correct unused assignments 2016-03-26 21:05:19 +00:00
Matthew Holt 7271b57136 Add golint and ineffassign to CI scripts
golint is not part of the tests since our Markdown dependency defines
an interface that is not lint-compliant (unfortunately).
See https://github.com/russross/blackfriday/issues/240
2016-03-26 14:26:12 -06:00
Matthew Holt 874bcff564 Fix YAML syntax. Sigh... 2016-03-26 09:23:05 -06:00
Matthew Holt eb279e7e8a Perform gofmt -s check 2016-03-26 08:53:00 -06:00
Matt Holt f4c729bd22 Merge pull request #691 from MariadeAnton/MdA-travis-test
Only bypass "bandwidth limit exceeded" errors on pushes
2016-03-22 09:27:20 -06:00
María de Antón ea35893be4 run bash dist/gitcookie.sh step only on build pushes 2016-03-22 16:15:17 +01:00
María de Antón 1efd1029dd Only bypass "bandwidth limit exceeded" errors on pushes 2016-03-22 16:08:29 +01:00
Matthew Holt 426d165254 expvar: Allow no args; publish number of goroutines 2016-03-21 22:39:57 -06:00
Matt Holt a3127bed5f Merge pull request #285 from mem/master
Add expvar middleware
2016-03-21 21:54:18 -06:00
Marcelo E. Magallon b94e513116 Add expvar middleware
Right now it has a very simple configuration:

	expvar /debug/vars

It will return a JSON object with memory statistics and the command line
used to start caddy, which are the two expvars that expvar registers by
default.
2016-03-21 21:46:41 -06:00
Matthew Holt b6e5a599fb Update change log 2016-03-21 12:36:27 -06:00
Matt Holt 8fc35edc3b Merge pull request #689 from tpng/restart-inproc
Add -restart=inproc option for in-process restart
2016-03-21 12:32:59 -06:00
Benny Ng 260c023e1e Add -restart=inproc flag for in process restart 2016-03-22 02:25:32 +08:00
Matthew Holt 27f9b58c5d Bypass "bandwidth limit exceeded" errors when cloning from googlesource
cf. golang/go#12933
2016-03-21 12:05:16 -06:00
Matthew Holt f23d8cb37f Add {upstream} placeholder when proxy middleware is used (closes #531)
Middlewares can now make their own placeholders that may be useful in
logging, on a per-request basis. Proxy is the first one to do this.
2016-03-20 21:56:13 -06:00
Matthew Holt 3f49b32086 Revert undesired changes to shell scripts 2016-03-20 14:13:50 -06:00
Matt Holt 0aacaea918 Merge pull request #686 from wmark/for-mholt
Reflow all bash scripts
2016-03-20 14:04:44 -06:00
Matt Holt 9e0b1b4216 Merge pull request #687 from abiosoft/fastcgi-except
fastcgi: Add `except` to FastCGI. Minor refactor in proxy.
2016-03-20 13:42:14 -06:00
Abiola Ibrahim e7001e6538 Add except to FastCGI. Minor refactor in proxy. 2016-03-20 08:02:17 +01:00
Matthew Holt 4d9741dda6 pprof: Only handle if path matches /debug/pprof, add tests 2016-03-19 20:02:05 -06:00
W-Mark Kubacki 74a5cb2fe3 Convert the barbarism in dist/automate.sh to proper BASH structure
When thy variables henceforth accept blessed white-space,
    guided will thy scripture be along righteous path(s).

    -- 4 BASH 3:42

Caddy's dist files sometimes ended up being owned by matt:staff or other
quite arcane and/or frightening names. If someone extracting didn't pay
attention a regular user who happened to have same uid by accident could
later tamper with the files' contents. It's 0:0 from now on.

Use all available threads when packaging distributables
Caddy binaries will be added to their archives in-place: This change
eliminates them being renamed within dist/builds one after another.
As does 'gox', dist/automate.sh will spare one available thread if possible.
2016-03-20 01:33:58 +01:00
W-Mark Kubacki ba2e9d80fd Use best practices in build.bash
Format of main.buildDate has been locale-dependent,
and is now ISO-8601 compliant.

Caddy displayed with ```-version``` something like (mind the datetime format):

    Caddy 0.8.2 (+591b209 Fri Mar 18 21:22:55 UTC 2016)
     2 files changed, 9 insertions(+), 4 deletions(-)
    build.bash
    main.go

which is now:

    Caddy 0.8.2 (+591b209 2016-03-18 21:22:55Z)
     2 files changed, 9 insertions(+), 4 deletions(-)
    build.bash,main.go

See also:

 * http://wiki.bash-hackers.org/scripting/obsolete
 * https://google.github.io/styleguide/shell.xml
 * https://xkcd.com/1179/
2016-03-19 17:04:53 +01:00
Abiola Ibrahim a05a664d56 Merge pull request #679 from abiosoft/case-insensitive-fs
Support for case insensitive paths using CASE_SENSITIVE_PATH env var.
2016-03-19 08:53:18 +01:00
Abiola Ibrahim 9f9fbf2e1b Support for case insensitive paths using CASE_SENSITIVE_PATH environment variable. 2016-03-19 08:45:23 +01:00
Matt Holt 63e4352db7 Merge pull request #612 from captncraig/pprof
pprof: Adding pprof middleware for profiling caddy.
2016-03-18 15:45:57 -06:00
Craig Peterson 640a0ef956 Adding pprof middleware for debugging purposes 2016-03-18 10:39:29 -06:00
Matt Holt 591b209024 Merge pull request #685 from wmark/for-mholt
Update unit files for systemd
2016-03-18 08:51:40 -06:00
W-Mark Kubacki f1c1ea9905 Service file for systemd starts after all networks have gotten IP addresses
Unlike network.target the network-online.target guarantees that the network
devices are online.

If you bind to 0.0.0.0, [::], [::1], and/or 127.0.0.1 only that is enough to
proceed. But in case a particular IP is needed, like ${COREOS_PUBLIC_IPV4},
we require any IP assignments to have completed before Caddy's start. That
is achieved by depending on systemd-networkd-wait-online.service (which is
scheduled before network-online.target, then, automatically).
2016-03-18 12:36:54 +01:00
Abiola Ibrahim 6b801b111b Merge pull request #684 from abiosoft/master
Fix for #659.
2016-03-18 07:08:24 +01:00
Matthew Holt 717c88ec0f Update contributing notes 2016-03-17 21:16:31 -06:00
Matt Holt 03a22aeb7e Merge pull request #683 from klingtnet/feat/systemd
systemd unit file
2016-03-17 21:05:12 -06:00
Matthew Holt 18332df358 Refactor output out of Start() 2016-03-17 17:58:40 -06:00
Matthew Holt b9f8c183fa gofmt! 2016-03-17 16:43:50 -06:00
Matthew Holt 37d050922b Fix typo, clarify readme 2016-03-17 16:42:28 -06:00
Abiola Ibrahim 04514fb791 Fix for #659. 2016-03-17 22:29:58 +01:00
Andreas Linz 6c2bf36dab Add systemd unit file and some usage instructions
Add systemd service file for caddy

Add some README with basic setup instructions

Explain how to view the service configuration

Add a note about permissions

Add a comment about run user and group

service->service unit

A systemd service can consist of different units. A unit configuration
file has the `.service` file ending which is a bit confusing, so please
be considerate if I'm confusing `service` and `unit` in the README

Fix typos/reword

Add contact information
2016-03-17 17:39:50 +01:00
Matt Holt 4f5fe2de24 Merge pull request #662 from mholt/md-include-fix
markdown: Included files in Markdown templates have access to document vars
2016-03-16 13:56:06 -06:00
Matthew Holt 90c24d2f32 Included files in Markdown templates have access to document vars (fixes #660)
Refactor how middleware.Context includes files
2016-03-16 13:42:16 -06:00
Matthew Holt d95c21ded5 Update readme with build script and link to CLI docs 2016-03-16 11:46:10 -06:00
Matthew Holt 4f4b34d481 Update changelog 2016-03-16 11:46:10 -06:00
Matthew Holt ed0342f171 Fix tests on Go tip (caused by golang/go#14827) 2016-03-16 11:46:10 -06:00
Matt Holt f14cdcc436 Merge pull request #682 from weingart/mime_fix
mime: Use a map, and error on duplicate extensions.
2016-03-16 08:40:10 -06:00
Tobias Weingartner b471b7e835 Fixup mime middleware to use a map and error on duplicate extensions.
- The mime middleware used filepath where it should arguably use path.
 - Changed the configuration to use a map instead of scanning an array
   during every request.  The map is static (after configuration), so
   should be fine for concurrent access.
 - Catch duplicate extensions within a configuration and error out.
 - Add tests for new error case.
2016-03-15 23:11:19 -07:00
Matt Holt b79ff7403f Merge pull request #664 from jupiter/max-connections
proxy: Add max_conns parameter for per-host maximum connections
2016-03-14 15:25:44 -06:00
Matt Holt 7560778602 Merge pull request #673 from ERIIX/master
Play nice when ACME used manually.
2016-03-13 14:50:33 -06:00
Matt Holt fc10951dde Merge pull request #676 from abiosoft/list-directives
Add flag to list directives.
2016-03-13 14:49:10 -06:00
Matt Holt 3e48e6a535 Merge pull request #657 from dprandzioch/feature/freebsd-service-script
Folder for init scripts within dist / FreeBSD service script
2016-03-13 14:46:06 -06:00
Abiola Ibrahim 44fc9b18a6 Print the directives in order of priority. 2016-03-13 18:29:26 +01:00
Abiola Ibrahim 3b6c387b84 Add flag to list directives. 2016-03-13 12:59:35 +01:00
Matthew Holt 35e4c1a7bf Sanity checkL this defer does not leak fds; comment added 2016-03-12 16:32:12 -07:00
Abiola Ibrahim 25bfdfe92c Merge pull request #672 from abiosoft/master
Hide only the currently used Caddyfile
2016-03-12 20:46:20 +01:00
Abiola Ibrahim 008ad398ce Hopefully, this is the final nail on the coffin. 2016-03-12 17:47:53 +01:00
Eric R. Monson 52d7379063 Play nice when ACME used manually.
Minor change to server/server.go such that /.well-known/acme-challenge
can be passed through when TLS.Manual is true on the vhost the request
came in through.
2016-03-11 22:37:54 -08:00
Abiola Ibrahim e92a911e7d Add more tests. 2016-03-11 23:44:50 +01:00
Abiola Ibrahim 84845a66ab Fix broken build. 2016-03-11 23:11:21 +01:00
Matt Holt e2f6ab3472 Merge pull request #671 from shawnps/patch-1
capitalize struct name in comment (go lint)
2016-03-11 08:21:56 -07:00
Abiola Ibrahim f3a183ecc1 Use filepath.Clean for fileserver. 2016-03-11 15:39:13 +01:00
Shawn Smith e958686ae4 capitalize struct name in comment 2016-03-11 23:16:28 +09:00
Pieter Raubenheimer 1f7d8d8ab0 Add test for UpstreamHost defaults 2016-03-10 14:45:23 +00:00
Pieter Raubenheimer a7766c9033 Add common method for checking host availability 2016-03-10 14:42:19 +00:00
Pieter Raubenheimer ce8ee831b3 Add check for per-host maximum connections 2016-03-08 16:25:05 +00:00
Matthew Holt 741d7685f1 Merge branch 'master' into fastcgi-methods
# Conflicts:
#	middleware/fastcgi/fastcgi.go
2016-03-07 16:25:23 -07:00
Matthew Holt 88e3a26c99 Full changes to contributing doc
That was weird, only half of the file got committed...
2016-03-07 12:10:26 -07:00
Matthew Holt f52b1e80f5 Update contributing doc and add issue template 2016-03-07 12:07:39 -07:00
David Prandzioch 202679efde Renamed apache24 occurance to caddy :-) 2016-03-06 10:49:29 +01:00
David Prandzioch 75915e0a25 Added a directory dist/init/ that may provide service scripts for various distributions in the future, added a experimental FreeBSD service script 2016-03-06 10:44:07 +01:00
Matt Holt 9e386fc921 Merge pull request #652 from elcore/patch-2
https: Support ECC keys
2016-03-03 08:19:20 -07:00
elcore 9099375b11 Support ECC certificates 2016-03-03 00:52:07 +00:00
Matthew Holt 36b440c04b https: Refuse start only if renewal fails on expired cert (closes #642) 2016-03-02 11:34:39 -07:00
Matthew Holt 2a46f2a14e Revert recent Content-Length-related changes and fix fastcgi return
fastcgi's ServeHTTP method originally returned the correct value (0) in
b51e8bc191. Later, I mistakenly suggested
we change that to return the status code because I forgot that status
codes aren't logged by the return value. So fastcgi broke due in
3966936bd6 due to my error.

We later had to try to make up for this with ugly Content-Length checks
like in c37ad7f677. Turns out that all we
had to do was fix the returned status here back to 0. The proxy
middleware behaves the same way, and returning 0 is correct. We should
only return a status code if the response has not been written, but with
upstream servers, we do write a response; they do not know about our
error handler.

Also clarifed this in the middleware.Handler documentation.
2016-03-02 11:33:40 -07:00
Matthew Holt 741880a38b Only obtain certificate and enable TLS if host qualifies (fixes #638) 2016-03-01 12:27:46 -07:00
Matt Holt 43c339c7e3 Merge pull request #641 from hkjn/fix-build-acme-crypt
https: Fix build after https://github.com/xenolf/lego/commit/0e26b
2016-02-27 10:21:44 -07:00
Henrik Jonsson 49c2807ba1 Fix build after https://github.com/xenolf/lego/commit/0e26b
Fix up last-second changes

Fixes #640
2016-02-27 18:06:56 +01:00
Matthew Holt da08c94a8c Implant version information with -ldflags with help of build script
Without -ldflags, the verison information needs to be updated manually,
which is never done between releases, so development builds appear
indiscernable from stable builds using `caddy -version`.

This is part of a set of changes intended to relieve the burden of
always updating version information manually and distributing binaries
that look stable but actually may not be.

A stable build is defined as one which is produced at a git tag with
a clean working directory (no uncommitted changes). A dev build is
anything else. With this build script, `caddy -version` will now reveal
whether it is a development build and, if so, the base version, the
latest commit, the date and time of build, and the names of files with
changes as well as how many changes were made.

The output of `caddy -version` for stable builds remains the same.
2016-02-26 00:26:31 -07:00
Matthew Holt c827a71d5d Version 0.8.2 2016-02-25 10:26:42 -07:00
Matthew Holt 2ecc837020 templates: .Truncate can truncate from end of string if length is negative 2016-02-24 20:32:26 -07:00
Matthew Holt c37ad7f677 Only write error message/page if body not already written (fixes #567)
Based on work started in, and replaces, #614
2016-02-24 19:50:46 -07:00
Matthew Holt 737c7c4372 fastcgi: Only perform extra copy if necessary; added tests 2016-02-24 16:42:01 -07:00
Matt Holt 367397dbd6 Merge pull request #623 from xlab/f/docflags
markdown: Implement .DocFlags and tests
2016-02-24 14:45:22 -07:00
Matt Holt a2dbfdc10e Merge pull request #636 from humboldtux/fastcgi-cl
fastcgi: Explicitly set Content-Length (fixes #626)
2016-02-24 14:39:15 -07:00
Benoit Benedetti ef5f9c771d FastCGI: Explicitly set Content-Length #626 2016-02-24 22:11:07 +01:00
Matthew Holt 05957b4965 gzip: Implement http.Hijacker (fixes #635) 2016-02-24 12:23:50 -07:00
Matt Holt 72fcdec8d8 Merge pull request #627 from ax-nathan/master
rewrite: Allow status to be 2xx or 4xx.
2016-02-24 11:01:54 -07:00
Nathan Probst f4bb43781c Remove unneeded Regexp from tests. 2016-02-24 10:28:06 -07:00
Matt Holt 197297b0d7 Merge pull request #628 from elcore/master
tls: Add SHA384 ciphers
2016-02-23 15:14:24 -07:00
elcore a541eb7899 Adding new cipher suites 2016-02-23 21:23:13 +00:00
Nathan Probst 2ea6c95ac4 Allow rewrite status codes to be 2xx and 4xx. 2016-02-22 15:30:55 -07:00
Maxim Kupriianov c7674e2060 Implement .DocFlags directive and tests. It holds all the boolean-typed front matter values. 2016-02-22 13:53:47 +03:00
Matt Holt c12847e5ba Merge pull request #620 from humboldtux/recorder
middleware: Export ResponseRecorder and add a couple getter methods
2016-02-20 15:17:45 -07:00
Benoit Benedetti bec130a563 Recorder: Exporting ResponseRecorder #614 2016-02-20 23:10:04 +01:00
Matthew Holt 09b7ce6c93 Try to get Go 1.6 on appveyor 2016-02-19 18:07:48 -07:00
Matt Holt b860be01bb Merge pull request #615 from 1lann/master
errors: Set missing Content-Type for plaintext error messages
2016-02-19 14:52:43 -07:00
Matthew Holt f7b5187bf3 server: Add "Referer" to log entry when host not found 2016-02-19 13:34:54 -07:00
Matthew Holt 09a7af8cae https: Wait as long as possible to create ACME client at startup (fixes #617) 2016-02-19 10:33:01 -07:00
Jason Chu 5f2670fdde Fix missing Content-Type for certain errors
And corrected an error in a copy and pasted comment
2016-02-20 00:42:17 +08:00
Matthew Holt ecf913e58d Update change log 2016-02-18 20:57:38 -07:00
Matthew Holt d05f89294e https: Minor refactoring and some new tests 2016-02-18 20:33:15 -07:00
Matthew Holt 1ef7f3c4b1 Remove path scoping for middleware slice
It was implemented for almost a year but we'll probably never use it, especially since we'll match more than the path in the future.
2016-02-17 18:11:03 -07:00
Matthew Holt f25ae8230f Move to Go 1.6 and set CGO_ENABLED=0 in tests 2016-02-17 16:08:25 -07:00
Matthew Holt 1cfd960f3c Bug fixes and other improvements to TLS functions
Now attempt to staple OCSP even for certs that don't have an existing staple (issue #605). "tls off" short-circuits tls setup function. Now we call getEmail() when setting up an acme.Client that does renewals, rather than making a new account with empty email address. Check certificate expiry every 12 hours, and OCSP every hour.
2016-02-15 23:39:04 -07:00
Matt Holt 2dba44327a Merge pull request #600 from jacobhands/betterlogging
Rotate process log
2016-02-14 23:32:50 -07:00
Matthew Holt cae9f7de9c gofmt -s; fix misspellings and lint; Go 1.5.3 in Travis CI 2016-02-14 00:10:57 -07:00
Matthew Holt a11e14aca8 Fix HTTPS config for empty/no Caddyfile
This fixes a regression introduced in recent commits that enabled TLS on the default ":2015" config. This fix is possible because On-Demand TLS is no longer implicit; it must be explicitly enabled by the user by setting a maximum number of certificates to issue.
2016-02-12 13:04:24 -07:00
Jacob Hands dc63e50172 Use rotating log files 2016-02-12 08:30:47 -06:00
Matthew Holt 04c7c442c5 https: Only create ACMEClient if it's actually going to be used
Otherwise it tries to create an account and stuff at first start, even without a Caddyfile or when serving localhost.
2016-02-11 16:20:59 -07:00
Matthew Holt 7bd2adf0dc Fix edge case related to reloaded configs and ACME challenge
If Caddy is running but not listening on port 80, reloading Caddy with a new Caddyfile that needs to obtain a TLS cert from the CA would fail, because it was just assumed that, if reloading, port 80 as already in use. That is not always the case, so we scan the servers to see if one of them is listening on port 80, and we configure the ACME client accordingly. Kind of a hack... but it works.
2016-02-11 15:37:51 -07:00
Matthew Holt 1fe39e4633 Additional mitigation for on-demand TLS
After 10 certificates are issued, no new certificate requests are allowed for 10 minutes after a successful issuance.
2016-02-11 14:27:57 -07:00
Matthew Holt 216a617249 tls: Some bug fixes, basic rate limiting, max_certs setting 2016-02-11 13:48:52 -07:00
Matthew Holt d25a3e95e4 Merge branch 'master' into getcertificate 2016-02-11 00:06:23 -07:00
Matthew Holt 11103bd8d6 Major refactor of all HTTPS/TLS/ACME code
Biggest change is no longer using standard library's tls.Config.getCertificate function to get a certificate during TLS handshake. Implemented our own cache which can be changed dynamically at runtime, even during TLS handshakes. As such, restarts are no longer required after certificate renewals or OCSP updates.

We also allow loading multiple certificates and keys per host, even by specifying a directory (tls got a new 'load' command for that).

Renamed the letsencrypt package to https in a gradual effort to become more generic; and https is more fitting for what the package does now.

There are still some known bugs, e.g. reloading where a new certificate is required but port 80 isn't currently listening, will cause the challenge to fail. There's still plenty of cleanup to do and tests to write. It is especially confusing right now how we enable "on-demand" TLS during setup and keep track of that. But this change should basically work so far.
2016-02-11 00:06:05 -07:00
Matt Holt f1ba7fa343 Merge pull request #467 from eiszfuchs/feature/proxy-socket
proxy: Support unix sockets
2016-02-10 11:52:57 -07:00
eiszfuchs 7091a2090b created http.Transport and tests for unix sockets 2016-02-10 19:45:31 +01:00
Matt Holt 57ffe5a619 Merge pull request #591 from Barberrrry/master
fastcgi: New function DialWithDialer to create FCGIClient with custom Dialer.
2016-02-10 09:06:29 -07:00
Vadim Petrov b1208d3fdf New function DialWithDialer to create FCGIClient with custom Dialer. 2016-02-10 18:03:43 +03:00
Matt Holt b089d14b67 Merge pull request #548 from captncraig/register
Making directives externally registerable
2016-02-05 10:10:10 -07:00
Craig Peterson e72fc20c78 making directives externally registerable 2016-02-05 10:03:20 -07:00
Matt Holt 5b7e0361dd Merge pull request #573 from miekg/markdown-directive
templates: Add .Markdown directive
2016-02-04 13:59:34 -07:00
Miek Gieben 86f36bdb61 Add .Markdown directive
This allows any template to use:
{{.Markdown "filename"}} which will convert the markdown contents
of filename to HTML and then include the HTML in the template.
2016-02-04 20:54:19 +00:00
Matt Holt 3278106421 Merge pull request #572 from miekg/markdown-def-lists
markdown: enable definition lists
2016-02-04 10:20:17 -07:00
Matt Holt f9b8e31ad7 Merge pull request #571 from incon/log-remote-host
When the requested host is not found, log the remote host.
2016-02-04 07:42:05 -07:00
Miek Gieben fbdfc979ec Markdown: enable definition lists 2016-02-04 11:21:44 +00:00
David Darrell 2acaf2fa6f Move logic to split the port to only happen when the host is not found. 2016-02-04 16:17:10 +08:00
David Darrell f4fcfa8793 When the requested host is not found log the remote host. 2016-02-04 12:46:24 +08:00
Matt Holt 79db939259 Merge pull request #551 from denquixote/acmehandlehost
letsencrypt: properly retrieve hostname from request.
2016-02-01 09:23:03 -07:00
Matt Holt f9b6ede92b Merge pull request #543 from DenBeke/master
fastcgi: IPv6 when parsing r.RemoteAddr
2016-02-01 08:26:21 -07:00
Abiola Ibrahim 184abe3bc8 Merge pull request #561 from DenBeke/fix-vet
basicauth: fixed 'go vet' printing function value
2016-02-01 11:31:04 +01:00
MathiasB fde9bbeb32 basicauth: fixed 'go vet' printing function value 2016-02-01 11:17:16 +01:00
MathiasB c59fd1c76e Defined test function in TestBuildEnv 2016-02-01 09:39:13 +01:00
Matthew Holt 600ee9a89f fastcgi: Accept any other methods as a POST-style request 2016-01-31 21:36:39 -07:00
Matthew Holt c5983e305f Merge branch 'master' of github.com:mholt/caddy 2016-01-30 21:05:18 -07:00
Den Quixote 8d057c8614 letsencrypt: properly retrieve hostname from request. 2016-01-30 02:20:34 +01:00
MathiasB ac197f1694 FastCGI: some simple tests for buildEnv
More tests are needed for the other environmental variables.
These tests were specifically made for testing of IP addresses.
2016-01-29 11:46:15 +01:00
MathiasB d8be787f39 FastCGI: IPv6 when parsing r.RemoteAddr 2016-01-28 15:26:33 +01:00
Matt Holt a8c8b48390 Merge pull request #542 from cudevmaxwell/master
fastcgi: Parse address from fastcgi directive, pass results to Dial()
2016-01-28 00:21:06 -07:00
Kevin Bowrin 4d4ea94465 Parse address from fastcgi directive, and pass results to fcgiclient Dial().
This allows scheme prefixes "tcp://" and "fastcgi://" in configuration.

Fixes #540
2016-01-28 00:19:00 -05:00
Matt Holt aeaf58b16a Merge pull request #541 from jungle-boogie/patch-5
wrap lines to 80
2016-01-27 12:34:38 -07:00
jungle-boogie 73ed286309 wrap lines to 80
also update copyright year.
2016-01-27 11:28:49 -08:00
Matthew Holt 9e900b0a08 godoc 2016-01-25 20:45:23 -07:00
Matthew Holt f1b2637d44 letsencrypt: Enable activation on empty hosts; fix email bug 2016-01-25 20:21:08 -07:00
Matthew Holt 178c4d11d9 Merge branch 'master' into getcertificate 2016-01-25 13:47:13 -07:00
Matthew Holt 7613ae3bf0 Change to Windows line endings for poor Notepad 2016-01-25 13:46:07 -07:00
Matthew Holt ad664e5bba browse: Render names with multiple consecutive spaces correctly 2016-01-25 13:43:35 -07:00
Matthew Holt cf06abd691 Log restarts 2016-01-25 13:41:51 -07:00
Matthew Holt a6abec8210 letsencrypt: Update to match upstream refactor
Challenge names now have their own type and constants
2016-01-25 08:47:28 -07:00
Matthew Holt 82b049229b proxy: Add basic proxying test and InsecureSkipVerify transport test 2016-01-23 10:14:13 -07:00
Matt Holt fae612d53b Merge pull request #529 from FiloSottile/filippo/insecure
proxy: add a insecure_skip_verify option - closes #320
2016-01-23 09:13:30 -07:00
Filippo Valsorda bae4ac9764 proxy: add a insecure_skip_verify option - closes #320 2016-01-23 03:26:31 +00:00
Matt Holt 6e340cb1d6 Merge pull request #514 from upsuper/fix-user-key-perm
letsencrypt: Fix perm of user key
2016-01-16 00:46:45 -07:00
Xidorn Quan 0d8d0ba5a0 letsencrypt: Fix perm of user key 2016-01-16 13:12:03 +11:00
Matt Holt 8655ea671b Merge pull request #510 from alehano/master
gzip: Add .svg to default ext list
2016-01-15 15:33:15 -07:00
Matthew Holt 0d8526b7d9 gzip: Allow empty extension (#509) 2016-01-15 11:59:05 -07:00
Alexey Khalyapin c9e0517e5e Add .svg to default ext list in gzip middleware 2016-01-14 11:52:03 +03:00
Matthew Holt e74558eaea browse: Fix for files and dirs with ':' in name 2016-01-13 13:17:58 -07:00
Matthew Holt b0ccab7b4a tls: Fix failing test 2016-01-13 09:24:03 -07:00
Matthew Holt 47079c3d24 PoC: on-demand TLS
Implements "on-demand TLS" as I call it, which means obtaining TLS certificates on-the-fly during TLS handshakes if a certificate for the requested hostname is not already available. Only the first request for a new hostname will experience higher latency; subsequent requests will get the new certificates right out of memory.

Code still needs lots of cleanup but the feature is basically working.
2016-01-13 00:32:46 -07:00
Matthew Holt b4cab78bec Starting transition to Go 1.6 (http2 compatibility)
I've built this on Go 1.6 beta 1 and made some changes to be more compatible. Namely, I removed the use of the /x/net/http2 package and let net/http enable h2 by default; updated the way h2 is disabled (if the user requires it); moved TLS_FALLBACK_SCSV to the front of the cipher suites list (all values not accepted by http2 must go after those allowed by it); removed the NextProto default of http/1.1; set the http.Server.TLSConfig value to the TLS config used by the listener (we left it nil before, but this prevents automatic enabling of h2).

It is very likely there is more to do, but at least already Caddy uses HTTP/2 when built with Go 1.6.
2016-01-13 00:29:22 -07:00
Matthew Holt 3c96718027 Version 0.8.1 2016-01-12 10:01:57 -07:00
Matthew Holt 4b6e0e9369 tls: Fix failing test 2016-01-12 08:55:01 -07:00
Matthew Holt 2bcbdd6a17 Merge branch 'getcertificate' 2016-01-12 08:52:52 -07:00
Matthew Holt 8f2196c047 tls: No arguments to directive and no block is an error 2016-01-12 08:52:43 -07:00
Matthew Holt c7d4d051cb letsencrypt: Ensure no prompt if user is not there
Also only set custom address if alternate port is specified (rather than using a blank address; just cleaner this way)
2016-01-12 08:52:08 -07:00
Matt Holt e283af4d9b Merge pull request #502 from abiosoft/gzip-fix
gzip: Fix for wrong content-type when templates is used.
2016-01-12 08:32:09 -07:00
Abiola Ibrahim 12cd2d528c Gzip: Fix for wrong content-type when templates is used. 2016-01-12 15:06:08 +01:00
Matthew Holt 8a6c778c8d Log if host is not configured for server 2016-01-11 09:07:14 -07:00
Matthew Holt 77eae62d9f letsencrypt: Don't prompt if user is not there
This change fixes the scenario where you reload the config and it tries to obtain a cert from the ACME server, but no email address is found or terms have not been agreed to in-process. This is unfortunate but it should not stop the server from reloading, so we assume empty email address in this case.
2016-01-10 23:40:55 -07:00
Matthew Holt 97c8c9582a Updated changelog 2016-01-10 21:52:56 -07:00
Matthew Holt ed0c0db6a3 If scheme and port defy convention, it is an error
This prevents serving HTTPS over port 80 or HTTP over 443. It's confusing and we don't allow it.
2016-01-10 20:51:50 -07:00
Matthew Holt 202849055c tls: Extra requirements to set port to 443
It is unexpected to serve localhost on port 443 or any server on 443 if TLS is disabled, even if the port is blank. Also don't warn about how to force TLS on the HTTP port.
2016-01-10 20:47:43 -07:00
Matthew Holt 060ab92d29 Reorder a few things
The docs link to this structure and all its methods related to the browse template; keeping them together makes it possible to link to the whole block of code that is relevant.
2016-01-10 09:47:26 -07:00
Matthew Holt 0830c728fe Remove pidfile when program exits (closes #495) 2016-01-09 21:48:07 -07:00
Matthew Holt dab679df86 import: Fix multiple imports (closes #480) 2016-01-09 10:52:21 -07:00
Matthew Holt 9453224639 Merge branch 'letsencryptfix' 2016-01-08 23:59:15 -07:00
Matthew Holt fd1765973a letsencrypt: Tests for handler that proxies challenge requests
Doesn't test the SkipInsecureVerify proxy setting, but that can be done at another time.
2016-01-08 16:44:54 -07:00
Matthew Holt 0efe39a705 Switch back to LE production endpoint 2016-01-08 14:13:21 -07:00
Matthew Holt a3f3bc67e1 Merge branch 'browse-tpl' 2016-01-08 14:02:55 -07:00
Matthew Holt 8b93bfe751 letsencrypt: More tests! \o/ 2016-01-08 13:49:06 -07:00
Matthew Holt 897b6c5b0e letsencrypt: More tests, other minor improvements 2016-01-08 12:32:47 -07:00
Matthew Holt fc928e0b3b letsencrypt: Couple minor refactors/fixes 2016-01-08 11:55:31 -07:00
Matthew Holt 93b301372b rewrite: Fix a lint suggestion 2016-01-06 22:23:18 -07:00
Matthew Holt ce4981d046 Merge branch 'master' into letsencryptfix
# Conflicts:
#	caddy/letsencrypt/letsencrypt.go
#	caddy/letsencrypt/letsencrypt_test.go
2016-01-06 22:21:15 -07:00
Matthew Holt 62b210b544 browse: Minor changes to improve icon positioning in template
Also make sure column header cells don't wrap
2016-01-06 22:18:17 -07:00
Matt Holt 5f6a0a4c0b Merge pull request #479 from abiosoft/rewrite-patch
rewrite: not_has, not_match, multiple to, and status codes
2016-01-06 22:08:16 -07:00
Matt Holt cae9880800 Merge pull request #483 from benschumacher/master
letsencrypt: Make plaintext redirect hosts honor the Bind settings
2016-01-06 22:06:53 -07:00
Matthew Holt 6d49392602 Improve tests, fix a few lint warnings 2016-01-06 16:04:33 -07:00
Matthew Holt 4593982065 letsencrypt: Major refactor of Activate(), fixes #474 and closes #397
Makes restarts cleaner and improves configuration usability related to the tls directive
2016-01-06 16:04:08 -07:00
Matthew Holt 94100a7ba6 Fix failing tests 2016-01-04 17:34:10 -07:00
Matthew Holt e9c2e50684 Merge branch 'master' into letsencryptfix
# Conflicts:
#	caddy/letsencrypt/letsencrypt.go
2016-01-03 23:56:22 -07:00
Ben Schumacher 82b0c0b9eb Make Let's Encrypt module honor the Bind settings 2016-01-03 23:56:14 -07:00
Matthew Holt 55601d3ec2 letsencrypt: Fix OCSP stapling and restarts with new LE-capable hosts
Before, Caddy couldn't support graceful (zero-downtime) restarts when the reloaded Caddyfile had a host in it that was elligible for a LE certificate because the port was already in use. This commit makes it possible to do zero-downtime reloads and issue certificates for new hosts that need it. Supports only http-01 challenge at this time.

OCSP stapling is improved in that it updates before the expiration time when the validity window has shifted forward. See 30c949085c. Before it only used to update when the status changed.

This commit also sets the user agent for Let's Encrypt requests with a string containing "Caddy".
2016-01-03 17:05:10 -07:00
Matthew Holt 829a0f34d0 Preserve and clean up original host input in Caddyfile-JSON conversions 2016-01-03 16:46:26 -07:00
Matthew Holt bb80f99190 tls: Allow opening block without specifying cert+key args 2016-01-03 16:44:30 -07:00
Matthew Holt 946ff5e87b Parser separate scheme/port, refactor config loading
By separating scheme and port at the parser, we are able to set the port appropriately and also keep the semantics of the scheme being specified by the user later on. The parser also stores an address' original input. Also, the config refactor makes it possible to partially load a config - valuable for determining which ones will need Let's Encrypt integration turned on during a restart.
2016-01-03 16:41:29 -07:00
Abiola Ibrahim 0a04fa40f4 Oops. status code check should be after all validations. 2016-01-02 08:08:55 +01:00
Abiola Ibrahim 48d7f1ead2 Refactor. Stop useless rewrite if status code is set. 2016-01-01 07:05:30 +01:00
Abiola Ibrahim be2f5c4b38 Support for 4xx status codes. 2015-12-31 23:19:11 +01:00
Abiola Ibrahim 281007c482 Merge remote-tracking branch 'upstream/master' into rewrite-patch 2015-12-31 20:34:25 +01:00
Matthew Holt b6326d402d Fix for case-insensitive header replacements (#476) 2015-12-31 12:31:30 -07:00
Matthew Holt e2a3ec4c3d Replacer supports case-insensitive header placeholders (fixes #476) 2015-12-31 12:12:16 -07:00
Abiola Ibrahim 3468986260 Support multiple values for to in simple rule. 2015-12-31 20:11:31 +01:00
Abiola Ibrahim 55f69fd742 Add not_has and not_match conditions. 2015-12-31 20:10:42 +01:00
Matthew Holt 1af7865e6c Move SVG up to top and make layout mobile-friendly 2015-12-31 11:18:43 -07:00
Matt Holt 4636ca1051 Merge pull request #471 from abiosoft/rewrite-improvements
rewrite: Support for rewrite match group.
2015-12-31 11:07:14 -07:00
Matthew Holt 94e3e7e5eb browse: New default template 2015-12-31 00:23:10 -07:00
Abiola Ibrahim 3c086fb2e6 Support for rewrite match group. 2015-12-30 21:47:37 +01:00
Abiola Ibrahim 55aa492dc1 Merge pull request #470 from abiosoft/replacer-patch
Replacer patch
2015-12-30 21:06:55 +01:00
Abiola Ibrahim 7dadcd5834 Add ability to set custom values. 2015-12-30 20:42:03 +01:00
Abiola Ibrahim 73327e784d Merge branch 'master' into replacer-patch 2015-12-30 20:26:11 +01:00
Abiola Ibrahim bb23f68a43 Merge pull request #463 from abiosoft/rewrite-improvements
Rewrite improvements
2015-12-30 19:42:13 +01:00
Matt Holt 6a27968f73 Merge pull request #385 from radim/master
Support glob character in import
2015-12-29 16:21:26 -07:00
Radim Marek 1e7ec3397b Import allows only one expression 2015-12-29 23:32:59 +01:00
Abiola Ibrahim 168723a026 Added escaped versions of uri, query and path. 2015-12-24 09:00:10 +01:00
Abiola Ibrahim 92bd914418 Fix vet errors. 2015-12-23 13:23:43 +01:00
Abiola Ibrahim 9110dc4745 Refactor. Tests and tests data. 2015-12-23 12:11:11 +01:00
Abiola Ibrahim 1ed786f836 Cleanups and panic prevention in tests. 2015-12-23 09:36:00 +01:00
Abiola Ibrahim 4d5bc9fa6c Backward compatibility ensured. 2015-12-23 09:02:52 +01:00
Abiola Ibrahim 98d8c0f81b Added new rewrite features. 2015-12-22 23:19:22 +01:00
Matt Holt 32b8857eea Merge pull request #461 from abiosoft/markdown-sitegen-panic
markdown: Fix panic on sitegen for request dependent template values.
2015-12-22 12:03:20 -07:00
Abiola Ibrahim 9e163a655d Use proper struct constructors instead. 2015-12-22 14:43:48 +01:00
Abiola Ibrahim 4d867e848b Markdown: Fix panic on sitegen for request dependent template values. 2015-12-22 13:32:27 +01:00
Abiola Ibrahim c748ef944b Merge pull request #449 from abiosoft/master
Gzip: Fix missing gzip encoding headers.
2015-12-21 20:54:00 +01:00
Abiola Ibrahim 55d22f4ead Merge remote-tracking branch 'upstream/master' 2015-12-21 20:49:01 +01:00
Abiola Ibrahim 3f787a20e3 Merge pull request #438 from captncraig/patch-1
Gzip: Append to Vary header instead of replacing.
2015-12-21 20:48:03 +01:00
Matt Holt 6276be4e90 Merge pull request #457 from abiosoft/fcgi-hanging-bug
fastcgi: Close client connections when done.
2015-12-21 10:46:00 -07:00
Abiola Ibrahim f639d3cd68 FastCGI: Close client connections when done. 2015-12-21 11:57:20 +01:00
Abiola Ibrahim 43020533f7 Merge remote-tracking branch 'upstream/master' 2015-12-19 21:58:24 +01:00
Matt Holt a5836aebfa Merge pull request #412 from pavlik/master
tls: Remove ECDHE-RSA-3DES-EDE-CBC-SHA and RSA-3DES-EDE-CBC-SHA from the default TLS config
2015-12-19 11:57:45 -07:00
Pavel Pavlenko 3dd4c0eb6a Fix TestTLSParseBasic 2015-12-19 14:37:38 +03:00
Pavel Pavlenko 1e27b5be89 Remove ECDHE-RSA-3DES-EDE-CBC-SHA and RSA-3DES-EDE-CBC-SHA from the default TLS config 2015-12-19 14:30:25 +03:00
Abiola Ibrahim a946d65fe6 Oops. Tests. 2015-12-18 21:25:06 +01:00
Abiola Ibrahim f04ff063ed Gzip: Fix missing gzip encoding headers. 2015-12-18 20:58:23 +01:00
Matt Holt 35ec61cc88 Merge pull request #441 from jungle-boogie/patch-1
Feek --> Feel
2015-12-17 21:50:04 -07:00
jungle-boogie 8f23c430ae Feek --> Feel
unless you meant Freak
2015-12-17 13:12:22 -08:00
Matthew Holt 5eadea6615 Slack -> Gitter 2015-12-17 08:37:16 -07:00
Craig Peterson 34d3cd7c92 Gzip: Append to Vary header instead of replacing. 2015-12-15 08:56:44 -07:00
Matt Holt f11cd4d9dd Merge pull request #429 from abiosoft/php-error-log
fastcgi: separate standard and error output responses.
2015-12-14 10:14:52 -07:00
Matt Holt a8c9d47805 Merge pull request #404 from abiosoft/master
gzip: support for min_length.
2015-12-14 10:11:22 -07:00
Abiola Ibrahim 3966936bd6 Remove trailing new line for cleaner log output.
Return correct status code.
2015-12-13 12:58:21 +01:00
Abiola Ibrahim b7fd1f4e9e FastCGI: separate standard and error output responses. 2015-12-12 14:04:48 +01:00
Matthew Holt b0397df719 Save obtained certs even if there were failures 2015-12-11 15:42:22 -07:00
Radim Marek eb48885d4d Updated comments 2015-12-11 22:02:31 +01:00
Radim Marek afbda595f6 import glob tests 2015-12-11 21:52:35 +01:00
Abiola Ibrahim b65ddbc750 Merge remote-tracking branch 'upstream/master' 2015-12-10 02:37:19 +01:00
Matt Holt aba0ae358e Merge pull request #415 from abiosoft/appveyor-fix
Fix race condition in test for AppVeyor
2015-12-09 15:50:51 -07:00
Abiola Ibrahim 2e295b51b3 Use channel instead for a synchronous interval. 2015-12-09 20:18:18 +01:00
Abiola Ibrahim 59dbea768c Fix race condition on AppVeyor. Increase timeout a bit. 2015-12-09 19:22:20 +01:00
Abiola Ibrahim a44d59f1e5 Use ioutil.Discard instead for unneeded bytes. 2015-12-09 18:44:25 +01:00
Pavel Pavlenko e4ff77ed07 fix tls_test.go 2015-12-09 11:27:59 +03:00
Pavel Pavlenko b6c4178f0a Remove ECDHE-RSA-3DES-EDE-CBC-SHA and RSA-3DES-EDE-CBC-SHA from the default TLS config 2015-12-09 11:10:55 +03:00
Abiola Ibrahim 23631cfaca Fix deleted Content-Length header bug. 2015-12-08 12:01:24 +01:00
Abiola Ibrahim 8631f33940 remove minor ugly parenthesis 2015-12-07 23:27:57 +01:00
Abiola Ibrahim ab5087e215 Gzip: support for min_length. 2015-12-07 23:17:05 +01:00
Radim Marek d56a9a1c5d Correct position of the newly imported tokens 2015-12-06 23:50:26 +01:00
Matt Holt 41bdd77545 Merge pull request #381 from ReadmeCritic/master
Update README URLs based on HTTP redirects
2015-12-06 10:23:32 -07:00
Matt Holt b7827a342a Merge pull request #387 from Moter8/patch-1
Latest Go 1.5 version
2015-12-05 09:38:15 -07:00
Carsten Hagemann e17a18365d Latest Go 1.5 version 2015-12-05 13:05:51 +01:00
Radim Marek d1216f409d Handle no matches 2015-12-04 23:22:03 +01:00
Radim Marek 12f594779c Added support magic characters in import pattern
Import now allows to use the star wildcard, question mark and square
brackets as used by filepath.Glob
2015-12-04 23:04:12 +01:00
ReadmeCritic b3f5e4d4ad Update README URLs based on HTTP redirects 2015-12-04 07:20:56 -08:00
Matthew Holt 5b93799a62 Version 0.8.0 2015-12-03 19:52:15 -07:00
Matthew Holt fd14f257df markdown: Add (currently failing) test for empty body 2015-12-02 17:09:43 -07:00
Matt Holt d044e497f6 Merge pull request #377 from abiosoft/master
markdown: Fix "metadata not closed" bug. More tests.
2015-12-02 17:04:16 -07:00
Abiola Ibrahim 6f4835f91a Markdown: Fix "metadata not closed" bug. More tests. 2015-12-03 00:41:12 +01:00
Matthew Holt 9002db2ae0 letsencrypt: Remote duplicate hosts from certificate request
Domain names must be unique in cert bundle request or really bad things happen (like, um, a panic)
2015-12-02 12:40:57 -07:00
Matthew Holt 19c6bbf6a2 Update changelist (env vars) 2015-12-02 11:42:50 -07:00
Matt Holt ef2ca1da3d Merge pull request #357 from tw4452852/my_md
markdown: fix json front matter parse issue when body content is long
2015-11-27 00:07:19 -07:00
Tw fbc18c5b85 markdown: fix json format parse issue
We can't use json meta parser's remaining buffered data as the markdown body
because it may not contain the entire original content.
Now we adopt the way like toml and yaml parser's way to extract the meta content
at first.

Also when spilting the meta data and content body, additional io.Copy is
unnecessary.

Fix issue #355

Signed-off-by: Tw <tw19881113@gmail.com>
2015-11-25 08:32:14 +08:00
Matt Holt d93fe53e84 Merge pull request #362 from jungle-boogie/patch-3
wrap lines to 80 characters
2015-11-24 13:42:21 -07:00
Matt Holt 0a40970dea Merge pull request #361 from jungle-boogie/patch-2
more copy edits
2015-11-24 13:34:39 -07:00
jungle-boogie 6478eee338 removed stray ) and spaces. 2015-11-24 11:16:05 -08:00
jungle-boogie a60c739797 wrap lines to 80 characters
also linked to bug reporting article.
2015-11-24 11:09:35 -08:00
jungle-boogie 19ca7d812e more copy edits
add links to inspired by projects and updated language with golang.
2015-11-24 11:04:15 -08:00
Matt Holt bc37cf0d1c Merge pull request #360 from jungle-boogie/patch-1
copy edit to wrap around 80 chars
2015-11-24 11:24:24 -07:00
jungle-boogie 78b95deb55 copy edit to wrap around 80 chars
not all lines wrap at 80 characters but now most do.
2015-11-24 10:17:34 -08:00
Matt Holt b787569820 Merge pull request #359 from abiosoft/master
rewrite: Use middleware.Replacer for simple rule
2015-11-24 10:17:02 -07:00
Abiola Ibrahim 016344bae7 Rewrite: Use middleware.Replacer for simple rule. 2015-11-24 12:20:39 +01:00
Matt Holt 0b51369932 Merge pull request #356 from Luit/master
proxy: Two small websocket fixes
2015-11-21 13:37:59 -07:00
Luit van Drongelen 1fb66d534a Close proxy's backend request earlier when re-connecting for websocket 2015-11-21 20:03:46 +01:00
Luit van Drongelen f0b1edaf8c Fix proxy for websocket with altered Host header 2015-11-21 20:00:44 +01:00
Matt Holt 4dbb4274d9 Merge pull request #354 from pyed/patch-3
browse: allow consecutive spaces in the header
2015-11-20 23:48:56 -07:00
pyed 9886e89e42 allow consecutive spaces in the header 2015-11-21 05:49:27 +03:00
Matt Holt 3e402e0692 Merge pull request #349 from carlisia/cc-lint-fixes
Add a few super minor lint fixes
2015-11-19 08:17:41 -07:00
Carlisia Campos 0a1721d5b2 Add a few super minor lint fixes
Other lint warnings left behind are ones due to external package and
due to maintainer's preferences.
2015-11-19 06:56:28 -08:00
2095 changed files with 836346 additions and 15250 deletions
+19
View File
@@ -0,0 +1,19 @@
# shell scripts should not use tabs to indent!
*.bash text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2
*.sh text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2
# files for systemd (shell-similar)
*.path text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2
*.service text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2
*.timer text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2
# 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
*.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
+160
View File
@@ -0,0 +1,160 @@
Contributing to Caddy
=====================
Welcome! Thank you for choosing to be a part of our community. Caddy wouldn't be great without your involvement!
For starters, we invite you to join [the Caddy forum](https://caddy.community) where you can hang out with other Caddy users and developers.
## Common Tasks
- [Contributing code](#contributing-code)
- [Writing a plugin](#writing-a-plugin)
- [Asking or answering questions for help using Caddy](#getting-help-using-caddy)
- [Reporting a bug](#reporting-bugs)
- [Suggesting an enhancement or a new feature](#suggesting-features)
- [Improving documentation](#improving-documentation)
Other menu items:
- [Values](#values)
- [Responsible Disclosure](#responsible-disclosure)
- [Thank You](#thank-you)
### 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).
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.
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.
- **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 related commits together in a PR.** We do want pull requests to be small, but you should also keep multiple related commits in the same PR if they rely on each other.
- **Write tests.** Tests are essential! Written properly, they ensure your change works, and that other changes in the future won't break your change. CI checks should pass.
- **Benchmarks should be included for optimizations.** Optimizations sometimes make code harder to read or have changes that are less than obvious. They should be proven with benchmarks or profiling.
- **[Squash](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html) insignificant commits.** Every commit should be significant. Commits which merely rewrite a comment or fix a typo can be combined into another commit that has more substance. Interactive rebase can do this, or a simpler way is `git reset --soft <diverging-commit>` then `git commit -s`.
- **Own your contributions.** Caddy is a growing project, and it's much better when individual contributors help maintain their change after it is merged.
- **Use comments properly.** We expect good godoc comments for package-level functions, types, and values. Comments are also useful whenever the purpose for a line of code is not obvious.
We often grant [collaborator status](#collaborator-instructions) to contributors who author one or more significant, high-quality PRs that are merged into the code base!
#### HOW TO MAKE A PULL REQUEST TO CADDY
Contributing to Go projects on GitHub is fun and easy. 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.
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`.
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`
4. Make your changes in the mholt/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.)
This workflow is nice because you don't have to change import paths. You can get fancier by using different branches if you want.
### Writing a plugin
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.
### Getting help using Caddy
If you have a question about using Caddy, [ask on our forum](https://caddy.community)! There will be more people there who can help you than just the Caddy developers who follow our issue tracker. Issues are not the place for usage questions.
Many people on the forums could benefit from your experience and expertise, too. Once you've been helped, consider giving back by answering other people's questions and participating in other discussions.
### Reporting bugs
Like every software, Caddy has its flaws. If you find one, [search the issues](https://github.com/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.)
**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.
Please follow the issue template so we have all the needed information. Unredacted&mdash;yes, actual values matter. We need to be able to repeat the bug using your instructions. Please simplify the issue as much as possible. The burden is on you to convince us that it is actually a bug in Caddy. This is easiest to do when you write clear, concise instructions so we can reproduce the behavior (even if it seems obvious). The more detailed and specific you are, the faster we will be able to help you!
We suggest reading [How to Report Bugs Effectively](http://www.chiark.greenend.org.uk/~sgtatham/bugs.html).
Please be kind. :smile: Remember that Caddy comes at no cost to you, and you're getting free support when we fix your issues. If we helped you, please consider helping someone else!
### 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.
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.
### 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, 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.
## Collaborator Instructions
Collaborators have push rights to the repository. We grant this permission after one or more successful, high-quality PRs are merged! We thank them for their help.The expectations we have of collaborators are:
- **Help review pull requests.** Be meticulous, but also kind. We love our contributors, but we critique the contribution to make it better. Multiple, thorough reviews make for the best contributions! Here are some questions to consider:
- Can the change be made more elegant?
- Is this a maintenance burden?
- What assumptions does the code make?
- Is it well-tested?
- Is the change a good fit for the project?
- Does it actually fix the problem or is it creating a special case instead?
- Does the change incur any new dependencies? (Avoid these!)
- **Answer issues.** If every collaborator helped out with issues, we could count the number of open issues on two hands. This means getting involved in the discussion, investigating the code, and yes, debugging it. It's fun. Really! :smile: Please, please help with open issues. Granted, some issues need to be done before others. And of course some are larger than others: you don't have to do it all yourself. Work with other collaborators as a team!
- **Do not merge pull requests until they have been approved by one or two other collaborators.** If a project owner approves the PR, it can be merged (as long as the conversation has finished too).
- **Prefer squashed commits over a messy merge.** If there are many little commits, please [squash the commits](https://stackoverflow.com/a/11732910/1048862) so we don't clutter the commit history.
- **Don't accept new dependencies lightly.** Dependencies can make the world crash and burn, but they are sometimes necessary. Choose carefully. Extremely small dependencies (a few lines of code) can be inlined. The rest may not be needed. For those that are, Caddy vendors all dependencies with the help of [gvt](https://github.com/FiloSottile/gvt). All external dependencies must be vendored, and _Caddy must not export any types defined by those dependencies_. Check this diligently!
- **Be extra careful in some areas of the code.** There are some critical areas in the Caddy code base that we review extra meticulously: the `caddy` and `caddytls` packages especially.
- **Make sure tests test the actual thing.** Double-check that the tests fail without the change, and pass with it. It's important that they assert what they're purported to assert.
- **Recommended reading**
- [CodeReviewComments](https://github.com/golang/go/wiki/CodeReviewComments) for an idea of what we look for in good, clean Go code
- [Linus Torvalds describes a good commit message](https://gist.github.com/matthewhudson/1475276)
- [Best Practices for Maintainers](https://opensource.guide/best-practices/)
- [Shrinking Code Review](https://alexgaynor.net/2015/dec/29/shrinking-code-review/)
## Values
- A person is always more important than code. People don't like being handled "efficiently". But we can still process issues and pull requests efficiently while being kind, patient, and considerate.
- The ends justify the means, if the means are good. A good tree won't produce bad fruit. But if we cut corners or are hasty in our process, the end result will not be good.
## Responsible Disclosure
If you've found a security vulnerability, please email me, the author, directly: Matthew dot Holt at Gmail. I'll need enough information to verify the bug and make a patch. It will speed things up if you suggest a working patch. If your report is valid and a patch is released, we will not reveal your identity by default. If you wish to be credited, please give me the name to use. Thanks for responsibly helping Caddy&mdash;and thousands of websites&mdash;be more secure!
## Thank you
Thanks for your help! Caddy would not be what it is today without your
contributions.
+83
View File
@@ -0,0 +1,83 @@
---
name: Bug report
about: For behaviors which violate documentation or cause incorrect results
title: ''
labels: ''
assignees: ''
---
<!--
This template is for bug reports. The lack of a feature is not a bug; to request a feature, please switch templates.
Are you asking for help with using Caddy? Please ask on our forum: https://caddy.community
Please do not skip relevant questions; this will slow down the debugging process and your issue may be closed.
-->
## 1. Which version of Caddy are you using (`caddy -version`)?
<!-- If there is no version information, please paste commit SHA instead. -->
## 2. What are you trying to do?
<!-- Please clearly describe what you are trying to do thoroughly enough so that a reader with no background information can repeat it. -->
## 3. What is your Caddyfile?
```text
paste entire Caddyfile here - DO NOT REDACT ANYTHING (except credentials)
```
<!-- Changing or hiding parts of your Caddyfile only slows things down and may result in your report being closed.
For more information, see https://caddy.community/t/how-to-get-help-with-caddy-more-effectively/5222 -->
<!-- If you are unable to post this publicly, we offer private support: https://caddyserver.com/products/support -->
## 4. How did you run Caddy (give the full command and describe the execution environment)?
<!-- IMPORTANT: Please eliminate Docker, systemd, reverse proxies, upstream dependencies, caches, firewalls, and other unnecessary, external factors from your setup first. This will help prove that this is a bug in Caddy and not a misconfiguration of your environment. We may close issues that are too complex to replicate. Thank you! -->
## 5. Please paste any relevant HTTP request(s) here.
<!-- Paste curl command, or full HTTP request including headers and body. You may skip this if the bug does not require HTTP requests. -->
## 6. What did you expect to see?
<!-- Describe your expected results as precisely as possible. -->
## 7. What did you see instead (give full error messages and/or log)?
<!-- Please run Caddy with the -log flag, and use the log and errors directives as needed. DO NOT REDACT INFORMATION except for credentials. See https://caddy.community/t/how-to-get-help-with-caddy-more-effectively/5222 -->
## 8. Why is this a bug, and how do you think this should be fixed?
<!-- Help us understand why it is a bug; it is not always obvious. You can help us get this resolved faster by thinking about the problem and describing possible solutions! -->
## 9. What are you doing to work around the problem in the meantime?
<!-- This can help others who encounter the same problem, until we implement a fix. -->
## 10. Please link to any related issues, pull requests, and/or discussion.
<!-- This can add crucial context to your report. -->
## Bonus: What do you use Caddy for? Why did you choose Caddy?
<!-- We'd like to know! -->
+35
View File
@@ -0,0 +1,35 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: feature request
assignees: ''
---
<!--
This template is for feature requests. If you are reporting a bug instead, please switch templates.
Are you asking for help with using Caddy? Please ask on our forum: https://caddy.community
-->
## 1. What would you like to have changed?
<!-- Fully describe the feature or enhancement you are requesting; examples can be helpful too -->
## 2. Why is this feature a useful, necessary, and/or important addition to this project?
<!-- Please justify why this change adds value to the project, considering the added maintenance burden and complexity the change introduces -->
## 3. What alternatives are there, or what are you doing in the meantime to work around the lack of this feature?
<!-- We want to get an idea of what is being done in practice, or how other projects support your feature -->
## 4. Please link to any relevant issues, pull requests, or other discussions.
<!-- This adds crucial context to your feature request and can speed things up -->
@@ -0,0 +1,40 @@
---
name: Pull request
about: Propose changes to the code
title: ''
labels: ''
assignees: ''
---
<!--
Thank you for contributing to Caddy! Please fill this out to help us make the most of your pull request.
Was this change discussed in an issue first? That can help save time in case the change is not a good fit for the project. Not all pull requests get merged.
It is not uncommon for pull requests to go through several, iterative reviews. Please be patient with us! Every reviewer is a volunteer, and each has their own style.
-->
## 1. What does this change do, exactly?
<!-- Please be specific. Motivate the problem, and justify why this is the best solution. -->
## 2. Please link to the relevant issues.
<!-- This adds crucial context to your change. -->
## 3. Which documentation changes (if any) need to be made because of this PR?
<!-- Reviewers will often reference this first in order to know what to expect from the change. Please be specific enough so that they can paste your wording into the documentation directly. -->
## 4. Checklist
- [ ] I have written tests and verified that they fail without my change
- [ ] I have squashed any insignificant commits
- [ ] This change has comments explaining 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
+7 -1
View File
@@ -3,6 +3,7 @@ Thumbs.db
_gitignore/
Vagrantfile
.vagrant/
/.idea
dist/builds/
dist/release/
@@ -12,5 +13,10 @@ access.log
/*.conf
Caddyfile
!caddyfile/
og_static/
og_static/
.vscode/
*.bat
+29 -6
View File
@@ -1,14 +1,37 @@
language: go
addons:
hosts:
- quic.clemente.io
go:
- 1.4.3
- 1.5.1
- 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:
- go get -d ./...
- go get golang.org/x/tools/cmd/vet
- if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then bash dist/gitcookie.sh; fi
- go get -t ./...
- go get golang.org/x/lint/golint
- go get github.com/FiloSottile/vendorcheck
- go get github.com/alecthomas/gometalinter
script:
- go vet ./...
- go test ./...
- 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 ./...
-32
View File
@@ -1,32 +0,0 @@
## Contributing to Caddy
**[Join us on Slack](https://gophers.slack.com/messages/caddy/)** to chat with other Caddy developers! ([Request an invite](http://bit.ly/go-slack-signup), then join the #caddy channel.)
This project gladly accepts contributions and we encourage interested users to get involved!
#### For small tweaks, bug fixes, and tests
Submit [pull requests](https://github.com/mholt/caddy/pulls) at any time. Thank you for helping out in simple ways! Bug fixes should be under test to assert correct behavior.
#### Ideas, questions, bug reports
You should totally [open an issue](https://github.com/mholt/caddy/issues) with your ideas, questions, and bug reports, if one does not already exist for it. Bug reports should state expected behavior and contain clear instructions for reproducing the problem.
#### New features
Before submitting a pull request, please open an issue first to discuss it and claim it. This prevents overlapping efforts and keeps the project in-line with its goals. If you prefer to discuss the feature privately, you can reach other developers on Slack or you may email me directly. (My email address is below.)
And don't forget to write tests for new features!
#### Vulnerabilities
If you've found a vulnerability that is serious, please email me: Matthew dot Holt at Gmail. If it's not a big deal, a pull request will probably be faster.
## Thank you
Thanks for your help! Caddy would not be what it is today without your contributions.
+128 -76
View File
@@ -1,132 +1,184 @@
[![Caddy](https://caddyserver.com/resources/images/caddy-boxed.png)](https://caddyserver.com)
<p align="center">
<a href="https://caddyserver.com"><img src="https://user-images.githubusercontent.com/1128849/36338535-05fb646a-136f-11e8-987b-e6901e717d5a.png" alt="Caddy" width="450"></a>
</p>
<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>
<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>
</p>
<p align="center">
<a href="https://caddyserver.com/download">Download</a> ·
<a href="https://caddyserver.com/docs">Documentation</a> ·
<a href="https://caddy.community">Community</a>
</p>
[![Documentation](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](https://godoc.org/github.com/mholt/caddy) [![Linux Build Status](https://img.shields.io/travis/mholt/caddy.svg?style=flat-square&label=linux+build)](https://travis-ci.org/mholt/caddy) [![Windows Build Status](https://img.shields.io/appveyor/ci/mholt/caddy.svg?style=flat-square&label=windows+build)](https://ci.appveyor.com/project/mholt/caddy)
---
Caddy is a lightweight, general-purpose web server for Windows, Mac, Linux, BSD, and [Android](https://github.com/mholt/caddy/wiki/Running-Caddy-on-Android). It is a capable alternative to other popular and easy to use web servers. ([@caddyserver](https://twitter.com/caddyserver) on Twitter)
Caddy is a **production-ready** open-source web server that is fast, easy to use, and makes you more productive.
The most notable features are HTTP/2, [Let's Encrypt](https://letsencrypt.org) support, Virtual Hosts, TLS + SNI, and easy configuration with a [Caddyfile](https://caddyserver.com/docs/caddyfile). In development, you usually put one Caddyfile with each site. In production, Caddy serves HTTPS by default and manages all cryptographic assets for you.
Available for Windows, Mac, Linux, BSD, Solaris, and [Android](https://github.com/mholt/caddy/wiki/Running-Caddy-on-Android).
[Download](https://github.com/mholt/caddy/releases) · [User Guide](https://caddyserver.com/docs)
<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
### Menu
- [Getting Caddy](#getting-caddy)
- [Features](#features)
- [Install](#install)
- [Quick Start](#quick-start)
- [Running from Source](#running-from-source)
- [Running in Production](#running-in-production)
- [Contributing](#contributing)
- [Donors](#donors)
- [About the Project](#about-the-project)
## Features
- **Easy configuration** with the Caddyfile
- **Automatic HTTPS** on by default (via [Let's Encrypt](https://letsencrypt.org))
- **HTTP/2** by default
- **Virtual hosting** so multiple sites just work
- Experimental **QUIC support** for cutting-edge transmissions
- TLS session ticket **key rotation** for more secure connections
- **Extensible with plugins** because a convenient web server is a helpful one
- **Runs anywhere** with **no external dependencies** (not even libc)
[See a more complete list of features built into Caddy.](https://caddyserver.com/features) On top of all those, Caddy does even more with plugins: choose which plugins you want at [download](https://caddyserver.com/download).
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>
## Getting Caddy
Caddy binaries have no dependencies and are available for nearly every platform.
## Install
[Latest release](https://github.com/mholt/caddy/releases/latest)
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
- **[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.12 or newer). Follow these instruction for fast building:
- 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`
Then make sure the `caddy` binary is in your PATH.
To build for other platforms, use build.go with the `--goos` and `--goarch` flags.
When building from source, telemetry is enabled by default. You can disable it by changing `enableTelemetry` in run.go before compiling, or use the `-disabled-metrics` flag at runtime to disable only certain metrics.
## Quick Start
The website has [full documentation](https://caddyserver.com/docs) but this will get you started in about 30 seconds:
Place a file named "Caddyfile" with your site. Paste this into it and save:
To serve static files from the current working directory, run:
```
caddy
```
Caddy's default port is 2015, so open your browser to [http://localhost:2015](http://localhost:2015).
### Go from 0 to HTTPS in 5 seconds
If the `caddy` binary has permission to bind to low ports and your domain name's DNS records point to the machine you're on:
```
caddy -host example.com
```
This command serves static files from the current directory over HTTPS. Certificates are automatically obtained and renewed for you! Caddy is also automatically configuring ports 80 and 443 for you, and redirecting HTTP to HTTPS. Cool, huh?
### Customizing your site
To customize how your site is served, create a file named Caddyfile by your site and paste this into it:
```plain
localhost
gzip
push
browse
ext .html
websocket /echo cat
log ../access.log
ext .html
log /var/log/access.log
proxy /api 127.0.0.1:7005
header /api Access-Control-Allow-Origin *
```
Run `caddy` from that directory, and it will automatically use that Caddyfile to configure itself.
When you run `caddy` in that directory, it will automatically find and use that Caddyfile.
That simple file enables compression, allows directory browsing (for folders without an index file), serves clean URLs, hosts a WebSocket echo server at /echo, logs requests to access.log, and adds the coveted `Access-Control-Allow-Origin: *` header for all responses from some API.
This simple file enables server push (via Link headers), allows directory browsing (for folders without an index file), hosts a WebSocket echo server at /echo, serves clean URLs, logs requests to an access log, proxies all API requests to a backend on port 7005, and adds the coveted `Access-Control-Allow-Origin: *` header for all responses from the API.
Wow! Caddy can do a lot with just a few lines.
### Doing more with Caddy
#### Defining multiple sites
To host multiple sites and do more with the Caddyfile, please see the [Caddyfile tutorial](https://caddyserver.com/tutorial/caddyfile).
You can run multiple sites from the same Caddyfile, too:
Sites with qualifying hostnames are served over [HTTPS by default](https://caddyserver.com/docs/automatic-https).
```
site1.com {
# ...
}
site2.com, sub.site2.com {
# ...
}
```
Note that all these sites will automatically be served over HTTPS using Let's Encrypt as the CA. Caddy will manage the certificates (including renewals) for you. You don't even have to think about it.
For more documentation, please view [the website](https://caddyserver.com/docs). You may also be interested in the [developer guide](https://github.com/mholt/caddy/wiki) on this project's GitHub wiki.
Caddy has a nice little command line interface. Run `caddy -h` to view basic help or see the [CLI documentation](https://caddyserver.com/docs/cli) for details.
## Running in Production
Caddy is production-ready if you find it to be a good fit for your site and workflow.
## Running from Source
**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`
Note: You will need **[Go 1.4](https://golang.org/dl)** or newer
1. `$ go get github.com/mholt/caddy`
2. `cd` into your website's directory
3. Run `caddy` (assumes `$GOPATH/bin` is in your `$PATH`)
If you're tinkering, you can also use `go run main.go`.
By default, Caddy serves the current directory at [localhost:2015](http://localhost:2015). You can place a Caddyfile to configure Caddy for serving your site.
Caddy accepts some flags from the command line. Run `caddy -h` to view the help for flags. You can also pipe a Caddyfile into the caddy command.
**Running as root:** We advise against this; use setcap instead, like so: `setcap cap_net_bind_service=+ep ./caddy` This will allow you to listen on ports < 1024 like 80 and 443.
#### Docker Container
Caddy is available as a Docker container from any of these sources:
- [abiosoft/caddy](https://registry.hub.docker.com/u/abiosoft/caddy/)
- [darron/caddy](https://registry.hub.docker.com/u/darron/caddy/)
- [joshix/caddy](https://registry.hub.docker.com/u/joshix/caddy/)
- [jumanjiman/caddy](https://registry.hub.docker.com/u/jumanjiman/caddy/)
- [zenithar/nano-caddy](https://registry.hub.docker.com/u/zenithar/nano-caddy/)
#### 3rd-party dependencies
Although Caddy's binaries are completely static, Caddy relies on some excellent libraries. [Godoc.org](https://godoc.org/github.com/mholt/caddy) shows the packages that each Caddy package imports.
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.
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.
If you have questions or concerns about Caddy' underlying crypto implementations, consult Go's [crypto packages](https://golang.org/pkg/crypto), starting with their documentation, then issues, then the code itself; as Caddy uses mainly those libraries.
## Contributing
**[Join us on Slack](https://gophers.slack.com/messages/caddy/)** to chat with other Caddy developers! ([Request an invite](http://bit.ly/go-slack-signup), then join the #caddy channel.)
**[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/)!
This project would not be what it is without your help. Please see the [contributing guidelines](https://github.com/mholt/caddy/blob/master/CONTRIBUTING.md) if you haven't already.
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).
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 an issue](https://github.com/mholt/caddy/issues/new) describing the change that should be made.
Thanks for making Caddy -- and the Web -- better!
Special thanks to [![DigitalOcean](http://i.imgur.com/sfGr0eY.png)](https://www.digitalocean.com) for hosting the Caddy project.
## Donors
- [DigitalOcean](https://m.do.co/c/6d7bdafccf96) is hosting the Caddy project.
- [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)!**
## About the Project
Caddy was born out of the need for a "batteries-included" web server that runs anywhere and doesn't have to take its configuration with it. Caddy took inspiration from [spark](https://github.com/rif/spark), [nginx](https://github.com/nginx/nginx), lighttpd,
[Websocketd](https://github.com/joewalnes/websocketd) and [Vagrant](https://www.vagrantup.com/), which provides a pleasant mixture of features from each of them.
## About the project
**The name "Caddy" is trademarked:** The name of the software is "Caddy", not "Caddy Server" or "CaddyServer". Please call it "Caddy" or, if you wish to clarify, "the Caddy web server". See [brand guidelines](https://caddyserver.com/brand). Caddy is a registered trademark of Light Code Labs, LLC.
Caddy was born out of the need for a "batteries-included" web server that runs anywhere and doesn't have to take its configuration with it. Caddy took inspiration from [spark](https://github.com/rif/spark), nginx, lighttpd, Websocketd, and Vagrant, and provides a pleasant mixture of features from each of them.
*Twitter: [@mholt6](https://twitter.com/mholt6)*
*Author on Twitter: [@mholt6](https://twitter.com/mholt6)*
+25 -6
View File
@@ -1,5 +1,8 @@
version: "{build}"
hosts:
quic.clemente.io: 127.0.0.1
os: Windows Server 2012 R2
clone_folder: c:\gopath\src\github.com\mholt\caddy
@@ -7,13 +10,29 @@ clone_folder: c:\gopath\src\github.com\mholt\caddy
environment:
GOPATH: c:\gopath
stack: go 1.12
install:
- go get golang.org/x/tools/cmd/vet
- echo %GOPATH%
- set PATH=%GOPATH%\bin;%PATH%
- set PATH=C:\msys64\mingw64\bin;%PATH%
- go version
- go env
- go get -d ./...
- go get -t ./...
- go get golang.org/x/lint/golint
- go get github.com/FiloSottile/vendorcheck
- go get github.com/alecthomas/gometalinter
build_script:
- go vet ./...
- go test ./...
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
skip_branch_with_pr: true
+48
View File
@@ -0,0 +1,48 @@
// 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.
package caddy
import (
"os"
"path/filepath"
"runtime"
)
// AssetsPath returns the path to the folder
// where the application may store data. If
// CADDYPATH env variable is set, that value
// is used. Otherwise, the path is the result
// of evaluating "$HOME/.caddy".
func AssetsPath() string {
if caddyPath := os.Getenv("CADDYPATH"); caddyPath != "" {
return caddyPath
}
return filepath.Join(userHomeDir(), ".caddy")
}
// userHomeDir returns the user's home directory according to
// environment variables.
//
// Credit: http://stackoverflow.com/a/7922977/1048862
func userHomeDir() string {
if runtime.GOOS == "windows" {
home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
if home == "" {
home = os.Getenv("USERPROFILE")
}
return home
}
return os.Getenv("HOME")
}
+39
View File
@@ -0,0 +1,39 @@
// 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.
package caddy
import (
"os"
"strings"
"testing"
)
func TestAssetsPath(t *testing.T) {
if actual := AssetsPath(); !strings.HasSuffix(actual, ".caddy") {
t.Errorf("Expected path to be a .caddy folder, got: %v", actual)
}
err := os.Setenv("CADDYPATH", "testpath")
if err != nil {
t.Error("Could not set CADDYPATH")
}
if actual, expected := AssetsPath(), "testpath"; actual != expected {
t.Errorf("Expected path to be %v, got: %v", expected, actual)
}
err = os.Setenv("CADDYPATH", "")
if err != nil {
t.Error("Could not set CADDYPATH")
}
}
+1025
View File
File diff suppressed because it is too large Load Diff
-29
View File
@@ -1,29 +0,0 @@
package assets
import (
"os"
"path/filepath"
"runtime"
)
// Path returns the path to the folder
// where the application may store data. This
// currently resolves to ~/.caddy
func Path() string {
return filepath.Join(userHomeDir(), ".caddy")
}
// userHomeDir returns the user's home directory according to
// environment variables.
//
// Credit: http://stackoverflow.com/a/7922977/1048862
func userHomeDir() string {
if runtime.GOOS == "windows" {
home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
if home == "" {
home = os.Getenv("USERPROFILE")
}
return home
}
return os.Getenv("HOME")
}
-12
View File
@@ -1,12 +0,0 @@
package assets
import (
"strings"
"testing"
)
func TestPath(t *testing.T) {
if actual := Path(); !strings.HasSuffix(actual, ".caddy") {
t.Errorf("Expected path to be a .caddy folder, got: %v", actual)
}
}
+95
View File
@@ -0,0 +1,95 @@
// 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
var race bool
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")
flag.BoolVar(&race, "race", false, "Enable race detector")
}
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))
if race {
args = append(args, "-race")
}
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,
} {
if race && env == "CGO_ENABLED=0" {
continue
}
cmd.Env = append(cmd.Env, env)
}
err = cmd.Run()
if err != nil {
log.Fatal(err)
}
}
-378
View File
@@ -1,378 +0,0 @@
// Package caddy implements the Caddy web server as a service.
//
// To use this package, follow a few simple steps:
//
// 1. Set the AppName and AppVersion variables.
// 2. Call LoadCaddyfile() to get the Caddyfile (it
// might have been piped in as part of a restart).
// You should pass in your own Caddyfile loader.
// 3. Call caddy.Start() to start Caddy, caddy.Stop()
// to stop it, or caddy.Restart() to restart it.
//
// You should use caddy.Wait() to wait for all Caddy servers
// to quit before your process exits.
package caddy
import (
"bytes"
"encoding/gob"
"errors"
"fmt"
"io/ioutil"
"log"
"net"
"os"
"path"
"strings"
"sync"
"time"
"github.com/mholt/caddy/caddy/letsencrypt"
"github.com/mholt/caddy/server"
)
// Configurable application parameters
var (
// The name and version of the application.
AppName string
AppVersion string
// If true, initialization will not show any informative output.
Quiet bool
// HTTP2 indicates whether HTTP2 is enabled or not.
HTTP2 bool // TODO: temporary flag until http2 is standard
// PidFile is the path to the pidfile to create.
PidFile string
// GracefulTimeout is the maximum duration of a graceful shutdown.
GracefulTimeout time.Duration
)
var (
// caddyfile is the input configuration text used for this process
caddyfile Input
// caddyfileMu protects caddyfile during changes
caddyfileMu sync.Mutex
// errIncompleteRestart occurs if this process is a fork
// of the parent but no Caddyfile was piped in
errIncompleteRestart = errors.New("incomplete restart")
// servers is a list of all the currently-listening servers
servers []*server.Server
// serversMu protects the servers slice during changes
serversMu sync.Mutex
// wg is used to wait for all servers to shut down
wg sync.WaitGroup
// loadedGob is used if this is a child process as part of
// a graceful restart; it is used to map listeners to their
// index in the list of inherited file descriptors. This
// variable is not safe for concurrent access.
loadedGob caddyfileGob
// startedBefore should be set to true if caddy has been started
// at least once (does not indicate whether currently running).
startedBefore bool
)
const (
// DefaultHost is the default host.
DefaultHost = ""
// DefaultPort is the default port.
DefaultPort = "2015"
// DefaultRoot is the default root folder.
DefaultRoot = "."
)
// Start starts Caddy with the given Caddyfile. If cdyfile
// is nil, the LoadCaddyfile function will be called to get
// one.
//
// This function blocks until all the servers are listening.
//
// Note (POSIX): If Start is called in the child process of a
// restart more than once within the duration of the graceful
// cutoff (i.e. the child process called Start a first time,
// then called Stop, then Start again within the first 5 seconds
// or however long GracefulTimeout is) and the Caddyfiles have
// at least one listener address in common, the second Start
// may fail with "address already in use" as there's no
// guarantee that the parent process has relinquished the
// address before the grace period ends.
func Start(cdyfile Input) (err error) {
// If we return with no errors, we must do two things: tell the
// parent that we succeeded and write to the pidfile.
defer func() {
if err == nil {
signalSuccessToParent() // TODO: Is doing this more than once per process a bad idea? Start could get called more than once in other apps.
if PidFile != "" {
err := writePidFile()
if err != nil {
log.Printf("[ERROR] Could not write pidfile: %v", err)
}
}
}
}()
// Input must never be nil; try to load something
if cdyfile == nil {
cdyfile, err = LoadCaddyfile(nil)
if err != nil {
return err
}
}
caddyfileMu.Lock()
caddyfile = cdyfile
caddyfileMu.Unlock()
// load the server configs (activates Let's Encrypt)
configs, err := loadConfigs(path.Base(cdyfile.Path()), bytes.NewReader(cdyfile.Body()))
if err != nil {
return err
}
// group virtualhosts by address
groupings, err := arrangeBindings(configs)
if err != nil {
return err
}
// Start each server with its one or more configurations
err = startServers(groupings)
if err != nil {
return err
}
startedBefore = true
// Show initialization output
if !Quiet && !IsRestart() {
var checkedFdLimit bool
for _, group := range groupings {
for _, conf := range group.Configs {
// Print address of site
fmt.Println(conf.Address())
// Note if non-localhost site resolves to loopback interface
if group.BindAddr.IP.IsLoopback() && !isLocalhost(conf.Host) {
fmt.Printf("Notice: %s is only accessible on this machine (%s)\n",
conf.Host, group.BindAddr.IP.String())
}
if !checkedFdLimit && !group.BindAddr.IP.IsLoopback() && !isLocalhost(conf.Host) {
checkFdlimit()
checkedFdLimit = true
}
}
}
}
return nil
}
// startServers starts all the servers in groupings,
// taking into account whether or not this process is
// a child from a graceful restart or not. It blocks
// until the servers are listening.
func startServers(groupings bindingGroup) error {
var startupWg sync.WaitGroup
errChan := make(chan error, len(groupings)) // must be buffered to allow Serve functions below to return if stopped later
for _, group := range groupings {
s, err := server.New(group.BindAddr.String(), group.Configs, GracefulTimeout)
if err != nil {
return err
}
s.HTTP2 = HTTP2 // TODO: This setting is temporary
var ln server.ListenerFile
if IsRestart() {
// Look up this server's listener in the map of inherited file descriptors;
// if we don't have one, we must make a new one (later).
if fdIndex, ok := loadedGob.ListenerFds[s.Addr]; ok {
file := os.NewFile(fdIndex, "")
fln, err := net.FileListener(file)
if err != nil {
return err
}
ln, ok = fln.(server.ListenerFile)
if !ok {
return errors.New("listener for " + s.Addr + " was not a ListenerFile")
}
file.Close()
delete(loadedGob.ListenerFds, s.Addr)
}
}
wg.Add(1)
go func(s *server.Server, ln server.ListenerFile) {
defer wg.Done()
// run startup functions that should only execute when
// the original parent process is starting.
if !IsRestart() && !startedBefore {
err := s.RunFirstStartupFuncs()
if err != nil {
errChan <- err
return
}
}
// start the server
if ln != nil {
errChan <- s.Serve(ln)
} else {
errChan <- s.ListenAndServe()
}
}(s, ln)
startupWg.Add(1)
go func(s *server.Server) {
defer startupWg.Done()
s.WaitUntilStarted()
}(s)
serversMu.Lock()
servers = append(servers, s)
serversMu.Unlock()
}
// Close the remaining (unused) file descriptors to free up resources
if IsRestart() {
for key, fdIndex := range loadedGob.ListenerFds {
os.NewFile(fdIndex, "").Close()
delete(loadedGob.ListenerFds, key)
}
}
// Wait for all servers to finish starting
startupWg.Wait()
// Return the first error, if any
select {
case err := <-errChan:
// "use of closed network connection" is normal if it was a graceful shutdown
if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
return err
}
default:
}
return nil
}
// Stop stops all servers. It blocks until they are all stopped.
// It does NOT execute shutdown callbacks that may have been
// configured by middleware (they must be executed separately).
func Stop() error {
letsencrypt.Deactivate()
serversMu.Lock()
for _, s := range servers {
if err := s.Stop(); err != nil {
log.Printf("[ERROR] Stopping %s: %v", s.Addr, err)
}
}
servers = []*server.Server{} // don't reuse servers
serversMu.Unlock()
return nil
}
// Wait blocks until all servers are stopped.
func Wait() {
wg.Wait()
}
// LoadCaddyfile loads a Caddyfile, prioritizing a Caddyfile
// piped from stdin as part of a restart (only happens on first call
// to LoadCaddyfile). If it is not a restart, this function tries
// calling the user's loader function, and if that returns nil, then
// this function resorts to the default configuration. Thus, if there
// are no other errors, this function always returns at least the
// default Caddyfile.
func LoadCaddyfile(loader func() (Input, error)) (cdyfile Input, err error) {
// If we are a fork, finishing the restart is highest priority;
// piped input is required in this case.
if IsRestart() {
err := gob.NewDecoder(os.Stdin).Decode(&loadedGob)
if err != nil {
return nil, err
}
cdyfile = loadedGob.Caddyfile
}
// Try user's loader
if cdyfile == nil && loader != nil {
cdyfile, err = loader()
}
// Otherwise revert to default
if cdyfile == nil {
cdyfile = DefaultInput()
}
return
}
// CaddyfileFromPipe loads the Caddyfile input from f if f is
// not interactive input. f is assumed to be a pipe or stream,
// such as os.Stdin. If f is not a pipe, no error is returned
// but the Input value will be nil. An error is only returned
// if there was an error reading the pipe, even if the length
// of what was read is 0.
func CaddyfileFromPipe(f *os.File) (Input, error) {
fi, err := f.Stat()
if err == nil && fi.Mode()&os.ModeCharDevice == 0 {
// Note that a non-nil error is not a problem. Windows
// will not create a stdin if there is no pipe, which
// produces an error when calling Stat(). But Unix will
// make one either way, which is why we also check that
// bitmask.
// BUG: Reading from stdin after this fails (e.g. for the let's encrypt email address) (OS X)
confBody, err := ioutil.ReadAll(f)
if err != nil {
return nil, err
}
return CaddyfileInput{
Contents: confBody,
Filepath: f.Name(),
}, nil
}
// not having input from the pipe is not itself an error,
// just means no input to return.
return nil, nil
}
// Caddyfile returns the current Caddyfile
func Caddyfile() Input {
caddyfileMu.Lock()
defer caddyfileMu.Unlock()
return caddyfile
}
// Input represents a Caddyfile; its contents and file path
// (which should include the file name at the end of the path).
// If path does not apply (e.g. piped input) you may use
// any understandable value. The path is mainly used for logging,
// error messages, and debugging.
type Input interface {
// Gets the Caddyfile contents
Body() []byte
// Gets the path to the origin file
Path() string
// IsFile returns true if the original input was a file on the file system
// that could be loaded again later if requested.
IsFile() bool
}
-32
View File
@@ -1,32 +0,0 @@
package caddy
import (
"net/http"
"testing"
"time"
)
func TestCaddyStartStop(t *testing.T) {
caddyfile := "localhost:1984\ntls off"
for i := 0; i < 2; i++ {
err := Start(CaddyfileInput{Contents: []byte(caddyfile)})
if err != nil {
t.Fatalf("Error starting, iteration %d: %v", i, err)
}
client := http.Client{
Timeout: time.Duration(2 * time.Second),
}
resp, err := client.Get("http://localhost:1984")
if err != nil {
t.Fatalf("Expected GET request to succeed (iteration %d), but it failed: %v", i, err)
}
resp.Body.Close()
err = Stop()
if err != nil {
t.Fatalf("Error stopping, iteration %d: %v", i, err)
}
}
}
-126
View File
@@ -1,126 +0,0 @@
package caddyfile
import "testing"
var tests = []struct {
caddyfile, json string
}{
{ // 0
caddyfile: `foo {
root /bar
}`,
json: `[{"hosts":["foo"],"body":[["root","/bar"]]}]`,
},
{ // 1
caddyfile: `host1, host2 {
dir {
def
}
}`,
json: `[{"hosts":["host1","host2"],"body":[["dir",[["def"]]]]}]`,
},
{ // 2
caddyfile: `host1, host2 {
dir abc {
def ghi
jkl
}
}`,
json: `[{"hosts":["host1","host2"],"body":[["dir","abc",[["def","ghi"],["jkl"]]]]}]`,
},
{ // 3
caddyfile: `host1:1234, host2:5678 {
dir abc {
}
}`,
json: `[{"hosts":["host1:1234","host2:5678"],"body":[["dir","abc",[]]]}]`,
},
{ // 4
caddyfile: `host {
foo "bar baz"
}`,
json: `[{"hosts":["host"],"body":[["foo","bar baz"]]}]`,
},
{ // 5
caddyfile: `host, host:80 {
foo "bar \"baz\""
}`,
json: `[{"hosts":["host","host:80"],"body":[["foo","bar \"baz\""]]}]`,
},
{ // 6
caddyfile: `host {
foo "bar
baz"
}`,
json: `[{"hosts":["host"],"body":[["foo","bar\nbaz"]]}]`,
},
{ // 7
caddyfile: `host {
dir 123 4.56 true
}`,
json: `[{"hosts":["host"],"body":[["dir","123","4.56","true"]]}]`, // NOTE: I guess we assume numbers and booleans should be encoded as strings...?
},
{ // 8
caddyfile: `http://host, https://host {
}`,
json: `[{"hosts":["host:http","host:https"],"body":[]}]`, // hosts in JSON are always host:port format (if port is specified), for consistency
},
{ // 9
caddyfile: `host {
dir1 a b
dir2 c d
}`,
json: `[{"hosts":["host"],"body":[["dir1","a","b"],["dir2","c","d"]]}]`,
},
{ // 10
caddyfile: `host {
dir a b
dir c d
}`,
json: `[{"hosts":["host"],"body":[["dir","a","b"],["dir","c","d"]]}]`,
},
{ // 11
caddyfile: `host {
dir1 a b
dir2 {
c
d
}
}`,
json: `[{"hosts":["host"],"body":[["dir1","a","b"],["dir2",[["c"],["d"]]]]}]`,
},
{ // 12
caddyfile: `host1 {
dir1
}
host2 {
dir2
}`,
json: `[{"hosts":["host1"],"body":[["dir1"]]},{"hosts":["host2"],"body":[["dir2"]]}]`,
},
}
func TestToJSON(t *testing.T) {
for i, test := range tests {
output, err := ToJSON([]byte(test.caddyfile))
if err != nil {
t.Errorf("Test %d: %v", i, err)
}
if string(output) != test.json {
t.Errorf("Test %d\nExpected:\n'%s'\nActual:\n'%s'", i, test.json, string(output))
}
}
}
func TestFromJSON(t *testing.T) {
for i, test := range tests {
output, err := FromJSON([]byte(test.json))
if err != nil {
t.Errorf("Test %d: %v", i, err)
}
if string(output) != test.caddyfile {
t.Errorf("Test %d\nExpected:\n'%s'\nActual:\n'%s'", i, test.caddyfile, string(output))
}
}
}
+601
View File
@@ -0,0 +1,601 @@
// 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.
package caddymain
import (
"bufio"
"errors"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"github.com/google/uuid"
"github.com/klauspost/cpuid"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyfile"
"github.com/mholt/caddy/caddytls"
"github.com/mholt/caddy/telemetry"
"github.com/mholt/certmagic"
lumberjack "gopkg.in/natefinch/lumberjack.v2"
_ "github.com/mholt/caddy/caddyhttp" // plug in the HTTP server type
// This is where other plugins get plugged in (imported)
)
func init() {
caddy.TrapSignals()
setVersion()
flag.BoolVar(&certmagic.Agreed, "agree", false, "Agree to the CA's Subscriber Agreement")
flag.StringVar(&certmagic.CA, "ca", certmagic.CA, "URL to certificate authority's ACME server directory")
flag.StringVar(&certmagic.DefaultServerName, "default-sni", certmagic.DefaultServerName, "If a ClientHello ServerName is empty, use this ServerName to choose a TLS certificate")
flag.BoolVar(&certmagic.DisableHTTPChallenge, "disable-http-challenge", certmagic.DisableHTTPChallenge, "Disable the ACME HTTP challenge")
flag.BoolVar(&certmagic.DisableTLSALPNChallenge, "disable-tls-alpn-challenge", certmagic.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.StringVar(&envFile, "env", "", "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(&certmagic.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.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")
caddy.RegisterCaddyfileLoader("flag", caddy.LoaderFunc(confLoader))
caddy.SetDefaultCaddyfileLoader("default", caddy.LoaderFunc(defaultLoader))
}
// Run is Caddy's main() function.
func Run() {
flag.Parse()
caddy.AppName = appName
caddy.AppVersion = appVersion
certmagic.UserAgent = appName + "/" + appVersion
// Set up process log before anything bad happens
switch logfile {
case "stdout":
log.SetOutput(os.Stdout)
case "stderr":
log.SetOutput(os.Stderr)
case "":
log.SetOutput(ioutil.Discard)
default:
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)
}
// 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
if revoke != "" {
err := caddytls.Revoke(revoke)
if err != nil {
mustLogFatalf("%v", err)
}
fmt.Printf("Revoked certificate for %s\n", revoke)
os.Exit(0)
}
if version {
fmt.Printf("%s %s (unofficial)\n", appName, appVersion)
if devBuild && gitShortStat != "" {
fmt.Printf("%s\n%s\n", gitShortStat, gitFilesModified)
}
os.Exit(0)
}
if plugins {
fmt.Println(caddy.DescribePlugins())
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 {
mustLogFatalf("%v", err)
}
// Executes Startup events
caddy.EmitEvent(caddy.StartupEvent, nil)
// Get Caddyfile input
caddyfileinput, err := caddy.LoadCaddyfile(serverType)
if err != nil {
mustLogFatalf("%v", err)
}
if validate {
err := caddy.ValidateAndExecuteDirectives(caddyfileinput, nil, true)
if err != nil {
mustLogFatalf("%v", err)
}
msg := "Caddyfile is valid"
fmt.Println(msg)
log.Printf("[INFO] %s", msg)
os.Exit(0)
}
// Start your engines
instance, err := caddy.Start(caddyfileinput)
if err != nil {
mustLogFatalf("%v", err)
}
// Begin telemetry (these are no-ops if telemetry disabled)
telemetry.Set("caddy_version", appVersion)
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()
}
// mustLogFatalf wraps log.Fatalf() in a way that ensures the
// output is always printed to stderr so the user can see it
// if the user is still there, even if the process log was not
// enabled. If this process is an upgrade, however, and the user
// might not be there anymore, this just logs to the process
// log and exits.
func mustLogFatalf(format string, args ...interface{}) {
if !caddy.IsUpgrade() {
log.SetOutput(os.Stderr)
}
log.Fatalf(format, args...)
}
// confLoader loads the Caddyfile using the -conf flag.
func confLoader(serverType string) (caddy.Input, error) {
if conf == "" {
return nil, nil
}
if conf == "stdin" {
return caddy.CaddyfileFromPipe(os.Stdin, serverType)
}
var contents []byte
if strings.Contains(conf, "*") {
// Let caddyfile.doImport logic handle the globbed path
contents = []byte("import " + conf)
} else {
var err error
contents, err = ioutil.ReadFile(conf)
if err != nil {
return nil, err
}
}
return caddy.CaddyfileInput{
Contents: contents,
Filepath: conf,
ServerTypeName: serverType,
}, nil
}
// defaultLoader loads the Caddyfile from the current working directory.
func defaultLoader(serverType string) (caddy.Input, error) {
contents, err := ioutil.ReadFile(caddy.DefaultConfigFile)
if err != nil {
if os.IsNotExist(err) {
return nil, nil
}
return nil, err
}
return caddy.CaddyfileInput{
Contents: contents,
Filepath: caddy.DefaultConfigFile,
ServerTypeName: serverType,
}, 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")
}
}
}
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
// according to its value. It accepts either
// a number (e.g. 3) or a percent (e.g. 50%).
// If the percent resolves to less than a single
// GOMAXPROCS, it rounds it up to GOMAXPROCS=1.
func setCPU(cpu string) error {
var numCPU int
availCPU := runtime.NumCPU()
if strings.HasSuffix(cpu, "%") {
// Percent
var percent float32
pctStr := cpu[:len(cpu)-1]
pctInt, err := strconv.Atoi(pctStr)
if err != nil || pctInt < 1 || pctInt > 100 {
return errors.New("invalid CPU value: percentage must be between 1-100")
}
percent = float32(pctInt) / 100
numCPU = int(float32(availCPU) * percent)
if numCPU < 1 {
numCPU = 1
}
} else {
// Number
num, err := strconv.Atoi(cpu)
if err != nil || num < 1 {
return errors.New("invalid CPU value: provide a number or percent greater than 0")
}
numCPU = num
}
if numCPU > availCPU {
numCPU = availCPU
}
runtime.GOMAXPROCS(numCPU)
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
envFile string
fromJSON bool
logfile string
logRollMB int
logRollCompress bool
revoke string
toJSON bool
version bool
plugins 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
+118
View File
@@ -0,0 +1,118 @@
// 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.
package caddymain
import (
"reflect"
"runtime"
"strings"
"testing"
)
func TestSetCPU(t *testing.T) {
currentCPU := runtime.GOMAXPROCS(-1)
maxCPU := runtime.NumCPU()
halfCPU := int(0.5 * float32(maxCPU))
if halfCPU < 1 {
halfCPU = 1
}
for i, test := range []struct {
input string
output int
shouldErr bool
}{
{"1", 1, false},
{"-1", currentCPU, true},
{"0", currentCPU, true},
{"100%", maxCPU, false},
{"50%", halfCPU, false},
{"110%", currentCPU, true},
{"-10%", currentCPU, true},
{"invalid input", currentCPU, true},
{"invalid input%", currentCPU, true},
{"9999", maxCPU, false}, // over available CPU
{"1%", 1, false}, // under a single CPU; assume maxCPU < 100
} {
err := setCPU(test.input)
if test.shouldErr && err == nil {
t.Errorf("Test %d: Expected error, but there wasn't any", i)
}
if !test.shouldErr && err != nil {
t.Errorf("Test %d: Expected no error, but there was one: %v", i, err)
}
if actual, expected := runtime.GOMAXPROCS(-1), test.output; actual != expected {
t.Errorf("Test %d: GOMAXPROCS was %d but expected %d", i, actual, expected)
}
// teardown
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)
}
})
}
}
-372
View File
@@ -1,372 +0,0 @@
package caddy
import (
"bytes"
"fmt"
"io"
"log"
"net"
"sync"
"github.com/mholt/caddy/caddy/letsencrypt"
"github.com/mholt/caddy/caddy/parse"
"github.com/mholt/caddy/caddy/setup"
"github.com/mholt/caddy/middleware"
"github.com/mholt/caddy/server"
)
const (
// DefaultConfigFile is the name of the configuration file that is loaded
// by default if no other file is specified.
DefaultConfigFile = "Caddyfile"
)
// loadConfigs reads input (named filename) and parses it, returning the
// server configurations in the order they appeared in the input. As part
// of this, it activates Let's Encrypt for the configs that are produced.
// Thus, the returned configs are already optimally configured optimally
// for HTTPS.
func loadConfigs(filename string, input io.Reader) ([]server.Config, error) {
var configs []server.Config
// Each server block represents similar hosts/addresses, since they
// were grouped together in the Caddyfile.
serverBlocks, err := parse.ServerBlocks(filename, input, true)
if err != nil {
return nil, err
}
if len(serverBlocks) == 0 {
newInput := DefaultInput()
serverBlocks, err = parse.ServerBlocks(newInput.Path(), bytes.NewReader(newInput.Body()), true)
if err != nil {
return nil, err
}
}
var lastDirectiveIndex int // we set up directives in two parts; this stores where we left off
// Iterate each server block and make a config for each one,
// executing the directives that were parsed in order up to the tls
// directive; this is because we must activate Let's Encrypt.
for i, sb := range serverBlocks {
onces := makeOnces()
storages := makeStorages()
for j, addr := range sb.Addresses {
config := server.Config{
Host: addr.Host,
Port: addr.Port,
Root: Root,
Middleware: make(map[string][]middleware.Middleware),
ConfigFile: filename,
AppName: AppName,
AppVersion: AppVersion,
}
// It is crucial that directives are executed in the proper order.
for k, dir := range directiveOrder {
// Execute directive if it is in the server block
if tokens, ok := sb.Tokens[dir.name]; ok {
// Each setup function gets a controller, from which setup functions
// get access to the config, tokens, and other state information useful
// to set up its own host only.
controller := &setup.Controller{
Config: &config,
Dispenser: parse.NewDispenserTokens(filename, tokens),
OncePerServerBlock: func(f func() error) error {
var err error
onces[dir.name].Do(func() {
err = f()
})
return err
},
ServerBlockIndex: i,
ServerBlockHostIndex: j,
ServerBlockHosts: sb.HostList(),
ServerBlockStorage: storages[dir.name],
}
// execute setup function and append middleware handler, if any
midware, err := dir.setup(controller)
if err != nil {
return nil, err
}
if midware != nil {
// TODO: For now, we only support the default path scope /
config.Middleware["/"] = append(config.Middleware["/"], midware)
}
storages[dir.name] = controller.ServerBlockStorage // persist for this server block
}
// Stop after TLS setup, since we need to activate Let's Encrypt before continuing;
// it makes some changes to the configs that middlewares might want to know about.
if dir.name == "tls" {
lastDirectiveIndex = k
break
}
}
configs = append(configs, config)
}
}
// Now we have all the configs, but they have only been set up to the
// point of tls. We need to activate Let's Encrypt before setting up
// the rest of the middlewares so they have correct information regarding
// TLS configuration, if necessary. (this call is append-only, so our
// iterations below shouldn't be affected)
if !IsRestart() && !Quiet {
fmt.Print("Activating privacy features...")
}
configs, err = letsencrypt.Activate(configs)
if err != nil {
if !Quiet {
fmt.Println()
}
return nil, err
}
if !IsRestart() && !Quiet {
fmt.Println(" done.")
}
// Finish setting up the rest of the directives, now that TLS is
// optimally configured. These loops are similar to above except
// we don't iterate all the directives from the beginning and we
// don't create new configs.
configIndex := -1
for i, sb := range serverBlocks {
onces := makeOnces()
storages := makeStorages()
for j := range sb.Addresses {
configIndex++
for k := lastDirectiveIndex + 1; k < len(directiveOrder); k++ {
dir := directiveOrder[k]
if tokens, ok := sb.Tokens[dir.name]; ok {
controller := &setup.Controller{
Config: &configs[configIndex],
Dispenser: parse.NewDispenserTokens(filename, tokens),
OncePerServerBlock: func(f func() error) error {
var err error
onces[dir.name].Do(func() {
err = f()
})
return err
},
ServerBlockIndex: i,
ServerBlockHostIndex: j,
ServerBlockHosts: sb.HostList(),
ServerBlockStorage: storages[dir.name],
}
midware, err := dir.setup(controller)
if err != nil {
return nil, err
}
if midware != nil {
// TODO: For now, we only support the default path scope /
configs[configIndex].Middleware["/"] = append(configs[configIndex].Middleware["/"], midware)
}
storages[dir.name] = controller.ServerBlockStorage // persist for this server block
}
}
}
}
return configs, nil
}
// makeOnces makes a map of directive name to sync.Once
// instance. This is intended to be called once per server
// block when setting up configs so that Setup functions
// for each directive can perform a task just once per
// server block, even if there are multiple hosts on the block.
//
// We need one Once per directive, otherwise the first
// directive to use it would exclude other directives from
// using it at all, which would be a bug.
func makeOnces() map[string]*sync.Once {
onces := make(map[string]*sync.Once)
for _, dir := range directiveOrder {
onces[dir.name] = new(sync.Once)
}
return onces
}
// makeStorages makes a map of directive name to interface{}
// so that directives' setup functions can persist state
// between different hosts on the same server block during the
// setup phase.
func makeStorages() map[string]interface{} {
storages := make(map[string]interface{})
for _, dir := range directiveOrder {
storages[dir.name] = nil
}
return storages
}
// arrangeBindings groups configurations by their bind address. For example,
// a server that should listen on localhost and another on 127.0.0.1 will
// be grouped into the same address: 127.0.0.1. It will return an error
// if an address is malformed or a TLS listener is configured on the
// same address as a plaintext HTTP listener. The return value is a map of
// bind address to list of configs that would become VirtualHosts on that
// server. Use the keys of the returned map to create listeners, and use
// the associated values to set up the virtualhosts.
func arrangeBindings(allConfigs []server.Config) (bindingGroup, error) {
var groupings bindingGroup
// Group configs by bind address
for _, conf := range allConfigs {
// use default port if none is specified
if conf.Port == "" {
conf.Port = Port
}
bindAddr, warnErr, fatalErr := resolveAddr(conf)
if fatalErr != nil {
return groupings, fatalErr
}
if warnErr != nil {
log.Printf("[WARNING] Resolving bind address for %s: %v", conf.Address(), warnErr)
}
// Make sure to compare the string representation of the address,
// not the pointer, since a new *TCPAddr is created each time.
var existing bool
for i := 0; i < len(groupings); i++ {
if groupings[i].BindAddr.String() == bindAddr.String() {
groupings[i].Configs = append(groupings[i].Configs, conf)
existing = true
break
}
}
if !existing {
groupings = append(groupings, bindingMapping{
BindAddr: bindAddr,
Configs: []server.Config{conf},
})
}
}
// Don't allow HTTP and HTTPS to be served on the same address
for _, group := range groupings {
isTLS := group.Configs[0].TLS.Enabled
for _, config := range group.Configs {
if config.TLS.Enabled != isTLS {
thisConfigProto, otherConfigProto := "HTTP", "HTTP"
if config.TLS.Enabled {
thisConfigProto = "HTTPS"
}
if group.Configs[0].TLS.Enabled {
otherConfigProto = "HTTPS"
}
return groupings, fmt.Errorf("configuration error: Cannot multiplex %s (%s) and %s (%s) on same address",
group.Configs[0].Address(), otherConfigProto, config.Address(), thisConfigProto)
}
}
}
return groupings, nil
}
// resolveAddr determines the address (host and port) that a config will
// bind to. The returned address, resolvAddr, should be used to bind the
// listener or group the config with other configs using the same address.
// The first error, if not nil, is just a warning and should be reported
// but execution may continue. The second error, if not nil, is a real
// problem and the server should not be started.
//
// This function handles edge cases gracefully. If a port name like
// "http" or "https" is unknown to the system, this function will
// change them to 80 or 443 respectively. If a hostname fails to
// resolve, that host can still be served but will be listening on
// the wildcard host instead. This function takes care of this for you.
func resolveAddr(conf server.Config) (resolvAddr *net.TCPAddr, warnErr, fatalErr error) {
bindHost := conf.BindHost
// TODO: Do we even need the port? Maybe we just need to look up the host.
resolvAddr, warnErr = net.ResolveTCPAddr("tcp", net.JoinHostPort(bindHost, conf.Port))
if warnErr != nil {
// Most likely the host lookup failed or the port is unknown
tryPort := conf.Port
switch errVal := warnErr.(type) {
case *net.AddrError:
if errVal.Err == "unknown port" {
// some odd Linux machines don't support these port names; see issue #136
switch conf.Port {
case "http":
tryPort = "80"
case "https":
tryPort = "443"
}
}
resolvAddr, fatalErr = net.ResolveTCPAddr("tcp", net.JoinHostPort(bindHost, tryPort))
if fatalErr != nil {
return
}
default:
// the hostname probably couldn't be resolved, just bind to wildcard then
resolvAddr, fatalErr = net.ResolveTCPAddr("tcp", net.JoinHostPort("0.0.0.0", tryPort))
if fatalErr != nil {
return
}
}
return
}
return
}
// validDirective returns true if d is a valid
// directive; false otherwise.
func validDirective(d string) bool {
for _, dir := range directiveOrder {
if dir.name == d {
return true
}
}
return false
}
// DefaultInput returns the default Caddyfile input
// to use when it is otherwise empty or missing.
// It uses the default host and port (depends on
// host, e.g. localhost is 2015, otherwise https) and
// root.
func DefaultInput() CaddyfileInput {
port := Port
if letsencrypt.HostQualifies(Host) {
port = "https"
}
return CaddyfileInput{
Contents: []byte(fmt.Sprintf("%s:%s\nroot %s", Host, port, Root)),
}
}
// These defaults are configurable through the command line
var (
// Site root
Root = DefaultRoot
// Site host
Host = DefaultHost
// Site port
Port = DefaultPort
)
// bindingMapping maps a network address to configurations
// that will bind to it. The order of the configs is important.
type bindingMapping struct {
BindAddr *net.TCPAddr
Configs []server.Config
}
// bindingGroup maps network addresses to their configurations.
// Preserving the order of the groupings is important
// (related to graceful shutdown and restart)
// so this is a slice, not a literal map.
type bindingGroup []bindingMapping
-147
View File
@@ -1,147 +0,0 @@
package caddy
import (
"reflect"
"sync"
"testing"
"github.com/mholt/caddy/server"
)
func TestDefaultInput(t *testing.T) {
if actual, expected := string(DefaultInput().Body()), ":2015\nroot ."; actual != expected {
t.Errorf("Host=%s; Port=%s; Root=%s;\nEXPECTED: '%s'\n ACTUAL: '%s'", Host, Port, Root, expected, actual)
}
// next few tests simulate user providing -host flag
Host = "not-localhost.com"
if actual, expected := string(DefaultInput().Body()), "not-localhost.com:https\nroot ."; actual != expected {
t.Errorf("Host=%s; Port=%s; Root=%s;\nEXPECTED: '%s'\n ACTUAL: '%s'", Host, Port, Root, expected, actual)
}
Host = "[::1]"
if actual, expected := string(DefaultInput().Body()), "[::1]:2015\nroot ."; actual != expected {
t.Errorf("Host=%s; Port=%s; Root=%s;\nEXPECTED: '%s'\n ACTUAL: '%s'", Host, Port, Root, expected, actual)
}
Host = "127.0.1.1"
if actual, expected := string(DefaultInput().Body()), "127.0.1.1:2015\nroot ."; actual != expected {
t.Errorf("Host=%s; Port=%s; Root=%s;\nEXPECTED: '%s'\n ACTUAL: '%s'", Host, Port, Root, expected, actual)
}
}
func TestResolveAddr(t *testing.T) {
// NOTE: If tests fail due to comparing to string "127.0.0.1",
// it's possible that system env resolves with IPv6, or ::1.
// If that happens, maybe we should use actualAddr.IP.IsLoopback()
// for the assertion, rather than a direct string comparison.
// NOTE: Tests with {Host: "", Port: ""} and {Host: "localhost", Port: ""}
// will not behave the same cross-platform, so they have been omitted.
for i, test := range []struct {
config server.Config
shouldWarnErr bool
shouldFatalErr bool
expectedIP string
expectedPort int
}{
{server.Config{Host: "127.0.0.1", Port: "1234"}, false, false, "<nil>", 1234},
{server.Config{Host: "localhost", Port: "80"}, false, false, "<nil>", 80},
{server.Config{BindHost: "localhost", Port: "1234"}, false, false, "127.0.0.1", 1234},
{server.Config{BindHost: "127.0.0.1", Port: "1234"}, false, false, "127.0.0.1", 1234},
{server.Config{BindHost: "should-not-resolve", Port: "1234"}, true, false, "0.0.0.0", 1234},
{server.Config{BindHost: "localhost", Port: "http"}, false, false, "127.0.0.1", 80},
{server.Config{BindHost: "localhost", Port: "https"}, false, false, "127.0.0.1", 443},
{server.Config{BindHost: "", Port: "1234"}, false, false, "<nil>", 1234},
{server.Config{BindHost: "localhost", Port: "abcd"}, false, true, "", 0},
{server.Config{BindHost: "127.0.0.1", Host: "should-not-be-used", Port: "1234"}, false, false, "127.0.0.1", 1234},
{server.Config{BindHost: "localhost", Host: "should-not-be-used", Port: "1234"}, false, false, "127.0.0.1", 1234},
{server.Config{BindHost: "should-not-resolve", Host: "localhost", Port: "1234"}, true, false, "0.0.0.0", 1234},
} {
actualAddr, warnErr, fatalErr := resolveAddr(test.config)
if test.shouldFatalErr && fatalErr == nil {
t.Errorf("Test %d: Expected error, but there wasn't any", i)
}
if !test.shouldFatalErr && fatalErr != nil {
t.Errorf("Test %d: Expected no error, but there was one: %v", i, fatalErr)
}
if fatalErr != nil {
continue
}
if test.shouldWarnErr && warnErr == nil {
t.Errorf("Test %d: Expected warning, but there wasn't any", i)
}
if !test.shouldWarnErr && warnErr != nil {
t.Errorf("Test %d: Expected no warning, but there was one: %v", i, warnErr)
}
if actual, expected := actualAddr.IP.String(), test.expectedIP; actual != expected {
t.Errorf("Test %d: IP was %s but expected %s", i, actual, expected)
}
if actual, expected := actualAddr.Port, test.expectedPort; actual != expected {
t.Errorf("Test %d: Port was %d but expected %d", i, actual, expected)
}
}
}
func TestMakeOnces(t *testing.T) {
directives := []directive{
{"dummy", nil},
{"dummy2", nil},
}
directiveOrder = directives
onces := makeOnces()
if len(onces) != len(directives) {
t.Errorf("onces had len %d , expected %d", len(onces), len(directives))
}
expected := map[string]*sync.Once{
"dummy": new(sync.Once),
"dummy2": new(sync.Once),
}
if !reflect.DeepEqual(onces, expected) {
t.Errorf("onces was %v, expected %v", onces, expected)
}
}
func TestMakeStorages(t *testing.T) {
directives := []directive{
{"dummy", nil},
{"dummy2", nil},
}
directiveOrder = directives
storages := makeStorages()
if len(storages) != len(directives) {
t.Errorf("storages had len %d , expected %d", len(storages), len(directives))
}
expected := map[string]interface{}{
"dummy": nil,
"dummy2": nil,
}
if !reflect.DeepEqual(storages, expected) {
t.Errorf("storages was %v, expected %v", storages, expected)
}
}
func TestValidDirective(t *testing.T) {
directives := []directive{
{"dummy", nil},
{"dummy2", nil},
}
directiveOrder = directives
for i, test := range []struct {
directive string
valid bool
}{
{"dummy", true},
{"dummy2", true},
{"dummy3", false},
} {
if actual, expected := validDirective(test.directive), test.valid; actual != expected {
t.Errorf("Test %d: valid was %t, expected %t", i, actual, expected)
}
}
}
-80
View File
@@ -1,80 +0,0 @@
package caddy
import (
"github.com/mholt/caddy/caddy/parse"
"github.com/mholt/caddy/caddy/setup"
"github.com/mholt/caddy/middleware"
)
func init() {
// The parse package must know which directives
// are valid, but it must not import the setup
// or config package. To solve this problem, we
// fill up this map in our init function here.
// The parse package does not need to know the
// ordering of the directives.
for _, dir := range directiveOrder {
parse.ValidDirectives[dir.name] = struct{}{}
}
}
// Directives are registered in the order they should be
// executed. Middleware (directives that inject a handler)
// are executed in the order A-B-C-*-C-B-A, assuming
// they all call the Next handler in the chain.
//
// Ordering is VERY important. Every middleware will
// feel the effects of all other middleware below
// (after) them during a request, but they must not
// care what middleware above them are doing.
//
// For example, log needs to know the status code and
// exactly how many bytes were written to the client,
// which every other middleware can affect, so it gets
// registered first. The errors middleware does not
// care if gzip or log modifies its response, so it
// gets registered below them. Gzip, on the other hand,
// DOES care what errors does to the response since it
// must compress every output to the client, even error
// pages, so it must be registered before the errors
// middleware and any others that would write to the
// response.
var directiveOrder = []directive{
// Essential directives that initialize vital configuration settings
{"root", setup.Root},
{"tls", setup.TLS}, // letsencrypt is set up just after tls
{"bind", setup.BindHost},
// Other directives that don't create HTTP handlers
{"startup", setup.Startup},
{"shutdown", setup.Shutdown},
// Directives that inject handlers (middleware)
{"log", setup.Log},
{"gzip", setup.Gzip},
{"errors", setup.Errors},
{"header", setup.Headers},
{"rewrite", setup.Rewrite},
{"redir", setup.Redir},
{"ext", setup.Ext},
{"mime", setup.Mime},
{"basicauth", setup.BasicAuth},
{"internal", setup.Internal},
{"proxy", setup.Proxy},
{"fastcgi", setup.FastCGI},
{"websocket", setup.WebSocket},
{"markdown", setup.Markdown},
{"templates", setup.Templates},
{"browse", setup.Browse},
}
// directive ties together a directive name with its setup function.
type directive struct {
name string
setup SetupFunc
}
// SetupFunc takes a controller and may optionally return a middleware.
// If the resulting middleware is not nil, it will be chained into
// the HTTP handlers in the order specified in this package.
type SetupFunc func(c *setup.Controller) (middleware.Middleware, error)
-106
View File
@@ -1,106 +0,0 @@
package caddy
import (
"bytes"
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"runtime"
"strconv"
"strings"
"sync"
"github.com/mholt/caddy/caddy/letsencrypt"
)
func init() {
letsencrypt.OnChange = func() error { return Restart(nil) }
}
// isLocalhost returns true if host looks explicitly like a localhost address.
func isLocalhost(host string) bool {
return host == "localhost" || host == "::1" || strings.HasPrefix(host, "127.")
}
// checkFdlimit issues a warning if the OS max file descriptors is below a recommended minimum.
func checkFdlimit() {
const min = 4096
// Warn if ulimit is too low for production sites
if runtime.GOOS == "linux" || runtime.GOOS == "darwin" {
out, err := exec.Command("sh", "-c", "ulimit -n").Output() // use sh because ulimit isn't in Linux $PATH
if err == nil {
// Note that an error here need not be reported
lim, err := strconv.Atoi(string(bytes.TrimSpace(out)))
if err == nil && lim < min {
fmt.Printf("Warning: File descriptor limit %d is too low for production sites. At least %d is recommended. Set with \"ulimit -n %d\".\n", lim, min, min)
}
}
}
}
// signalSuccessToParent tells the parent our status using pipe at index 3.
// If this process is not a restart, this function does nothing.
// Calling this function once this process has successfully initialized
// is vital so that the parent process can unblock and kill itself.
// This function is idempotent; it executes at most once per process.
func signalSuccessToParent() {
signalParentOnce.Do(func() {
if IsRestart() {
ppipe := os.NewFile(3, "") // parent is reading from pipe at index 3
_, err := ppipe.Write([]byte("success")) // we must send some bytes to the parent
if err != nil {
log.Printf("[ERROR] Communicating successful init to parent: %v", err)
}
ppipe.Close()
}
})
}
// signalParentOnce is used to make sure that the parent is only
// signaled once; doing so more than once breaks whatever socket is
// at fd 4 (the reason for this is still unclear - to reproduce,
// call Stop() and Start() in succession at least once after a
// restart, then try loading first host of Caddyfile in the browser).
// Do not use this directly - call signalSuccessToParent instead.
var signalParentOnce sync.Once
// caddyfileGob maps bind address to index of the file descriptor
// in the Files array passed to the child process. It also contains
// the caddyfile contents. Used only during graceful restarts.
type caddyfileGob struct {
ListenerFds map[string]uintptr
Caddyfile Input
}
// IsRestart returns whether this process is, according
// to env variables, a fork as part of a graceful restart.
func IsRestart() bool {
return os.Getenv("CADDY_RESTART") == "true"
}
// writePidFile writes the process ID to the file at PidFile, if specified.
func writePidFile() error {
pid := []byte(strconv.Itoa(os.Getpid()) + "\n")
return ioutil.WriteFile(PidFile, pid, 0644)
}
// CaddyfileInput represents a Caddyfile as input
// and is simply a convenient way to implement
// the Input interface.
type CaddyfileInput struct {
Filepath string
Contents []byte
RealFile bool
}
// Body returns c.Contents.
func (c CaddyfileInput) Body() []byte { return c.Contents }
// Path returns c.Filepath.
func (c CaddyfileInput) Path() string { return c.Filepath }
// IsFile returns true if the original input was a real file on the file system.
func (c CaddyfileInput) IsFile() bool { return c.RealFile }
-30
View File
@@ -1,30 +0,0 @@
package letsencrypt
import (
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"io/ioutil"
"os"
)
// loadRSAPrivateKey loads a PEM-encoded RSA private key from file.
func loadRSAPrivateKey(file string) (*rsa.PrivateKey, error) {
keyBytes, err := ioutil.ReadFile(file)
if err != nil {
return nil, err
}
keyBlock, _ := pem.Decode(keyBytes)
return x509.ParsePKCS1PrivateKey(keyBlock.Bytes)
}
// saveRSAPrivateKey saves a PEM-encoded RSA private key to file.
func saveRSAPrivateKey(key *rsa.PrivateKey, file string) error {
pemKey := pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}
keyOut, err := os.Create(file)
if err != nil {
return err
}
defer keyOut.Close()
return pem.Encode(keyOut, &pemKey)
}
-51
View File
@@ -1,51 +0,0 @@
package letsencrypt
import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"os"
"testing"
)
func init() {
rsaKeySizeToUse = 128 // make tests faster; small key size OK for testing
}
func TestSaveAndLoadRSAPrivateKey(t *testing.T) {
keyFile := "test.key"
defer os.Remove(keyFile)
privateKey, err := rsa.GenerateKey(rand.Reader, rsaKeySizeToUse)
if err != nil {
t.Fatal(err)
}
// test save
err = saveRSAPrivateKey(privateKey, keyFile)
if err != nil {
t.Fatal("error saving private key:", err)
}
// test load
loadedKey, err := loadRSAPrivateKey(keyFile)
if err != nil {
t.Error("error loading private key:", err)
}
// very loaded key is correct
if !rsaPrivateKeysSame(privateKey, loadedKey) {
t.Error("Expected key bytes to be the same, but they weren't")
}
}
// rsaPrivateKeyBytes returns the bytes of DER-encoded key.
func rsaPrivateKeyBytes(key *rsa.PrivateKey) []byte {
return x509.MarshalPKCS1PrivateKey(key)
}
// rsaPrivateKeysSame compares the bytes of a and b and returns true if they are the same.
func rsaPrivateKeysSame(a, b *rsa.PrivateKey) bool {
return bytes.Equal(rsaPrivateKeyBytes(a), rsaPrivateKeyBytes(b))
}
-55
View File
@@ -1,55 +0,0 @@
package letsencrypt
import (
"crypto/tls"
"net"
"net/http"
"net/http/httputil"
"net/url"
"strings"
"github.com/mholt/caddy/middleware"
)
const challengeBasePath = "/.well-known/acme-challenge"
// Handler is a Caddy middleware that can proxy ACME challenge
// requests to the real ACME client endpoint. This is necessary
// to renew certificates while the server is running.
type Handler struct {
Next middleware.Handler
//ChallengeActive int32 // (TODO) use sync/atomic to set/get this flag safely and efficiently
}
// ServeHTTP is basically a no-op unless an ACME challenge is active on this host
// and the request path matches the expected path exactly.
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
// Proxy challenge requests to ACME client
// TODO: Only do this if a challenge is active?
if strings.HasPrefix(r.URL.Path, challengeBasePath) {
scheme := "http"
if r.TLS != nil {
scheme = "https"
}
hostname, _, err := net.SplitHostPort(r.URL.Host)
if err != nil {
hostname = r.URL.Host
}
upstream, err := url.Parse(scheme + "://" + hostname + ":" + alternatePort)
if err != nil {
return http.StatusInternalServerError, err
}
proxy := httputil.NewSingleHostReverseProxy(upstream)
proxy.Transport = &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // client would use self-signed cert
}
proxy.ServeHTTP(w, r)
return 0, nil
}
return h.Next.ServeHTTP(w, r)
}
-530
View File
@@ -1,530 +0,0 @@
// Package letsencrypt integrates Let's Encrypt functionality into Caddy
// with first-class support for creating and renewing certificates
// automatically. It is designed to configure sites for HTTPS by default.
package letsencrypt
import (
"encoding/json"
"errors"
"io/ioutil"
"net/http"
"os"
"strings"
"time"
"github.com/mholt/caddy/caddy/setup"
"github.com/mholt/caddy/middleware"
"github.com/mholt/caddy/middleware/redirect"
"github.com/mholt/caddy/server"
"github.com/xenolf/lego/acme"
)
// Activate sets up TLS for each server config in configs
// as needed. It only skips the config if the cert and key
// are already provided, if plaintext http is explicitly
// specified as the port, TLS is explicitly disabled, or
// the host looks like a loopback or wildcard address.
//
// This function may prompt the user to provide an email
// address if none is available through other means. It
// prefers the email address specified in the config, but
// if that is not available it will check the command line
// argument. If absent, it will use the most recent email
// address from last time. If there isn't one, the user
// will be prompted and shown SA link.
//
// Also note that calling this function activates asset
// management automatically, which keeps certificates
// renewed and OCSP stapling updated. This has the effect
// of causing restarts when assets are updated.
//
// Activate returns the updated list of configs, since
// some may have been appended, for example, to redirect
// plaintext HTTP requests to their HTTPS counterpart.
// This function only appends; it does not prepend or splice.
func Activate(configs []server.Config) ([]server.Config, error) {
// just in case previous caller forgot...
Deactivate()
// reset cached ocsp statuses from any previous activations
ocspStatus = make(map[*[]byte]int)
// Identify and configure any eligible hosts for which
// we already have certs and keys in storage from last time.
configLen := len(configs) // avoid infinite loop since this loop appends plaintext to the slice
for i := 0; i < configLen; i++ {
if existingCertAndKey(configs[i].Host) && configQualifies(configs, i) {
configs = autoConfigure(configs, i)
}
}
// Group configs by email address; only configs that are eligible
// for TLS management are included. We group by email so that we
// can request certificates in batches with the same client.
// Note: The return value is a map, and iteration over a map is
// not ordered. I don't think it will be a problem, but if an
// ordering problem arises, look at this carefully.
groupedConfigs, err := groupConfigsByEmail(configs)
if err != nil {
return configs, err
}
// obtain certificates for configs that need one, and reconfigure each
// config to use the certificates
for leEmail, cfgIndexes := range groupedConfigs {
// make client to service this email address with CA server
client, err := newClient(leEmail)
if err != nil {
return configs, errors.New("error creating client: " + err.Error())
}
// little bit of housekeeping; gather the hostnames into a slice
hosts := make([]string, len(cfgIndexes))
for i, idx := range cfgIndexes {
hosts[i] = configs[idx].Host
}
// client is ready, so let's get free, trusted SSL certificates!
Obtain:
certificates, failures := client.ObtainCertificates(hosts, true)
if len(failures) > 0 {
// Build an error string to return, using all the failures in the list.
var errMsg string
// If an error is because of updated SA, only prompt user for agreement once
var promptedForAgreement bool
for domain, obtainErr := range failures {
// If the failure was simply because the terms have changed, re-prompt and re-try
if tosErr, ok := obtainErr.(acme.TOSError); ok {
if !Agreed && !promptedForAgreement {
Agreed = promptUserAgreement(tosErr.Detail, true) // TODO: Use latest URL
promptedForAgreement = true
}
if Agreed {
err := client.AgreeToTOS()
if err != nil {
return configs, errors.New("error agreeing to updated terms: " + err.Error())
}
goto Obtain
}
}
// If user did not agree or it was any other kind of error, just append to the list of errors
errMsg += "[" + domain + "] failed to get certificate: " + obtainErr.Error() + "\n"
}
return configs, errors.New(errMsg)
}
// ... that's it. save the certs, keys, and metadata files to disk
err = saveCertsAndKeys(certificates)
if err != nil {
return configs, errors.New("error saving assets: " + err.Error())
}
// it all comes down to this: turning on TLS with all the new certs
for _, idx := range cfgIndexes {
configs = autoConfigure(configs, idx)
}
}
// renew all certificates that need renewal
renewCertificates(configs, false)
// keep certificates renewed and OCSP stapling updated
go maintainAssets(configs, stopChan)
return configs, nil
}
// Deactivate cleans up long-term, in-memory resources
// allocated by calling Activate(). Essentially, it stops
// the asset maintainer from running, meaning that certificates
// will not be renewed, OCSP staples will not be updated, etc.
func Deactivate() (err error) {
defer func() {
if rec := recover(); rec != nil {
err = errors.New("already deactivated")
}
}()
close(stopChan)
stopChan = make(chan struct{})
return
}
// configQualifies returns true if the config at cfgIndex (within allConfigs)
// qualifes for automatic LE activation. It does NOT check to see if a cert
// and key already exist for the config.
func configQualifies(allConfigs []server.Config, cfgIndex int) bool {
cfg := allConfigs[cfgIndex]
return cfg.TLS.Certificate == "" && // user could provide their own cert and key
cfg.TLS.Key == "" &&
// user can force-disable automatic HTTPS for this host
cfg.Port != "http" &&
cfg.TLS.LetsEncryptEmail != "off" &&
// obviously we get can't certs for loopback or internal hosts
HostQualifies(cfg.Host) &&
// make sure another HTTPS version of this config doesn't exist in the list already
!otherHostHasScheme(allConfigs, cfgIndex, "https")
}
// HostQualifies returns true if the hostname alone
// appears eligible for automatic HTTPS. For example,
// localhost, empty hostname, and wildcard hosts are
// not eligible because we cannot obtain certificates
// for those names.
func HostQualifies(hostname string) bool {
return hostname != "localhost" &&
strings.TrimSpace(hostname) != "" &&
hostname != "0.0.0.0" &&
hostname != "[::]" && // before parsing
hostname != "::" && // after parsing
hostname != "[::1]" && // before parsing
hostname != "::1" && // after parsing
!strings.HasPrefix(hostname, "127.") // to use boulder on your own machine, add fake domain to hosts file
// not excluding 10.* and 192.168.* hosts for possibility of running internal Boulder instance
}
// groupConfigsByEmail groups configs by user email address. The returned map is
// a map of email address to the configs that are serviced under that account.
// If an email address is not available for an eligible config, the user will be
// prompted to provide one. The returned map contains pointers to the original
// server config values.
func groupConfigsByEmail(configs []server.Config) (map[string][]int, error) {
initMap := make(map[string][]int)
for i := 0; i < len(configs); i++ {
// filter out configs that we already have certs for and
// that we won't be obtaining certs for - this way we won't
// bother the user for an email address unnecessarily and
// we don't obtain new certs for a host we already have certs for.
if existingCertAndKey(configs[i].Host) || !configQualifies(configs, i) {
continue
}
leEmail := getEmail(configs[i])
initMap[leEmail] = append(initMap[leEmail], i)
}
return initMap, nil
}
// existingCertAndKey returns true if the host has a certificate
// and private key in storage already, false otherwise.
func existingCertAndKey(host string) bool {
_, err := os.Stat(storage.SiteCertFile(host))
if err != nil {
return false
}
_, err = os.Stat(storage.SiteKeyFile(host))
if err != nil {
return false
}
return true
}
// newClient creates a new ACME client to facilitate communication
// with the Let's Encrypt CA server on behalf of the user specified
// by leEmail. As part of this process, a user will be loaded from
// disk (if already exists) or created new and registered via ACME
// and saved to the file system for next time.
func newClient(leEmail string) (*acme.Client, error) {
return newClientPort(leEmail, "")
}
// newClientPort does the same thing as newClient, except it creates a
// new client with a custom port used for ACME transactions instead of
// the default port. This is important if the default port is already in
// use or is not exposed to the public, etc.
func newClientPort(leEmail, port string) (*acme.Client, error) {
// Look up or create the LE user account
leUser, err := getUser(leEmail)
if err != nil {
return nil, err
}
// The client facilitates our communication with the CA server.
client, err := acme.NewClient(CAUrl, &leUser, rsaKeySizeToUse, port)
if err != nil {
return nil, err
}
// If not registered, the user must register an account with the CA
// and agree to terms
if leUser.Registration == nil {
reg, err := client.Register()
if err != nil {
return nil, errors.New("registration error: " + err.Error())
}
leUser.Registration = reg
if !Agreed && reg.TosURL == "" {
Agreed = promptUserAgreement(saURL, false) // TODO - latest URL
}
if !Agreed && reg.TosURL == "" {
return nil, errors.New("user must agree to terms")
}
err = client.AgreeToTOS()
if err != nil {
saveUser(leUser) // TODO: Might as well try, right? Error check?
return nil, errors.New("error agreeing to terms: " + err.Error())
}
// save user to the file system
err = saveUser(leUser)
if err != nil {
return nil, errors.New("could not save user: " + err.Error())
}
}
return client, nil
}
// obtainCertificates obtains certificates from the CA server for
// the configurations in serverConfigs using client.
func obtainCertificates(client *acme.Client, serverConfigs []server.Config) ([]acme.CertificateResource, map[string]error) {
var hosts []string
for _, cfg := range serverConfigs {
hosts = append(hosts, cfg.Host)
}
return client.ObtainCertificates(hosts, true)
}
// saveCertificates saves each certificate resource to disk. This
// includes the certificate file itself, the private key, and the
// metadata file.
func saveCertsAndKeys(certificates []acme.CertificateResource) error {
for _, cert := range certificates {
err := os.MkdirAll(storage.Site(cert.Domain), 0700)
if err != nil {
return err
}
// Save cert
err = ioutil.WriteFile(storage.SiteCertFile(cert.Domain), cert.Certificate, 0600)
if err != nil {
return err
}
// Save private key
err = ioutil.WriteFile(storage.SiteKeyFile(cert.Domain), cert.PrivateKey, 0600)
if err != nil {
return err
}
// Save cert metadata
jsonBytes, err := json.MarshalIndent(&cert, "", "\t")
if err != nil {
return err
}
err = ioutil.WriteFile(storage.SiteMetaFile(cert.Domain), jsonBytes, 0600)
if err != nil {
return err
}
}
return nil
}
// autoConfigure enables TLS on allConfigs[cfgIndex] and appends, if necessary,
// a new config to allConfigs that redirects plaintext HTTP to its new HTTPS
// counterpart. It expects the certificate and key to already be in storage. It
// returns the new list of allConfigs, since it may append a new config. This
// function assumes that allConfigs[cfgIndex] is already set up for HTTPS.
func autoConfigure(allConfigs []server.Config, cfgIndex int) []server.Config {
cfg := &allConfigs[cfgIndex]
bundleBytes, err := ioutil.ReadFile(storage.SiteCertFile(cfg.Host))
// TODO: Handle these errors better
if err == nil {
ocsp, status, err := acme.GetOCSPForCert(bundleBytes)
ocspStatus[&bundleBytes] = status
if err == nil && status == acme.OCSPGood {
cfg.TLS.OCSPStaple = ocsp
}
}
cfg.TLS.Certificate = storage.SiteCertFile(cfg.Host)
cfg.TLS.Key = storage.SiteKeyFile(cfg.Host)
cfg.TLS.Enabled = true
// Ensure all defaults are set for the TLS config
setup.SetDefaultTLSParams(cfg)
if cfg.Port == "" {
cfg.Port = "https"
}
// Set up http->https redirect as long as there isn't already a http counterpart
// in the configs and this isn't, for some reason, already on port 80.
// Also, the port 80 variant of this config is necessary for proxying challenge requests.
if !otherHostHasScheme(allConfigs, cfgIndex, "http") &&
cfg.Port != "80" && cfg.Port != "http" { // (would not be http port with current program flow, but just in case)
allConfigs = append(allConfigs, redirPlaintextHost(*cfg))
}
// To support renewals, we need handlers at ports 80 and 443,
// depending on the challenge type that is used to complete renewal.
for i, c := range allConfigs {
if c.Address() == cfg.Host+":80" ||
c.Address() == cfg.Host+":443" ||
c.Address() == cfg.Host+":http" ||
c.Address() == cfg.Host+":https" {
// Each virtualhost must have their own handlers, or the chaining gets messed up when middlewares are compiled!
handler := new(Handler)
mid := func(next middleware.Handler) middleware.Handler {
handler.Next = next
return handler
}
// TODO: Currently, acmeHandlers are not referenced, but we need to add a way to toggle
// their proxy functionality -- or maybe not. Gotta figure this out for sure.
acmeHandlers[c.Address()] = handler
allConfigs[i].Middleware["/"] = append(allConfigs[i].Middleware["/"], mid)
}
}
return allConfigs
}
// otherHostHasScheme tells you whether there is ANOTHER config in allConfigs
// for the same host but with the port equal to scheme as allConfigs[cfgIndex].
// This function considers "443" and "https" to be the same scheme, as well as
// "http" and "80". It does not tell you whether there is ANY config with scheme,
// only if there's a different one with it.
func otherHostHasScheme(allConfigs []server.Config, cfgIndex int, scheme string) bool {
if scheme == "80" {
scheme = "http"
} else if scheme == "443" {
scheme = "https"
}
for i, otherCfg := range allConfigs {
if i == cfgIndex {
continue // has to be a config OTHER than the one we're comparing against
}
if otherCfg.Host == allConfigs[cfgIndex].Host {
if (otherCfg.Port == scheme) ||
(scheme == "https" && otherCfg.Port == "443") ||
(scheme == "http" && otherCfg.Port == "80") {
return true
}
}
}
return false
}
// 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 the "http" port (port 80).
func redirPlaintextHost(cfg server.Config) server.Config {
toURL := "https://" + cfg.Host
if cfg.Port != "https" && cfg.Port != "http" {
toURL += ":" + cfg.Port
}
redirMidware := func(next middleware.Handler) middleware.Handler {
return redirect.Redirect{Next: next, Rules: []redirect.Rule{
{
FromScheme: "http",
FromPath: "/",
To: toURL + "{uri}",
Code: http.StatusMovedPermanently,
},
}}
}
return server.Config{
Host: cfg.Host,
Port: "http",
Middleware: map[string][]middleware.Middleware{
"/": []middleware.Middleware{redirMidware},
},
}
}
// Revoke revokes the certificate for host via ACME protocol.
func Revoke(host string) error {
if !existingCertAndKey(host) {
return errors.New("no certificate and key for " + host)
}
email := getEmail(server.Config{Host: host})
if email == "" {
return errors.New("email is required to revoke")
}
client, err := newClient(email)
if err != nil {
return err
}
certFile := storage.SiteCertFile(host)
certBytes, err := ioutil.ReadFile(certFile)
if err != nil {
return err
}
err = client.RevokeCertificate(certBytes)
if err != nil {
return err
}
err = os.Remove(certFile)
if err != nil {
return errors.New("certificate revoked, but unable to delete certificate file: " + err.Error())
}
return nil
}
var (
// DefaultEmail represents the Let's Encrypt account email to use if none provided
DefaultEmail string
// Agreed indicates whether user has agreed to the Let's Encrypt SA
Agreed bool
// CAUrl represents the base URL to the CA's ACME endpoint
CAUrl string
)
// Some essential values related to the Let's Encrypt process
const (
// alternatePort is the port on which the acme client will open a
// listener and solve the CA's challenges. If this alternate port
// is used instead of the default port (80 or 443), then the
// default port for the challenge must be forwarded to this one.
alternatePort = "5033"
// How often to check certificates for renewal.
renewInterval = 24 * time.Hour
// How often to update OCSP stapling.
ocspInterval = 1 * time.Hour
)
// KeySize represents the length of a key in bits.
type KeySize int
// Key sizes are used to determine the strength of a key.
const (
ECC_224 KeySize = 224
ECC_256 = 256
RSA_2048 = 2048
RSA_4096 = 4096
)
// rsaKeySizeToUse is the size to use for new RSA keys.
// This shouldn't need to change except for in tests;
// the size can be drastically reduced for speed.
var rsaKeySizeToUse = RSA_2048
// stopChan is used to signal the maintenance goroutine
// to terminate.
var stopChan chan struct{}
// ocspStatus maps certificate bundle to OCSP status at start.
// It is used during regular OCSP checks to see if the OCSP
// status has changed.
var ocspStatus = make(map[*[]byte]int)
-79
View File
@@ -1,79 +0,0 @@
package letsencrypt
import (
"net/http"
"testing"
"github.com/mholt/caddy/middleware/redirect"
"github.com/mholt/caddy/server"
)
func TestHostQualifies(t *testing.T) {
for i, test := range []struct {
host string
expect bool
}{
{"localhost", false},
{"127.0.0.1", false},
{"127.0.1.5", false},
{"::1", false},
{"[::1]", false},
{"[::]", false},
{"::", false},
{"", false},
{" ", false},
{"0.0.0.0", false},
{"192.168.1.3", true},
{"10.0.2.1", true},
{"foobar.com", true},
} {
if HostQualifies(test.host) && !test.expect {
t.Errorf("Test %d: Expected '%s' to NOT qualify, but it did", i, test.host)
}
if !HostQualifies(test.host) && test.expect {
t.Errorf("Test %d: Expected '%s' to qualify, but it did NOT", i, test.host)
}
}
}
func TestRedirPlaintextHost(t *testing.T) {
cfg := redirPlaintextHost(server.Config{
Host: "example.com",
Port: "http",
})
// Check host and port
if actual, expected := cfg.Host, "example.com"; actual != expected {
t.Errorf("Expected redir config to have host %s but got %s", expected, actual)
}
if actual, expected := cfg.Port, "http"; actual != expected {
t.Errorf("Expected redir config to have port '%s' but got '%s'", expected, actual)
}
// Make sure redirect handler is set up properly
if cfg.Middleware == nil || len(cfg.Middleware["/"]) != 1 {
t.Fatalf("Redir config middleware not set up properly; got: %#v", cfg.Middleware)
}
handler, ok := cfg.Middleware["/"][0](nil).(redirect.Redirect)
if !ok {
t.Fatalf("Expected a redirect.Redirect middleware, but got: %#v", handler)
}
if len(handler.Rules) != 1 {
t.Fatalf("Expected one redirect rule, got: %#v", handler.Rules)
}
// Check redirect rule for correctness
if actual, expected := handler.Rules[0].FromScheme, "http"; actual != expected {
t.Errorf("Expected redirect rule to be from scheme '%s' but is actually from '%s'", expected, actual)
}
if actual, expected := handler.Rules[0].FromPath, "/"; actual != expected {
t.Errorf("Expected redirect rule to be for path '%s' but is actually for '%s'", expected, actual)
}
if actual, expected := handler.Rules[0].To, "https://example.com{uri}"; actual != expected {
t.Errorf("Expected redirect rule to be to URL '%s' but is actually to '%s'", expected, actual)
}
if actual, expected := handler.Rules[0].Code, http.StatusMovedPermanently; actual != expected {
t.Errorf("Expected redirect rule to have code %d but was %d", expected, actual)
}
}
-168
View File
@@ -1,168 +0,0 @@
package letsencrypt
import (
"encoding/json"
"io/ioutil"
"log"
"time"
"github.com/mholt/caddy/server"
"github.com/xenolf/lego/acme"
)
// OnChange is a callback function that will be used to restart
// the application or the part of the application that uses
// the certificates maintained by this package. When at least
// one certificate is renewed or an OCSP status changes, this
// function will be called.
var OnChange func() error
// maintainAssets is a permanently-blocking function
// that loops indefinitely and, on a regular schedule, checks
// certificates for expiration and initiates a renewal of certs
// that are expiring soon. It also updates OCSP stapling and
// performs other maintenance of assets.
//
// You must pass in the server configs to maintain and the channel
// which you'll close when maintenance should stop, to allow this
// goroutine to clean up after itself and unblock.
func maintainAssets(configs []server.Config, stopChan chan struct{}) {
renewalTicker := time.NewTicker(renewInterval)
ocspTicker := time.NewTicker(ocspInterval)
for {
select {
case <-renewalTicker.C:
n, errs := renewCertificates(configs, true)
if len(errs) > 0 {
for _, err := range errs {
log.Printf("[ERROR] Certificate renewal: %v", err)
}
}
// even if there was an error, some renewals may have succeeded
if n > 0 && OnChange != nil {
err := OnChange()
if err != nil {
log.Printf("[ERROR] OnChange after cert renewal: %v", err)
}
}
case <-ocspTicker.C:
for bundle, oldStatus := range ocspStatus {
_, newStatus, err := acme.GetOCSPForCert(*bundle)
if err == nil && newStatus != oldStatus && OnChange != nil {
log.Printf("[INFO] OCSP status changed from %v to %v", oldStatus, newStatus)
err := OnChange()
if err != nil {
log.Printf("[ERROR] OnChange after OCSP update: %v", err)
}
break
}
}
case <-stopChan:
renewalTicker.Stop()
ocspTicker.Stop()
return
}
}
}
// renewCertificates loops through all configured site and
// looks for certificates to renew. Nothing is mutated
// through this function; all changes happen directly on disk.
// It returns the number of certificates renewed and any errors
// that occurred. It only performs a renewal if necessary.
// If useCustomPort is true, a custom port will be used, and
// whatever is listening at 443 better proxy ACME requests to it.
// Otherwise, the acme package will create its own listener on 443.
func renewCertificates(configs []server.Config, useCustomPort bool) (int, []error) {
log.Printf("[INFO] Checking certificates for %d hosts", len(configs))
var errs []error
var n int
for _, cfg := range configs {
// Host must be TLS-enabled and have existing assets managed by LE
if !cfg.TLS.Enabled || !existingCertAndKey(cfg.Host) {
continue
}
// Read the certificate and get the NotAfter time.
certBytes, err := ioutil.ReadFile(storage.SiteCertFile(cfg.Host))
if err != nil {
errs = append(errs, err)
continue // still have to check other certificates
}
expTime, err := acme.GetPEMCertExpiration(certBytes)
if err != nil {
errs = append(errs, err)
continue
}
// The time returned from the certificate is always in UTC.
// So calculate the time left with local time as UTC.
// Directly convert it to days for the following checks.
daysLeft := int(expTime.Sub(time.Now().UTC()).Hours() / 24)
// Renew with two weeks or less remaining.
if daysLeft <= 14 {
log.Printf("[INFO] Certificate for %s has %d days remaining; attempting renewal", cfg.Host, daysLeft)
var client *acme.Client
if useCustomPort {
client, err = newClientPort("", alternatePort) // email not used for renewal
} else {
client, err = newClient("")
}
if err != nil {
errs = append(errs, err)
continue
}
// Read and set up cert meta, required for renewal
metaBytes, err := ioutil.ReadFile(storage.SiteMetaFile(cfg.Host))
if err != nil {
errs = append(errs, err)
continue
}
privBytes, err := ioutil.ReadFile(storage.SiteKeyFile(cfg.Host))
if err != nil {
errs = append(errs, err)
continue
}
var certMeta acme.CertificateResource
err = json.Unmarshal(metaBytes, &certMeta)
certMeta.Certificate = certBytes
certMeta.PrivateKey = privBytes
// Renew certificate
Renew:
newCertMeta, err := client.RenewCertificate(certMeta, true, true)
if err != nil {
if _, ok := err.(acme.TOSError); ok {
err := client.AgreeToTOS()
if err != nil {
errs = append(errs, err)
}
goto Renew
}
time.Sleep(10 * time.Second)
newCertMeta, err = client.RenewCertificate(certMeta, true, true)
if err != nil {
errs = append(errs, err)
continue
}
}
saveCertsAndKeys([]acme.CertificateResource{newCertMeta})
n++
} else if daysLeft <= 30 {
// Warn on 30 days remaining. TODO: Just do this once...
log.Printf("[WARNING] Certificate for %s has %d days remaining; will automatically renew when 14 days remain\n", cfg.Host, daysLeft)
}
}
return n, errs
}
// acmeHandlers is a map of host to ACME handler. These
// are used to proxy ACME requests to the ACME client.
var acmeHandlers = make(map[string]*Handler)
-94
View File
@@ -1,94 +0,0 @@
package letsencrypt
import (
"path/filepath"
"strings"
"github.com/mholt/caddy/caddy/assets"
)
// storage is used to get file paths in a consistent,
// cross-platform way for persisting Let's Encrypt assets
// on the file system.
var storage = Storage(filepath.Join(assets.Path(), "letsencrypt"))
// Storage is a root directory and facilitates
// forming file paths derived from it.
type Storage string
// Sites gets the directory that stores site certificate and keys.
func (s Storage) Sites() string {
return filepath.Join(string(s), "sites")
}
// Site returns the path to the folder containing assets for domain.
func (s Storage) Site(domain string) string {
return filepath.Join(s.Sites(), domain)
}
// SiteCertFile returns the path to the certificate file for domain.
func (s Storage) SiteCertFile(domain string) string {
return filepath.Join(s.Site(domain), domain+".crt")
}
// SiteKeyFile returns the path to domain's private key file.
func (s Storage) SiteKeyFile(domain string) string {
return filepath.Join(s.Site(domain), domain+".key")
}
// SiteMetaFile returns the path to the domain's asset metadata file.
func (s Storage) SiteMetaFile(domain string) string {
return filepath.Join(s.Site(domain), domain+".json")
}
// Users gets the directory that stores account folders.
func (s Storage) Users() string {
return filepath.Join(string(s), "users")
}
// User gets the account folder for the user with email.
func (s Storage) User(email string) string {
if email == "" {
email = emptyEmail
}
return filepath.Join(s.Users(), email)
}
// UserRegFile gets the path to the registration file for
// the user with the given email address.
func (s Storage) UserRegFile(email string) string {
if email == "" {
email = emptyEmail
}
fileName := emailUsername(email)
if fileName == "" {
fileName = "registration"
}
return filepath.Join(s.User(email), fileName+".json")
}
// UserKeyFile gets the path to the private key file for
// the user with the given email address.
func (s Storage) UserKeyFile(email string) string {
if email == "" {
email = emptyEmail
}
fileName := emailUsername(email)
if fileName == "" {
fileName = "private"
}
return filepath.Join(s.User(email), fileName+".key")
}
// emailUsername returns the username portion of an
// email address (part before '@') or the original
// input if it can't find the "@" symbol.
func emailUsername(email string) string {
at := strings.Index(email, "@")
if at == -1 {
return email
} else if at == 0 {
return email[1:]
}
return email[:at]
}
-88
View File
@@ -1,88 +0,0 @@
package letsencrypt
import (
"path/filepath"
"testing"
)
func TestStorage(t *testing.T) {
storage = Storage("./letsencrypt")
if expected, actual := filepath.Join("letsencrypt", "sites"), storage.Sites(); actual != expected {
t.Errorf("Expected Sites() to return '%s' but got '%s'", expected, actual)
}
if expected, actual := filepath.Join("letsencrypt", "sites", "test.com"), storage.Site("test.com"); actual != expected {
t.Errorf("Expected Site() to return '%s' but got '%s'", expected, actual)
}
if expected, actual := filepath.Join("letsencrypt", "sites", "test.com", "test.com.crt"), storage.SiteCertFile("test.com"); actual != expected {
t.Errorf("Expected SiteCertFile() to return '%s' but got '%s'", expected, actual)
}
if expected, actual := filepath.Join("letsencrypt", "sites", "test.com", "test.com.key"), storage.SiteKeyFile("test.com"); actual != expected {
t.Errorf("Expected SiteKeyFile() to return '%s' but got '%s'", expected, actual)
}
if expected, actual := filepath.Join("letsencrypt", "sites", "test.com", "test.com.json"), storage.SiteMetaFile("test.com"); actual != expected {
t.Errorf("Expected SiteMetaFile() to return '%s' but got '%s'", expected, actual)
}
if expected, actual := filepath.Join("letsencrypt", "users"), storage.Users(); actual != expected {
t.Errorf("Expected Users() to return '%s' but got '%s'", expected, actual)
}
if expected, actual := filepath.Join("letsencrypt", "users", "me@example.com"), storage.User("me@example.com"); actual != expected {
t.Errorf("Expected User() to return '%s' but got '%s'", expected, actual)
}
if expected, actual := filepath.Join("letsencrypt", "users", "me@example.com", "me.json"), storage.UserRegFile("me@example.com"); actual != expected {
t.Errorf("Expected UserRegFile() to return '%s' but got '%s'", expected, actual)
}
if expected, actual := filepath.Join("letsencrypt", "users", "me@example.com", "me.key"), storage.UserKeyFile("me@example.com"); actual != expected {
t.Errorf("Expected UserKeyFile() to return '%s' but got '%s'", expected, actual)
}
// Test with empty emails
if expected, actual := filepath.Join("letsencrypt", "users", emptyEmail), storage.User(emptyEmail); actual != expected {
t.Errorf("Expected User(\"\") to return '%s' but got '%s'", expected, actual)
}
if expected, actual := filepath.Join("letsencrypt", "users", emptyEmail, emptyEmail+".json"), storage.UserRegFile(""); actual != expected {
t.Errorf("Expected UserRegFile(\"\") to return '%s' but got '%s'", expected, actual)
}
if expected, actual := filepath.Join("letsencrypt", "users", emptyEmail, emptyEmail+".key"), storage.UserKeyFile(""); actual != expected {
t.Errorf("Expected UserKeyFile(\"\") to return '%s' but got '%s'", expected, actual)
}
}
func TestEmailUsername(t *testing.T) {
for i, test := range []struct {
input, expect string
}{
{
input: "username@example.com",
expect: "username",
},
{
input: "plus+addressing@example.com",
expect: "plus+addressing",
},
{
input: "me+plus-addressing@example.com",
expect: "me+plus-addressing",
},
{
input: "not-an-email",
expect: "not-an-email",
},
{
input: "@foobar.com",
expect: "foobar.com",
},
{
input: emptyEmail,
expect: emptyEmail,
},
{
input: "",
expect: "",
},
} {
if actual := emailUsername(test.input); actual != test.expect {
t.Errorf("Test %d: Expected username to be '%s' but was '%s'", i, test.expect, actual)
}
}
}
-196
View File
@@ -1,196 +0,0 @@
package letsencrypt
import (
"bufio"
"crypto/rand"
"crypto/rsa"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"strings"
"github.com/mholt/caddy/server"
"github.com/xenolf/lego/acme"
)
// User represents a Let's Encrypt user account.
type User struct {
Email string
Registration *acme.RegistrationResource
key *rsa.PrivateKey
}
// GetEmail gets u's email.
func (u User) GetEmail() string {
return u.Email
}
// GetRegistration gets u's registration resource.
func (u User) GetRegistration() *acme.RegistrationResource {
return u.Registration
}
// GetPrivateKey gets u's private key.
func (u User) GetPrivateKey() *rsa.PrivateKey {
return u.key
}
// getUser loads the user with the given email from disk.
// If the user does not exist, it will create a new one,
// but it does NOT save new users to the disk or register
// them via ACME.
func getUser(email string) (User, error) {
var user User
// open user file
regFile, err := os.Open(storage.UserRegFile(email))
if err != nil {
if os.IsNotExist(err) {
// create a new user
return newUser(email)
}
return user, err
}
defer regFile.Close()
// load user information
err = json.NewDecoder(regFile).Decode(&user)
if err != nil {
return user, err
}
// load their private key
user.key, err = loadRSAPrivateKey(storage.UserKeyFile(email))
if err != nil {
return user, err
}
return user, nil
}
// saveUser persists a user's key and account registration
// to the file system. It does NOT register the user via ACME.
func saveUser(user User) error {
// make user account folder
err := os.MkdirAll(storage.User(user.Email), 0700)
if err != nil {
return err
}
// save private key file
err = saveRSAPrivateKey(user.key, storage.UserKeyFile(user.Email))
if err != nil {
return err
}
// save registration file
jsonBytes, err := json.MarshalIndent(&user, "", "\t")
if err != nil {
return err
}
return ioutil.WriteFile(storage.UserRegFile(user.Email), jsonBytes, 0600)
}
// newUser creates a new User for the given email address
// with a new private key. This function does NOT save the
// user to disk or register it via ACME. If you want to use
// a user account that might already exist, call getUser
// instead.
func newUser(email string) (User, error) {
user := User{Email: email}
privateKey, err := rsa.GenerateKey(rand.Reader, rsaKeySizeToUse)
if err != nil {
return user, errors.New("error generating private key: " + err.Error())
}
user.key = privateKey
return user, nil
}
// getEmail does everything it can to obtain an email
// address from the user to use for TLS for cfg. If it
// cannot get an email address, it returns empty string.
// (It will warn the user of the consequences of an
// empty email.)
func getEmail(cfg server.Config) string {
// First try the tls directive from the Caddyfile
leEmail := cfg.TLS.LetsEncryptEmail
if leEmail == "" {
// Then try memory (command line flag or typed by user previously)
leEmail = DefaultEmail
}
if leEmail == "" {
// Then try to get most recent user email ~/.caddy/users file
userDirs, err := ioutil.ReadDir(storage.Users())
if err == nil {
var mostRecent os.FileInfo
for _, dir := range userDirs {
if !dir.IsDir() {
continue
}
if mostRecent == nil || dir.ModTime().After(mostRecent.ModTime()) {
mostRecent = dir
}
}
if mostRecent != nil {
leEmail = mostRecent.Name()
}
}
}
if leEmail == "" {
// Alas, we must bother the user and ask for an email address;
// if they proceed they also agree to the SA.
reader := bufio.NewReader(stdin)
fmt.Println("Your sites will be served over HTTPS automatically using Let's Encrypt.")
fmt.Println("By continuing, you agree to the Let's Encrypt Subscriber Agreement at:")
fmt.Println(" " + saURL) // TODO: Show current SA link
fmt.Println("Please enter your email address so you can recover your account if needed.")
fmt.Println("You can leave it blank, but you'll lose the ability to recover your account.")
fmt.Print("Email address: ")
var err error
leEmail, err = reader.ReadString('\n')
if err != nil {
return ""
}
DefaultEmail = leEmail
Agreed = true
}
return strings.TrimSpace(leEmail)
}
// promptUserAgreement prompts the user to agree to the agreement
// at agreementURL via stdin. If the agreement has changed, then pass
// true as the second argument. If this is the user's first time
// agreeing, pass false. It returns whether the user agreed or not.
func promptUserAgreement(agreementURL string, changed bool) bool {
if changed {
fmt.Printf("The Let's Encrypt Subscriber Agreement has changed:\n %s\n", agreementURL)
fmt.Print("Do you agree to the new terms? (y/n): ")
} else {
fmt.Printf("To continue, you must agree to the Let's Encrypt Subscriber Agreement:\n %s\n", agreementURL)
fmt.Print("Do you agree to the terms? (y/n): ")
}
reader := bufio.NewReader(stdin)
answer, err := reader.ReadString('\n')
if err != nil {
return false
}
answer = strings.ToLower(strings.TrimSpace(answer))
return answer == "y" || answer == "yes"
}
// stdin is used to read the user's input if prompted;
// this is changed by tests during tests.
var stdin = io.ReadWriter(os.Stdin)
// The name of the folder for accounts where the email
// address was not provided; default 'username' if you will.
const emptyEmail = "default"
// TODO: Use latest
const saURL = "https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf"
-192
View File
@@ -1,192 +0,0 @@
package letsencrypt
import (
"bytes"
"crypto/rand"
"crypto/rsa"
"io"
"os"
"strings"
"testing"
"time"
"github.com/mholt/caddy/server"
"github.com/xenolf/lego/acme"
)
func TestUser(t *testing.T) {
privateKey, err := rsa.GenerateKey(rand.Reader, 128)
if err != nil {
t.Fatalf("Could not generate test private key: %v", err)
}
u := User{
Email: "me@mine.com",
Registration: new(acme.RegistrationResource),
key: privateKey,
}
if expected, actual := "me@mine.com", u.GetEmail(); actual != expected {
t.Errorf("Expected email '%s' but got '%s'", expected, actual)
}
if u.GetRegistration() == nil {
t.Error("Expected a registration resource, but got nil")
}
if expected, actual := privateKey, u.GetPrivateKey(); actual != expected {
t.Errorf("Expected the private key at address %p but got one at %p instead ", expected, actual)
}
}
func TestNewUser(t *testing.T) {
email := "me@foobar.com"
user, err := newUser(email)
if err != nil {
t.Fatalf("Error creating user: %v", err)
}
if user.key == nil {
t.Error("Private key is nil")
}
if user.Email != email {
t.Errorf("Expected email to be %s, but was %s", email, user.Email)
}
if user.Registration != nil {
t.Error("New user already has a registration resource; it shouldn't")
}
}
func TestSaveUser(t *testing.T) {
storage = Storage("./testdata")
defer os.RemoveAll(string(storage))
email := "me@foobar.com"
user, err := newUser(email)
if err != nil {
t.Fatalf("Error creating user: %v", err)
}
err = saveUser(user)
if err != nil {
t.Fatalf("Error saving user: %v", err)
}
_, err = os.Stat(storage.UserRegFile(email))
if err != nil {
t.Errorf("Cannot access user registration file, error: %v", err)
}
_, err = os.Stat(storage.UserKeyFile(email))
if err != nil {
t.Errorf("Cannot access user private key file, error: %v", err)
}
}
func TestGetUserDoesNotAlreadyExist(t *testing.T) {
storage = Storage("./testdata")
defer os.RemoveAll(string(storage))
user, err := getUser("user_does_not_exist@foobar.com")
if err != nil {
t.Fatalf("Error getting user: %v", err)
}
if user.key == nil {
t.Error("Expected user to have a private key, but it was nil")
}
}
func TestGetUserAlreadyExists(t *testing.T) {
storage = Storage("./testdata")
defer os.RemoveAll(string(storage))
email := "me@foobar.com"
// Set up test
user, err := newUser(email)
if err != nil {
t.Fatalf("Error creating user: %v", err)
}
err = saveUser(user)
if err != nil {
t.Fatalf("Error saving user: %v", err)
}
// Expect to load user from disk
user2, err := getUser(email)
if err != nil {
t.Fatalf("Error getting user: %v", err)
}
// Assert keys are the same
if !rsaPrivateKeysSame(user.key, user2.key) {
t.Error("Expected private key to be the same after loading, but it wasn't")
}
// Assert emails are the same
if user.Email != user2.Email {
t.Errorf("Expected emails to be equal, but was '%s' before and '%s' after loading", user.Email, user2.Email)
}
}
func TestGetEmail(t *testing.T) {
storage = Storage("./testdata")
defer os.RemoveAll(string(storage))
DefaultEmail = "test2@foo.com"
// Test1: Use email in config
config := server.Config{
TLS: server.TLSConfig{
LetsEncryptEmail: "test1@foo.com",
},
}
actual := getEmail(config)
if actual != "test1@foo.com" {
t.Errorf("Did not get correct email from config; expected '%s' but got '%s'", "test1@foo.com", actual)
}
// Test2: Use default email from flag (or user previously typing it)
actual = getEmail(server.Config{})
if actual != DefaultEmail {
t.Errorf("Did not get correct email from config; expected '%s' but got '%s'", DefaultEmail, actual)
}
// Test3: Get input from user
DefaultEmail = ""
stdin = new(bytes.Buffer)
_, err := io.Copy(stdin, strings.NewReader("test3@foo.com\n"))
if err != nil {
t.Fatalf("Could not simulate user input, error: %v", err)
}
actual = getEmail(server.Config{})
if actual != "test3@foo.com" {
t.Errorf("Did not get correct email from user input prompt; expected '%s' but got '%s'", "test3@foo.com", actual)
}
// Test4: Get most recent email from before
DefaultEmail = ""
for i, eml := range []string{
"test4-3@foo.com",
"test4-2@foo.com",
"test4-1@foo.com",
} {
u, err := newUser(eml)
if err != nil {
t.Fatalf("Error creating user %d: %v", i, err)
}
err = saveUser(u)
if err != nil {
t.Fatalf("Error saving user %d: %v", i, err)
}
// Change modified time so they're all different, so the test becomes deterministic
f, err := os.Stat(storage.User(eml))
if err != nil {
t.Fatalf("Could not access user folder for '%s': %v", eml, err)
}
chTime := f.ModTime().Add(-(time.Duration(i) * time.Second))
if err := os.Chtimes(storage.User(eml), chTime, chTime); err != nil {
t.Fatalf("Could not change user folder mod time for '%s': %v", eml, err)
}
}
actual = getEmail(server.Config{})
if actual != "test4-3@foo.com" {
t.Errorf("Did not get correct email from storage; expected '%s' but got '%s'", "test4-3@foo.com", actual)
}
}
+28
View File
@@ -0,0 +1,28 @@
// 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.
// By moving the application's package main logic into
// a package other than main, it becomes much easier to
// wrap caddy for custom builds that are go-gettable.
// https://caddy.community/t/my-wish-for-0-9-go-gettable-custom-builds/59?u=matt
package main
import "github.com/mholt/caddy/caddy/caddymain"
var run = caddymain.Run // replaced for tests
func main() {
run()
}
+31
View File
@@ -0,0 +1,31 @@
// 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.
package main
import "testing"
// This works because it does not have the same signature as the
// conventional "TestMain" function described in the testing package
// godoc.
func TestMain(t *testing.T) {
var ran bool
run = func() {
ran = true
}
main()
if !ran {
t.Error("Expected Run() to be called, but it wasn't")
}
}
-165
View File
@@ -1,165 +0,0 @@
package parse
import (
"strings"
"testing"
)
type lexerTestCase struct {
input string
expected []token
}
func TestLexer(t *testing.T) {
testCases := []lexerTestCase{
{
input: `host:123`,
expected: []token{
{line: 1, text: "host:123"},
},
},
{
input: `host:123
directive`,
expected: []token{
{line: 1, text: "host:123"},
{line: 3, text: "directive"},
},
},
{
input: `host:123 {
directive
}`,
expected: []token{
{line: 1, text: "host:123"},
{line: 1, text: "{"},
{line: 2, text: "directive"},
{line: 3, text: "}"},
},
},
{
input: `host:123 { directive }`,
expected: []token{
{line: 1, text: "host:123"},
{line: 1, text: "{"},
{line: 1, text: "directive"},
{line: 1, text: "}"},
},
},
{
input: `host:123 {
#comment
directive
# comment
foobar # another comment
}`,
expected: []token{
{line: 1, text: "host:123"},
{line: 1, text: "{"},
{line: 3, text: "directive"},
{line: 5, text: "foobar"},
{line: 6, text: "}"},
},
},
{
input: `a "quoted value" b
foobar`,
expected: []token{
{line: 1, text: "a"},
{line: 1, text: "quoted value"},
{line: 1, text: "b"},
{line: 2, text: "foobar"},
},
},
{
input: `A "quoted \"value\" inside" B`,
expected: []token{
{line: 1, text: "A"},
{line: 1, text: `quoted "value" inside`},
{line: 1, text: "B"},
},
},
{
input: `"don't\escape"`,
expected: []token{
{line: 1, text: `don't\escape`},
},
},
{
input: `"don't\\escape"`,
expected: []token{
{line: 1, text: `don't\\escape`},
},
},
{
input: `A "quoted value with line
break inside" {
foobar
}`,
expected: []token{
{line: 1, text: "A"},
{line: 1, text: "quoted value with line\n\t\t\t\t\tbreak inside"},
{line: 2, text: "{"},
{line: 3, text: "foobar"},
{line: 4, text: "}"},
},
},
{
input: `"C:\php\php-cgi.exe"`,
expected: []token{
{line: 1, text: `C:\php\php-cgi.exe`},
},
},
{
input: `empty "" string`,
expected: []token{
{line: 1, text: `empty`},
{line: 1, text: ``},
{line: 1, text: `string`},
},
},
{
input: "skip those\r\nCR characters",
expected: []token{
{line: 1, text: "skip"},
{line: 1, text: "those"},
{line: 2, text: "CR"},
{line: 2, text: "characters"},
},
},
}
for i, testCase := range testCases {
actual := tokenize(testCase.input)
lexerCompare(t, i, testCase.expected, actual)
}
}
func tokenize(input string) (tokens []token) {
l := lexer{}
l.load(strings.NewReader(input))
for l.next() {
tokens = append(tokens, l.token)
}
return
}
func lexerCompare(t *testing.T, n int, expected, actual []token) {
if len(expected) != len(actual) {
t.Errorf("Test case %d: expected %d token(s) but got %d", n, len(expected), len(actual))
}
for i := 0; i < len(actual) && i < len(expected); i++ {
if actual[i].line != expected[i].line {
t.Errorf("Test case %d token %d ('%s'): expected line %d but was line %d",
n, i, expected[i].text, expected[i].line, actual[i].line)
break
}
if actual[i].text != expected[i].text {
t.Errorf("Test case %d token %d: expected text '%s' but was '%s'",
n, i, expected[i].text, actual[i].text)
break
}
}
}
-32
View File
@@ -1,32 +0,0 @@
// Package parse provides facilities for parsing configuration files.
package parse
import "io"
// ServerBlocks parses the input just enough to organize tokens,
// in order, by server block. No further parsing is performed.
// If checkDirectives is true, only valid directives will be allowed
// otherwise we consider it a parse error. Server blocks are returned
// in the order in which they appear.
func ServerBlocks(filename string, input io.Reader, checkDirectives bool) ([]serverBlock, error) {
p := parser{Dispenser: NewDispenser(filename, input)}
p.checkDirectives = checkDirectives
blocks, err := p.parseAll()
return blocks, err
}
// allTokens lexes the entire input, but does not parse it.
// It returns all the tokens from the input, unstructured
// and in order.
func allTokens(input io.Reader) (tokens []token) {
l := new(lexer)
l.load(input)
for l.next() {
tokens = append(tokens, l.token)
}
return
}
// ValidDirectives is a set of directives that are valid (unordered). Populated
// by config package's init function.
var ValidDirectives = make(map[string]struct{})
-22
View File
@@ -1,22 +0,0 @@
package parse
import (
"strings"
"testing"
)
func TestAllTokens(t *testing.T) {
input := strings.NewReader("a b c\nd e")
expected := []string{"a", "b", "c", "d", "e"}
tokens := allTokens(input)
if len(tokens) != len(expected) {
t.Fatalf("Expected %d tokens, got %d", len(expected), len(tokens))
}
for i, val := range expected {
if tokens[i].text != val {
t.Errorf("Token %d should be '%s' but was '%s'", i, val, tokens[i].text)
}
}
}
-355
View File
@@ -1,355 +0,0 @@
package parse
import (
"net"
"os"
"path/filepath"
"strings"
)
type parser struct {
Dispenser
block serverBlock // current server block being parsed
eof bool // if we encounter a valid EOF in a hard place
checkDirectives bool // if true, directives must be known
}
func (p *parser) parseAll() ([]serverBlock, error) {
var blocks []serverBlock
for p.Next() {
err := p.parseOne()
if err != nil {
return blocks, err
}
if len(p.block.Addresses) > 0 {
blocks = append(blocks, p.block)
}
}
return blocks, nil
}
func (p *parser) parseOne() error {
p.block = serverBlock{Tokens: make(map[string][]token)}
err := p.begin()
if err != nil {
return err
}
return nil
}
func (p *parser) begin() error {
if len(p.tokens) == 0 {
return nil
}
err := p.addresses()
if err != nil {
return err
}
if p.eof {
// this happens if the Caddyfile consists of only
// a line of addresses and nothing else
return nil
}
err = p.blockContents()
if err != nil {
return err
}
return nil
}
func (p *parser) addresses() error {
var expectingAnother bool
for {
tkn := replaceEnvVars(p.Val())
// special case: import directive replaces tokens during parse-time
if tkn == "import" && p.isNewLine() {
err := p.doImport()
if err != nil {
return err
}
continue
}
// Open brace definitely indicates end of addresses
if tkn == "{" {
if expectingAnother {
return p.Errf("Expected another address but had '%s' - check for extra comma", tkn)
}
break
}
if tkn != "" { // empty token possible if user typed "" in Caddyfile
// Trailing comma indicates another address will follow, which
// may possibly be on the next line
if tkn[len(tkn)-1] == ',' {
tkn = tkn[:len(tkn)-1]
expectingAnother = true
} else {
expectingAnother = false // but we may still see another one on this line
}
// Parse and save this address
host, port, err := standardAddress(tkn)
if err != nil {
return err
}
p.block.Addresses = append(p.block.Addresses, address{host, port})
}
// Advance token and possibly break out of loop or return error
hasNext := p.Next()
if expectingAnother && !hasNext {
return p.EOFErr()
}
if !hasNext {
p.eof = true
break // EOF
}
if !expectingAnother && p.isNewLine() {
break
}
}
return nil
}
func (p *parser) blockContents() error {
errOpenCurlyBrace := p.openCurlyBrace()
if errOpenCurlyBrace != nil {
// single-server configs don't need curly braces
p.cursor--
}
err := p.directives()
if err != nil {
return err
}
// Only look for close curly brace if there was an opening
if errOpenCurlyBrace == nil {
err = p.closeCurlyBrace()
if err != nil {
return err
}
}
return nil
}
// directives parses through all the lines for directives
// and it expects the next token to be the first
// directive. It goes until EOF or closing curly brace
// which ends the server block.
func (p *parser) directives() error {
for p.Next() {
// end of server block
if p.Val() == "}" {
break
}
// special case: import directive replaces tokens during parse-time
if p.Val() == "import" {
err := p.doImport()
if err != nil {
return err
}
p.cursor-- // cursor is advanced when we continue, so roll back one more
continue
}
// normal case: parse a directive on this line
if err := p.directive(); err != nil {
return err
}
}
return nil
}
// doImport swaps out the import directive and its argument
// (a total of 2 tokens) with the tokens in the file specified.
// When the function returns, the cursor is on the token before
// where the import directive was. In other words, call Next()
// to access the first token that was imported.
func (p *parser) doImport() error {
if !p.NextArg() {
return p.ArgErr()
}
importFile := p.Val()
if p.NextArg() {
return p.Err("Import allows only one file to import")
}
file, err := os.Open(importFile)
if err != nil {
return p.Errf("Could not import %s - %v", importFile, err)
}
defer file.Close()
importedTokens := allTokens(file)
// Tack the filename onto these tokens so any errors show the imported file's name
for i := 0; i < len(importedTokens); i++ {
importedTokens[i].file = filepath.Base(importFile)
}
// Splice out the import directive and its argument (2 tokens total)
// and insert the imported tokens in their place.
tokensBefore := p.tokens[:p.cursor-1]
tokensAfter := p.tokens[p.cursor+1:]
p.tokens = append(tokensBefore, append(importedTokens, tokensAfter...)...)
p.cursor-- // cursor was advanced one position to read the filename; rewind it
return nil
}
// directive collects tokens until the directive's scope
// closes (either end of line or end of curly brace block).
// It expects the currently-loaded token to be a directive
// (or } that ends a server block). The collected tokens
// are loaded into the current server block for later use
// by directive setup functions.
func (p *parser) directive() error {
dir := p.Val()
nesting := 0
if p.checkDirectives {
if _, ok := ValidDirectives[dir]; !ok {
return p.Errf("Unknown directive '%s'", dir)
}
}
// The directive itself is appended as a relevant token
p.block.Tokens[dir] = append(p.block.Tokens[dir], p.tokens[p.cursor])
for p.Next() {
if p.Val() == "{" {
nesting++
} else if p.isNewLine() && nesting == 0 {
p.cursor-- // read too far
break
} else if p.Val() == "}" && nesting > 0 {
nesting--
} else if p.Val() == "}" && nesting == 0 {
return p.Err("Unexpected '}' because no matching opening brace")
}
p.tokens[p.cursor].text = replaceEnvVars(p.tokens[p.cursor].text)
p.block.Tokens[dir] = append(p.block.Tokens[dir], p.tokens[p.cursor])
}
if nesting > 0 {
return p.EOFErr()
}
return nil
}
// openCurlyBrace expects the current token to be an
// opening curly brace. This acts like an assertion
// because it returns an error if the token is not
// a opening curly brace. It does NOT advance the token.
func (p *parser) openCurlyBrace() error {
if p.Val() != "{" {
return p.SyntaxErr("{")
}
return nil
}
// closeCurlyBrace expects the current token to be
// a closing curly brace. This acts like an assertion
// because it returns an error if the token is not
// a closing curly brace. It does NOT advance the token.
func (p *parser) closeCurlyBrace() error {
if p.Val() != "}" {
return p.SyntaxErr("}")
}
return nil
}
// standardAddress turns the accepted host and port patterns
// into a format accepted by net.Dial.
func standardAddress(str string) (host, port string, err error) {
var schemePort, splitPort string
if strings.HasPrefix(str, "https://") {
schemePort = "https"
str = str[8:]
} else if strings.HasPrefix(str, "http://") {
schemePort = "http"
str = str[7:]
}
host, splitPort, err = net.SplitHostPort(str)
if err != nil {
host, splitPort, err = net.SplitHostPort(str + ":") // tack on empty port
}
if err != nil {
// ¯\_(ツ)_/¯
host = str
}
if splitPort != "" {
port = splitPort
} else {
port = schemePort
}
return
}
// replaceEnvVars replaces environment variables that appear in the token
// and understands both the Unix $SYNTAX and Windows %SYNTAX%.
func replaceEnvVars(s string) string {
s = replaceEnvReferences(s, "{%", "%}")
s = replaceEnvReferences(s, "{$", "}")
return s
}
// replaceEnvReferences performs the actual replacement of env variables
// in s, given the placeholder start and placeholder end strings.
func replaceEnvReferences(s, refStart, refEnd string) string {
index := strings.Index(s, refStart)
for index != -1 {
endIndex := strings.Index(s, refEnd)
if endIndex != -1 {
ref := s[index : endIndex+len(refEnd)]
s = strings.Replace(s, ref, os.Getenv(ref[len(refStart):len(ref)-len(refEnd)]), -1)
} else {
return s
}
index = strings.Index(s, refStart)
}
return s
}
type (
// serverBlock associates tokens with a list of addresses
// and groups tokens by directive name.
serverBlock struct {
Addresses []address
Tokens map[string][]token
}
address struct {
Host, Port string
}
)
// HostList converts the list of addresses (hosts)
// that are associated with this server block into
// a slice of strings. Each string is a host:port
// combination.
func (sb serverBlock) HostList() []string {
sbHosts := make([]string, len(sb.Addresses))
for j, addr := range sb.Addresses {
sbHosts[j] = net.JoinHostPort(addr.Host, addr.Port)
}
return sbHosts
}
-457
View File
@@ -1,457 +0,0 @@
package parse
import (
"os"
"strings"
"testing"
)
func TestStandardAddress(t *testing.T) {
for i, test := range []struct {
input string
host, port string
shouldErr bool
}{
{`localhost`, "localhost", "", false},
{`localhost:1234`, "localhost", "1234", false},
{`localhost:`, "localhost", "", false},
{`0.0.0.0`, "0.0.0.0", "", false},
{`127.0.0.1:1234`, "127.0.0.1", "1234", false},
{`:1234`, "", "1234", false},
{`[::1]`, "::1", "", false},
{`[::1]:1234`, "::1", "1234", false},
{`:`, "", "", false},
{`localhost:http`, "localhost", "http", false},
{`localhost:https`, "localhost", "https", false},
{`:http`, "", "http", false},
{`:https`, "", "https", false},
{`http://localhost`, "localhost", "http", false},
{`https://localhost`, "localhost", "https", false},
{`http://127.0.0.1`, "127.0.0.1", "http", false},
{`https://127.0.0.1`, "127.0.0.1", "https", false},
{`http://[::1]`, "::1", "http", false},
{`http://localhost:1234`, "localhost", "1234", false},
{`https://127.0.0.1:1234`, "127.0.0.1", "1234", false},
{`http://[::1]:1234`, "::1", "1234", false},
{``, "", "", false},
{`::1`, "::1", "", true},
{`localhost::`, "localhost::", "", true},
{`#$%@`, "#$%@", "", true},
} {
host, port, err := standardAddress(test.input)
if err != nil && !test.shouldErr {
t.Errorf("Test %d: Expected no error, but had error: %v", i, err)
}
if err == nil && test.shouldErr {
t.Errorf("Test %d: Expected error, but had none", i)
}
if host != test.host {
t.Errorf("Test %d: Expected host '%s', got '%s'", i, test.host, host)
}
if port != test.port {
t.Errorf("Test %d: Expected port '%s', got '%s'", i, test.port, port)
}
}
}
func TestParseOneAndImport(t *testing.T) {
setupParseTests()
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
}
for i, test := range []struct {
input string
shouldErr bool
addresses []address
tokens map[string]int // map of directive name to number of tokens expected
}{
{`localhost`, false, []address{
{"localhost", ""},
}, map[string]int{}},
{`localhost
dir1`, false, []address{
{"localhost", ""},
}, map[string]int{
"dir1": 1,
}},
{`localhost:1234
dir1 foo bar`, false, []address{
{"localhost", "1234"},
}, map[string]int{
"dir1": 3,
}},
{`localhost {
dir1
}`, false, []address{
{"localhost", ""},
}, map[string]int{
"dir1": 1,
}},
{`localhost:1234 {
dir1 foo bar
dir2
}`, false, []address{
{"localhost", "1234"},
}, map[string]int{
"dir1": 3,
"dir2": 1,
}},
{`http://localhost https://localhost
dir1 foo bar`, false, []address{
{"localhost", "http"},
{"localhost", "https"},
}, map[string]int{
"dir1": 3,
}},
{`http://localhost https://localhost {
dir1 foo bar
}`, false, []address{
{"localhost", "http"},
{"localhost", "https"},
}, map[string]int{
"dir1": 3,
}},
{`http://localhost, https://localhost {
dir1 foo bar
}`, false, []address{
{"localhost", "http"},
{"localhost", "https"},
}, map[string]int{
"dir1": 3,
}},
{`http://localhost, {
}`, true, []address{
{"localhost", "http"},
}, map[string]int{}},
{`host1:80, http://host2.com
dir1 foo bar
dir2 baz`, false, []address{
{"host1", "80"},
{"host2.com", "http"},
}, map[string]int{
"dir1": 3,
"dir2": 2,
}},
{`http://host1.com,
http://host2.com,
https://host3.com`, false, []address{
{"host1.com", "http"},
{"host2.com", "http"},
{"host3.com", "https"},
}, map[string]int{}},
{`http://host1.com:1234, https://host2.com
dir1 foo {
bar baz
}
dir2`, false, []address{
{"host1.com", "1234"},
{"host2.com", "https"},
}, map[string]int{
"dir1": 6,
"dir2": 1,
}},
{`127.0.0.1
dir1 {
bar baz
}
dir2 {
foo bar
}`, false, []address{
{"127.0.0.1", ""},
}, map[string]int{
"dir1": 5,
"dir2": 5,
}},
{`127.0.0.1
unknown_directive`, true, []address{
{"127.0.0.1", ""},
}, map[string]int{}},
{`localhost
dir1 {
foo`, true, []address{
{"localhost", ""},
}, map[string]int{
"dir1": 3,
}},
{`localhost
dir1 {
}`, false, []address{
{"localhost", ""},
}, map[string]int{
"dir1": 3,
}},
{`localhost
dir1 {
} }`, true, []address{
{"localhost", ""},
}, map[string]int{
"dir1": 3,
}},
{`localhost
dir1 {
nested {
foo
}
}
dir2 foo bar`, false, []address{
{"localhost", ""},
}, map[string]int{
"dir1": 7,
"dir2": 3,
}},
{``, false, []address{}, map[string]int{}},
{`localhost
dir1 arg1
import import_test1.txt`, false, []address{
{"localhost", ""},
}, map[string]int{
"dir1": 2,
"dir2": 3,
"dir3": 1,
}},
{`import import_test2.txt`, false, []address{
{"host1", ""},
}, map[string]int{
"dir1": 1,
"dir2": 2,
}},
{`import import_test1.txt import_test2.txt`, true, []address{}, map[string]int{}},
{`import not_found.txt`, true, []address{}, map[string]int{}},
{`""`, false, []address{}, map[string]int{}},
{``, false, []address{}, map[string]int{}},
} {
result, err := testParseOne(test.input)
if test.shouldErr && err == nil {
t.Errorf("Test %d: Expected an error, but didn't get one", i)
}
if !test.shouldErr && err != nil {
t.Errorf("Test %d: Expected no error, but got: %v", i, err)
}
if len(result.Addresses) != len(test.addresses) {
t.Errorf("Test %d: Expected %d addresses, got %d",
i, len(test.addresses), len(result.Addresses))
continue
}
for j, addr := range result.Addresses {
if addr.Host != test.addresses[j].Host {
t.Errorf("Test %d, address %d: Expected host to be '%s', but was '%s'",
i, j, test.addresses[j].Host, addr.Host)
}
if addr.Port != test.addresses[j].Port {
t.Errorf("Test %d, address %d: Expected port to be '%s', but was '%s'",
i, j, test.addresses[j].Port, addr.Port)
}
}
if len(result.Tokens) != len(test.tokens) {
t.Errorf("Test %d: Expected %d directives, had %d",
i, len(test.tokens), len(result.Tokens))
continue
}
for directive, tokens := range result.Tokens {
if len(tokens) != test.tokens[directive] {
t.Errorf("Test %d, directive '%s': Expected %d tokens, counted %d",
i, directive, test.tokens[directive], len(tokens))
continue
}
}
}
}
func TestParseAll(t *testing.T) {
setupParseTests()
for i, test := range []struct {
input string
shouldErr bool
addresses [][]address // addresses per server block, in order
}{
{`localhost`, false, [][]address{
{{"localhost", ""}},
}},
{`localhost:1234`, false, [][]address{
[]address{{"localhost", "1234"}},
}},
{`localhost:1234 {
}
localhost:2015 {
}`, false, [][]address{
[]address{{"localhost", "1234"}},
[]address{{"localhost", "2015"}},
}},
{`localhost:1234, http://host2`, false, [][]address{
[]address{{"localhost", "1234"}, {"host2", "http"}},
}},
{`localhost:1234, http://host2,`, true, [][]address{}},
{`http://host1.com, http://host2.com {
}
https://host3.com, https://host4.com {
}`, false, [][]address{
[]address{{"host1.com", "http"}, {"host2.com", "http"}},
[]address{{"host3.com", "https"}, {"host4.com", "https"}},
}},
} {
p := testParser(test.input)
blocks, err := p.parseAll()
if test.shouldErr && err == nil {
t.Errorf("Test %d: Expected an error, but didn't get one", i)
}
if !test.shouldErr && err != nil {
t.Errorf("Test %d: Expected no error, but got: %v", i, err)
}
if len(blocks) != len(test.addresses) {
t.Errorf("Test %d: Expected %d server blocks, got %d",
i, len(test.addresses), len(blocks))
continue
}
for j, block := range blocks {
if len(block.Addresses) != len(test.addresses[j]) {
t.Errorf("Test %d: Expected %d addresses in block %d, got %d",
i, len(test.addresses[j]), j, len(block.Addresses))
continue
}
for k, addr := range block.Addresses {
if addr.Host != test.addresses[j][k].Host {
t.Errorf("Test %d, block %d, address %d: Expected host to be '%s', but was '%s'",
i, j, k, test.addresses[j][k].Host, addr.Host)
}
if addr.Port != test.addresses[j][k].Port {
t.Errorf("Test %d, block %d, address %d: Expected port to be '%s', but was '%s'",
i, j, k, test.addresses[j][k].Port, addr.Port)
}
}
}
}
}
func TestEnvironmentReplacement(t *testing.T) {
setupParseTests()
os.Setenv("PORT", "8080")
os.Setenv("ADDRESS", "servername.com")
os.Setenv("FOOBAR", "foobar")
// basic test; unix-style env vars
p := testParser(`{$ADDRESS}`)
blocks, _ := p.parseAll()
if actual, expected := blocks[0].Addresses[0].Host, "servername.com"; expected != actual {
t.Errorf("Expected host to be '%s' but was '%s'", expected, actual)
}
// multiple vars per token
p = testParser(`{$ADDRESS}:{$PORT}`)
blocks, _ = p.parseAll()
if actual, expected := blocks[0].Addresses[0].Host, "servername.com"; expected != actual {
t.Errorf("Expected host to be '%s' but was '%s'", expected, actual)
}
if actual, expected := blocks[0].Addresses[0].Port, "8080"; expected != actual {
t.Errorf("Expected port to be '%s' but was '%s'", expected, actual)
}
// windows-style var and unix style in same token
p = testParser(`{%ADDRESS%}:{$PORT}`)
blocks, _ = p.parseAll()
if actual, expected := blocks[0].Addresses[0].Host, "servername.com"; expected != actual {
t.Errorf("Expected host to be '%s' but was '%s'", expected, actual)
}
if actual, expected := blocks[0].Addresses[0].Port, "8080"; expected != actual {
t.Errorf("Expected port to be '%s' but was '%s'", expected, actual)
}
// reverse order
p = testParser(`{$ADDRESS}:{%PORT%}`)
blocks, _ = p.parseAll()
if actual, expected := blocks[0].Addresses[0].Host, "servername.com"; expected != actual {
t.Errorf("Expected host to be '%s' but was '%s'", expected, actual)
}
if actual, expected := blocks[0].Addresses[0].Port, "8080"; expected != actual {
t.Errorf("Expected port to be '%s' but was '%s'", expected, actual)
}
// env var in server block body as argument
p = testParser(":{%PORT%}\ndir1 {$FOOBAR}")
blocks, _ = p.parseAll()
if actual, expected := blocks[0].Addresses[0].Port, "8080"; expected != actual {
t.Errorf("Expected port to be '%s' but was '%s'", expected, actual)
}
if actual, expected := blocks[0].Tokens["dir1"][1].text, "foobar"; expected != actual {
t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
}
// combined windows env vars in argument
p = testParser(":{%PORT%}\ndir1 {%ADDRESS%}/{%FOOBAR%}")
blocks, _ = p.parseAll()
if actual, expected := blocks[0].Tokens["dir1"][1].text, "servername.com/foobar"; expected != actual {
t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
}
// malformed env var (windows)
p = testParser(":1234\ndir1 {%ADDRESS}")
blocks, _ = p.parseAll()
if actual, expected := blocks[0].Tokens["dir1"][1].text, "{%ADDRESS}"; expected != actual {
t.Errorf("Expected host to be '%s' but was '%s'", expected, actual)
}
// malformed (non-existent) env var (unix)
p = testParser(`:{$PORT$}`)
blocks, _ = p.parseAll()
if actual, expected := blocks[0].Addresses[0].Port, ""; expected != actual {
t.Errorf("Expected port to be '%s' but was '%s'", expected, actual)
}
}
func setupParseTests() {
// Set up some bogus directives for testing
ValidDirectives = map[string]struct{}{
"dir1": {},
"dir2": {},
"dir3": {},
}
}
func testParser(input string) parser {
buf := strings.NewReader(input)
p := parser{Dispenser: NewDispenser("Test", buf), checkDirectives: true}
return p
}
-112
View File
@@ -1,112 +0,0 @@
// +build !windows
package caddy
import (
"encoding/gob"
"io/ioutil"
"log"
"os"
"os/exec"
)
func init() {
gob.Register(CaddyfileInput{})
}
// Restart restarts the entire application; gracefully with zero
// downtime if on a POSIX-compatible system, or forcefully if on
// Windows but with imperceptibly-short downtime.
//
// The restarted application will use newCaddyfile as its input
// configuration. If newCaddyfile is nil, the current (existing)
// Caddyfile configuration will be used.
//
// Note: The process must exist in the same place on the disk in
// order for this to work. Thus, multiple graceful restarts don't
// work if executing with `go run`, since the binary is cleaned up
// when `go run` sees the initial parent process exit.
func Restart(newCaddyfile Input) error {
if newCaddyfile == nil {
caddyfileMu.Lock()
newCaddyfile = caddyfile
caddyfileMu.Unlock()
}
if len(os.Args) == 0 { // this should never happen, but...
os.Args = []string{""}
}
// Tell the child that it's a restart
os.Setenv("CADDY_RESTART", "true")
// Prepare our payload to the child process
cdyfileGob := caddyfileGob{
ListenerFds: make(map[string]uintptr),
Caddyfile: newCaddyfile,
}
// Prepare a pipe to the fork's stdin so it can get the Caddyfile
rpipe, wpipe, err := os.Pipe()
if err != nil {
return err
}
// Prepare a pipe that the child process will use to communicate
// its success with us by sending > 0 bytes
sigrpipe, sigwpipe, err := os.Pipe()
if err != nil {
return err
}
// Pass along relevant file descriptors to child process; ordering
// is very important since we rely on these being in certain positions.
extraFiles := []*os.File{sigwpipe}
// Add file descriptors of all the sockets
serversMu.Lock()
for i, s := range servers {
extraFiles = append(extraFiles, s.ListenerFd())
cdyfileGob.ListenerFds[s.Addr] = uintptr(4 + i) // 4 fds come before any of the listeners
}
serversMu.Unlock()
// Set up the command
cmd := exec.Command(os.Args[0], os.Args[1:]...)
cmd.Stdin = rpipe // fd 0
cmd.Stdout = os.Stdout // fd 1
cmd.Stderr = os.Stderr // fd 2
cmd.ExtraFiles = extraFiles
// Spawn the child process
err = cmd.Start()
if err != nil {
return err
}
// Immediately close our dup'ed fds and the write end of our signal pipe
for _, f := range extraFiles {
f.Close()
}
// Feed Caddyfile to the child
err = gob.NewEncoder(wpipe).Encode(cdyfileGob)
if err != nil {
return err
}
wpipe.Close()
// Determine whether child startup succeeded
answer, readErr := ioutil.ReadAll(sigrpipe)
if answer == nil || len(answer) == 0 {
cmdErr := cmd.Wait() // get exit status
log.Printf("[ERROR] Restart: child failed to initialize (%v) - changes not applied", cmdErr)
if readErr != nil {
log.Printf("[ERROR] Restart: additionally, error communicating with child process: %v", readErr)
}
return errIncompleteRestart
}
// Looks like child is successful; we can exit gracefully.
return Stop()
}
-27
View File
@@ -1,27 +0,0 @@
package caddy
// Restart restarts Caddy forcefully using newCaddyfile,
// or, if nil, the current/existing Caddyfile is reused.
func Restart(newCaddyfile Input) error {
if newCaddyfile == nil {
caddyfileMu.Lock()
newCaddyfile = caddyfile
caddyfileMu.Unlock()
}
wg.Add(1) // barrier so Wait() doesn't unblock
err := Stop()
if err != nil {
return err
}
err = Start(newCaddyfile)
if err != nil {
return err
}
wg.Done() // take down our barrier
return nil
}
-72
View File
@@ -1,72 +0,0 @@
package setup
import (
"strings"
"github.com/mholt/caddy/middleware"
"github.com/mholt/caddy/middleware/basicauth"
)
// BasicAuth configures a new BasicAuth middleware instance.
func BasicAuth(c *Controller) (middleware.Middleware, error) {
root := c.Root
rules, err := basicAuthParse(c)
if err != nil {
return nil, err
}
basic := basicauth.BasicAuth{Rules: rules}
return func(next middleware.Handler) middleware.Handler {
basic.Next = next
basic.SiteRoot = root
return basic
}, nil
}
func basicAuthParse(c *Controller) ([]basicauth.Rule, error) {
var rules []basicauth.Rule
var err error
for c.Next() {
var rule basicauth.Rule
args := c.RemainingArgs()
switch len(args) {
case 2:
rule.Username = args[0]
if rule.Password, err = passwordMatcher(rule.Username, args[1], c.Root); err != nil {
return rules, c.Errf("Get password matcher from %s: %v", c.Val(), err)
}
for c.NextBlock() {
rule.Resources = append(rule.Resources, c.Val())
if c.NextArg() {
return rules, c.Errf("Expecting only one resource per line (extra '%s')", c.Val())
}
}
case 3:
rule.Resources = append(rule.Resources, args[0])
rule.Username = args[1]
if rule.Password, err = passwordMatcher(rule.Username, args[2], c.Root); err != nil {
return rules, c.Errf("Get password matcher from %s: %v", c.Val(), err)
}
default:
return rules, c.ArgErr()
}
rules = append(rules, rule)
}
return rules, nil
}
func passwordMatcher(username, passw, siteRoot string) (basicauth.PasswordMatcher, error) {
if !strings.HasPrefix(passw, "htpasswd=") {
return basicauth.PlainMatcher(passw), nil
}
return basicauth.GetHtpasswdMatcher(passw[9:], username, siteRoot)
}
-13
View File
@@ -1,13 +0,0 @@
package setup
import "github.com/mholt/caddy/middleware"
// BindHost sets the host to bind the listener to.
func BindHost(c *Controller) (middleware.Middleware, error) {
for c.Next() {
if !c.Args(&c.BindHost) {
return nil, c.ArgErr()
}
}
return nil, nil
}
-256
View File
@@ -1,256 +0,0 @@
package setup
import (
"fmt"
"io/ioutil"
"text/template"
"github.com/mholt/caddy/middleware"
"github.com/mholt/caddy/middleware/browse"
)
// Browse configures a new Browse middleware instance.
func Browse(c *Controller) (middleware.Middleware, error) {
configs, err := browseParse(c)
if err != nil {
return nil, err
}
browse := browse.Browse{
Root: c.Root,
Configs: configs,
IgnoreIndexes: false,
}
return func(next middleware.Handler) middleware.Handler {
browse.Next = next
return browse
}, nil
}
func browseParse(c *Controller) ([]browse.Config, error) {
var configs []browse.Config
appendCfg := func(bc browse.Config) error {
for _, c := range configs {
if c.PathScope == bc.PathScope {
return fmt.Errorf("duplicate browsing config for %s", c.PathScope)
}
}
configs = append(configs, bc)
return nil
}
for c.Next() {
var bc browse.Config
// First argument is directory to allow browsing; default is site root
if c.NextArg() {
bc.PathScope = c.Val()
} else {
bc.PathScope = "/"
}
// Second argument would be the template file to use
var tplText string
if c.NextArg() {
tplBytes, err := ioutil.ReadFile(c.Val())
if err != nil {
return configs, err
}
tplText = string(tplBytes)
} else {
tplText = defaultTemplate
}
// Build the template
tpl, err := template.New("listing").Parse(tplText)
if err != nil {
return configs, err
}
bc.Template = tpl
// Save configuration
err = appendCfg(bc)
if err != nil {
return configs, err
}
}
return configs, nil
}
// The default template to use when serving up directory listings
const defaultTemplate = `<!DOCTYPE html>
<html>
<head>
<title>{{.Name}}</title>
<meta charset="utf-8">
<style>
* { padding: 0; margin: 0; }
body {
padding: 1% 2%;
font: 16px Arial;
}
header {
font-size: 45px;
padding: 25px;
}
header a {
text-decoration: none;
color: inherit;
}
header .up {
display: inline-block;
height: 50px;
width: 50px;
text-align: center;
margin-right: 20px;
}
header a.up:hover {
background: #000;
color: #FFF;
}
h1 {
font-size: 30px;
display: inline;
}
table {
border: 0;
border-collapse: collapse;
max-width: 750px;
margin: 0 auto;
}
th,
td {
padding: 4px 20px;
vertical-align: middle;
line-height: 1.5em; /* emoji are kind of odd heights */
}
th {
text-align: left;
}
th a {
color: #000;
text-decoration: none;
}
@media (max-width: 700px) {
.hideable {
display: none;
}
body {
padding: 0;
}
header,
header h1 {
font-size: 16px;
}
header {
position: fixed;
top: 0;
width: 100%;
background: #333;
color: #FFF;
padding: 15px;
text-align: center;
}
header .up {
height: auto;
width: auto;
display: none;
}
header a.up {
display: inline-block;
position: absolute;
left: 0;
top: 0;
width: 40px;
height: 48px;
font-size: 35px;
}
header h1 {
font-weight: normal;
}
main {
margin-top: 70px;
}
}
.name {
white-space: pre;
}
</style>
</head>
<body>
<header>
{{if .CanGoUp}}
<a href=".." class="up" title="Up one level">&#11025;</a>
{{else}}
<div class="up">&nbsp;</div>
{{end}}
<h1>{{.Path}}</h1>
</header>
<main>
<table>
<tr>
<th>
{{if and (eq .Sort "name") (ne .Order "desc")}}
<a href="?sort=name&order=desc">Name &#9650;</a>
{{else if and (eq .Sort "name") (ne .Order "asc")}}
<a href="?sort=name&order=asc">Name &#9660;</a>
{{else}}
<a href="?sort=name&order=asc">Name</a>
{{end}}
</th>
<th>
{{if and (eq .Sort "size") (ne .Order "desc")}}
<a href="?sort=size&order=desc">Size &#9650;</a>
{{else if and (eq .Sort "size") (ne .Order "asc")}}
<a href="?sort=size&order=asc">Size &#9660;</a>
{{else}}
<a href="?sort=size&order=asc">Size</a>
{{end}}
</th>
<th class="hideable">
{{if and (eq .Sort "time") (ne .Order "desc")}}
<a href="?sort=time&order=desc">Modified &#9650;</a>
{{else if and (eq .Sort "time") (ne .Order "asc")}}
<a href="?sort=time&order=asc">Modified &#9660;</a>
{{else}}
<a href="?sort=time&order=asc">Modified</a>
{{end}}
</th>
</tr>
{{range .Items}}
<tr>
<td>
{{if .IsDir}}&#128194;{{else}}&#128196;{{end}}
<a href="{{.URL}}" class="name">{{.Name}}</a>
</td>
<td>{{.HumanSize}}</td>
<td class="hideable">{{.HumanModTime "01/02/2006 3:04:05 PM -0700"}}</td>
</tr>
{{end}}
</table>
</main>
</body>
</html>`
-65
View File
@@ -1,65 +0,0 @@
package setup
import (
"io/ioutil"
"os"
"path/filepath"
"strconv"
"testing"
"time"
"github.com/mholt/caddy/middleware/browse"
)
func TestBrowse(t *testing.T) {
tempDirPath, err := getTempDirPath()
if err != nil {
t.Fatalf("BeforeTest: Failed to find an existing directory for testing! Error was: %v", err)
}
nonExistantDirPath := filepath.Join(tempDirPath, strconv.Itoa(int(time.Now().UnixNano())))
tempTemplate, err := ioutil.TempFile(".", "tempTemplate")
if err != nil {
t.Fatalf("BeforeTest: Failed to create a temporary file in the working directory! Error was: %v", err)
}
defer os.Remove(tempTemplate.Name())
tempTemplatePath := filepath.Join(".", tempTemplate.Name())
for i, test := range []struct {
input string
expectedPathScope []string
shouldErr bool
}{
// test case #0 tests handling of multiple pathscopes
{"browse " + tempDirPath + "\n browse .", []string{tempDirPath, "."}, false},
// test case #1 tests instantiation of browse.Config with default values
{"browse /", []string{"/"}, false},
// test case #2 tests detectaction of custom template
{"browse . " + tempTemplatePath, []string{"."}, false},
// test case #3 tests detection of non-existant template
{"browse . " + nonExistantDirPath, nil, true},
// test case #4 tests detection of duplicate pathscopes
{"browse " + tempDirPath + "\n browse " + tempDirPath, nil, true},
} {
recievedFunc, err := Browse(NewTestController(test.input))
if err != nil && !test.shouldErr {
t.Errorf("Test case #%d recieved an error of %v", i, err)
}
if test.expectedPathScope == nil {
continue
}
recievedConfigs := recievedFunc(nil).(browse.Browse).Configs
for j, config := range recievedConfigs {
if config.PathScope != test.expectedPathScope[j] {
t.Errorf("Test case #%d expected a pathscope of %v, but got %v", i, test.expectedPathScope, config.PathScope)
}
}
}
}
-83
View File
@@ -1,83 +0,0 @@
package setup
import (
"fmt"
"net/http"
"strings"
"github.com/mholt/caddy/caddy/parse"
"github.com/mholt/caddy/middleware"
"github.com/mholt/caddy/server"
)
// Controller is given to the setup function of middlewares which
// gives them access to be able to read tokens and set config. Each
// virtualhost gets their own server config and dispenser.
type Controller struct {
*server.Config
parse.Dispenser
// OncePerServerBlock is a function that executes f
// exactly once per server block, no matter how many
// hosts are associated with it. If it is the first
// time, the function f is executed immediately
// (not deferred) and may return an error which is
// returned by OncePerServerBlock.
OncePerServerBlock func(f func() error) error
// ServerBlockIndex is the 0-based index of the
// server block as it appeared in the input.
ServerBlockIndex int
// ServerBlockHostIndex is the 0-based index of this
// host as it appeared in the input at the head of the
// server block.
ServerBlockHostIndex int
// ServerBlockHosts is a list of hosts that are
// associated with this server block. All these
// hosts, consequently, share the same tokens.
ServerBlockHosts []string
// ServerBlockStorage is used by a directive's
// setup function to persist state between all
// the hosts on a server block.
ServerBlockStorage interface{}
}
// NewTestController creates a new *Controller for
// the input specified, with a filename of "Testfile".
// The Config is bare, consisting only of a Root of cwd.
//
// Used primarily for testing but needs to be exported so
// add-ons can use this as a convenience. Does not initialize
// the server-block-related fields.
func NewTestController(input string) *Controller {
return &Controller{
Config: &server.Config{
Root: ".",
},
Dispenser: parse.NewDispenser("Testfile", strings.NewReader(input)),
OncePerServerBlock: func(f func() error) error {
return f()
},
}
}
// EmptyNext is a no-op function that can be passed into
// middleware.Middleware functions so that the assignment
// to the Next field of the Handler can be tested.
//
// Used primarily for testing but needs to be exported so
// add-ons can use this as a convenience.
var EmptyNext = middleware.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
return 0, nil
})
// SameNext does a pointer comparison between next1 and next2.
//
// Used primarily for testing but needs to be exported so
// add-ons can use this as a convenience.
func SameNext(next1, next2 middleware.Handler) bool {
return fmt.Sprintf("%v", next1) == fmt.Sprintf("%v", next2)
}
-148
View File
@@ -1,148 +0,0 @@
package setup
import (
"io"
"log"
"os"
"path/filepath"
"strconv"
"github.com/hashicorp/go-syslog"
"github.com/mholt/caddy/middleware"
"github.com/mholt/caddy/middleware/errors"
)
// Errors configures a new gzip middleware instance.
func Errors(c *Controller) (middleware.Middleware, error) {
handler, err := errorsParse(c)
if err != nil {
return nil, err
}
// Open the log file for writing when the server starts
c.Startup = append(c.Startup, func() error {
var err error
var writer io.Writer
switch handler.LogFile {
case "visible":
handler.Debug = true
case "stdout":
writer = os.Stdout
case "stderr":
writer = os.Stderr
case "syslog":
writer, err = gsyslog.NewLogger(gsyslog.LOG_ERR, "LOCAL0", "caddy")
if err != nil {
return err
}
default:
if handler.LogFile == "" {
writer = os.Stderr // default
break
}
var file *os.File
file, err = os.OpenFile(handler.LogFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
return err
}
if handler.LogRoller != nil {
file.Close()
handler.LogRoller.Filename = handler.LogFile
writer = handler.LogRoller.GetLogWriter()
} else {
writer = file
}
}
handler.Log = log.New(writer, "", 0)
return nil
})
return func(next middleware.Handler) middleware.Handler {
handler.Next = next
return handler
}, nil
}
func errorsParse(c *Controller) (*errors.ErrorHandler, error) {
// Very important that we make a pointer because the Startup
// function that opens the log file must have access to the
// same instance of the handler, not a copy.
handler := &errors.ErrorHandler{ErrorPages: make(map[int]string)}
optionalBlock := func() (bool, error) {
var hadBlock bool
for c.NextBlock() {
hadBlock = true
what := c.Val()
if !c.NextArg() {
return hadBlock, c.ArgErr()
}
where := c.Val()
if what == "log" {
if where == "visible" {
handler.Debug = true
} else {
handler.LogFile = where
if c.NextArg() {
if c.Val() == "{" {
c.IncrNest()
logRoller, err := parseRoller(c)
if err != nil {
return hadBlock, err
}
handler.LogRoller = logRoller
}
}
}
} else {
// Error page; ensure it exists
where = filepath.Join(c.Root, where)
f, err := os.Open(where)
if err != nil {
log.Printf("[WARNING] Unable to open error page '%s': %v", where, err)
}
f.Close()
whatInt, err := strconv.Atoi(what)
if err != nil {
return hadBlock, c.Err("Expecting a numeric status code, got '" + what + "'")
}
handler.ErrorPages[whatInt] = where
}
}
return hadBlock, nil
}
for c.Next() {
// weird hack to avoid having the handler values overwritten.
if c.Val() == "}" {
continue
}
// Configuration may be in a block
hadBlock, err := optionalBlock()
if err != nil {
return handler, err
}
// Otherwise, the only argument would be an error log file name or 'visible'
if !hadBlock {
if c.NextArg() {
if c.Val() == "visible" {
handler.Debug = true
} else {
handler.LogFile = c.Val()
}
}
}
}
return handler, nil
}
-158
View File
@@ -1,158 +0,0 @@
package setup
import (
"testing"
"github.com/mholt/caddy/middleware"
"github.com/mholt/caddy/middleware/errors"
)
func TestErrors(t *testing.T) {
c := NewTestController(`errors`)
mid, err := Errors(c)
if err != nil {
t.Errorf("Expected no errors, got: %v", err)
}
if mid == nil {
t.Fatal("Expected middleware, was nil instead")
}
handler := mid(EmptyNext)
myHandler, ok := handler.(*errors.ErrorHandler)
if !ok {
t.Fatalf("Expected handler to be type ErrorHandler, got: %#v", handler)
}
if myHandler.LogFile != "" {
t.Errorf("Expected '%s' as the default LogFile", "")
}
if myHandler.LogRoller != nil {
t.Errorf("Expected LogRoller to be nil, got: %v", *myHandler.LogRoller)
}
if !SameNext(myHandler.Next, EmptyNext) {
t.Error("'Next' field of handler was not set properly")
}
// Test Startup function
if len(c.Startup) == 0 {
t.Fatal("Expected 1 startup function, had 0")
}
err = c.Startup[0]()
if myHandler.Log == nil {
t.Error("Expected Log to be non-nil after startup because Debug is not enabled")
}
}
func TestErrorsParse(t *testing.T) {
tests := []struct {
inputErrorsRules string
shouldErr bool
expectedErrorHandler errors.ErrorHandler
}{
{`errors`, false, errors.ErrorHandler{
LogFile: "",
}},
{`errors errors.txt`, false, errors.ErrorHandler{
LogFile: "errors.txt",
}},
{`errors visible`, false, errors.ErrorHandler{
LogFile: "",
Debug: true,
}},
{`errors { log visible }`, false, errors.ErrorHandler{
LogFile: "",
Debug: true,
}},
{`errors { log errors.txt
404 404.html
500 500.html
}`, false, errors.ErrorHandler{
LogFile: "errors.txt",
ErrorPages: map[int]string{
404: "404.html",
500: "500.html",
},
}},
{`errors { log errors.txt { size 2 age 10 keep 3 } }`, false, errors.ErrorHandler{
LogFile: "errors.txt",
LogRoller: &middleware.LogRoller{
MaxSize: 2,
MaxAge: 10,
MaxBackups: 3,
LocalTime: true,
},
}},
{`errors { log errors.txt {
size 3
age 11
keep 5
}
404 404.html
503 503.html
}`, false, errors.ErrorHandler{
LogFile: "errors.txt",
ErrorPages: map[int]string{
404: "404.html",
503: "503.html",
},
LogRoller: &middleware.LogRoller{
MaxSize: 3,
MaxAge: 11,
MaxBackups: 5,
LocalTime: true,
},
}},
}
for i, test := range tests {
c := NewTestController(test.inputErrorsRules)
actualErrorsRule, err := errorsParse(c)
if err == nil && test.shouldErr {
t.Errorf("Test %d didn't error, but it should have", i)
} else if err != nil && !test.shouldErr {
t.Errorf("Test %d errored, but it shouldn't have; got '%v'", i, err)
}
if actualErrorsRule.LogFile != test.expectedErrorHandler.LogFile {
t.Errorf("Test %d expected LogFile to be %s, but got %s",
i, test.expectedErrorHandler.LogFile, actualErrorsRule.LogFile)
}
if actualErrorsRule.Debug != test.expectedErrorHandler.Debug {
t.Errorf("Test %d expected Debug to be %v, but got %v",
i, test.expectedErrorHandler.Debug, actualErrorsRule.Debug)
}
if actualErrorsRule.LogRoller != nil && test.expectedErrorHandler.LogRoller == nil || actualErrorsRule.LogRoller == nil && test.expectedErrorHandler.LogRoller != nil {
t.Fatalf("Test %d expected LogRoller to be %v, but got %v",
i, test.expectedErrorHandler.LogRoller, actualErrorsRule.LogRoller)
}
if len(actualErrorsRule.ErrorPages) != len(test.expectedErrorHandler.ErrorPages) {
t.Fatalf("Test %d expected %d no of Error pages, but got %d ",
i, len(test.expectedErrorHandler.ErrorPages), len(actualErrorsRule.ErrorPages))
}
if actualErrorsRule.LogRoller != nil && test.expectedErrorHandler.LogRoller != nil {
if actualErrorsRule.LogRoller.Filename != test.expectedErrorHandler.LogRoller.Filename {
t.Fatalf("Test %d expected LogRoller Filename to be %s, but got %s",
i, test.expectedErrorHandler.LogRoller.Filename, actualErrorsRule.LogRoller.Filename)
}
if actualErrorsRule.LogRoller.MaxAge != test.expectedErrorHandler.LogRoller.MaxAge {
t.Fatalf("Test %d expected LogRoller MaxAge to be %d, but got %d",
i, test.expectedErrorHandler.LogRoller.MaxAge, actualErrorsRule.LogRoller.MaxAge)
}
if actualErrorsRule.LogRoller.MaxBackups != test.expectedErrorHandler.LogRoller.MaxBackups {
t.Fatalf("Test %d expected LogRoller MaxBackups to be %d, but got %d",
i, test.expectedErrorHandler.LogRoller.MaxBackups, actualErrorsRule.LogRoller.MaxBackups)
}
if actualErrorsRule.LogRoller.MaxSize != test.expectedErrorHandler.LogRoller.MaxSize {
t.Fatalf("Test %d expected LogRoller MaxSize to be %d, but got %d",
i, test.expectedErrorHandler.LogRoller.MaxSize, actualErrorsRule.LogRoller.MaxSize)
}
if actualErrorsRule.LogRoller.LocalTime != test.expectedErrorHandler.LogRoller.LocalTime {
t.Fatalf("Test %d expected LogRoller LocalTime to be %t, but got %t",
i, test.expectedErrorHandler.LogRoller.LocalTime, actualErrorsRule.LogRoller.LocalTime)
}
}
}
}
-54
View File
@@ -1,54 +0,0 @@
package setup
import (
"os"
"github.com/mholt/caddy/middleware"
"github.com/mholt/caddy/middleware/extensions"
)
// Ext configures a new instance of 'extensions' middleware for clean URLs.
func Ext(c *Controller) (middleware.Middleware, error) {
root := c.Root
exts, err := extParse(c)
if err != nil {
return nil, err
}
return func(next middleware.Handler) middleware.Handler {
return extensions.Ext{
Next: next,
Extensions: exts,
Root: root,
}
}, nil
}
// extParse sets up an instance of extension middleware
// from a middleware controller and returns a list of extensions.
func extParse(c *Controller) ([]string, error) {
var exts []string
for c.Next() {
// At least one extension is required
if !c.NextArg() {
return exts, c.ArgErr()
}
exts = append(exts, c.Val())
// Tack on any other extensions that may have been listed
exts = append(exts, c.RemainingArgs()...)
}
return exts, nil
}
// resourceExists returns true if the file specified at
// root + path exists; false otherwise.
func resourceExists(root, path string) bool {
_, err := os.Stat(root + path)
// technically we should use os.IsNotExist(err)
// but we don't handle any other kinds of errors anyway
return err == nil
}
-110
View File
@@ -1,110 +0,0 @@
package setup
import (
"errors"
"net/http"
"path/filepath"
"github.com/mholt/caddy/middleware"
"github.com/mholt/caddy/middleware/fastcgi"
)
// FastCGI configures a new FastCGI middleware instance.
func FastCGI(c *Controller) (middleware.Middleware, error) {
absRoot, err := filepath.Abs(c.Root)
if err != nil {
return nil, err
}
rules, err := fastcgiParse(c)
if err != nil {
return nil, err
}
return func(next middleware.Handler) middleware.Handler {
return fastcgi.Handler{
Next: next,
Rules: rules,
Root: c.Root,
AbsRoot: absRoot,
FileSys: http.Dir(c.Root),
SoftwareName: c.AppName,
SoftwareVersion: c.AppVersion,
ServerName: c.Host,
ServerPort: c.Port,
}
}, nil
}
func fastcgiParse(c *Controller) ([]fastcgi.Rule, error) {
var rules []fastcgi.Rule
for c.Next() {
var rule fastcgi.Rule
args := c.RemainingArgs()
switch len(args) {
case 0:
return rules, c.ArgErr()
case 1:
rule.Path = "/"
rule.Address = args[0]
case 2:
rule.Path = args[0]
rule.Address = args[1]
case 3:
rule.Path = args[0]
rule.Address = args[1]
err := fastcgiPreset(args[2], &rule)
if err != nil {
return rules, c.Err("Invalid fastcgi rule preset '" + args[2] + "'")
}
}
for c.NextBlock() {
switch c.Val() {
case "ext":
if !c.NextArg() {
return rules, c.ArgErr()
}
rule.Ext = c.Val()
case "split":
if !c.NextArg() {
return rules, c.ArgErr()
}
rule.SplitPath = c.Val()
case "index":
args := c.RemainingArgs()
if len(args) == 0 {
return rules, c.ArgErr()
}
rule.IndexFiles = args
case "env":
envArgs := c.RemainingArgs()
if len(envArgs) < 2 {
return rules, c.ArgErr()
}
rule.EnvVars = append(rule.EnvVars, [2]string{envArgs[0], envArgs[1]})
}
}
rules = append(rules, rule)
}
return rules, nil
}
// fastcgiPreset configures rule according to name. It returns an error if
// name is not a recognized preset name.
func fastcgiPreset(name string, rule *fastcgi.Rule) error {
switch name {
case "php":
rule.Ext = ".php"
rule.SplitPath = ".php"
rule.IndexFiles = []string{"index.php"}
default:
return errors.New(name + " is not a valid preset name")
}
return nil
}
-107
View File
@@ -1,107 +0,0 @@
package setup
import (
"fmt"
"github.com/mholt/caddy/middleware/fastcgi"
"testing"
)
func TestFastCGI(t *testing.T) {
c := NewTestController(`fastcgi / 127.0.0.1:9000`)
mid, err := FastCGI(c)
if err != nil {
t.Errorf("Expected no errors, got: %v", err)
}
if mid == nil {
t.Fatal("Expected middleware, was nil instead")
}
handler := mid(EmptyNext)
myHandler, ok := handler.(fastcgi.Handler)
if !ok {
t.Fatalf("Expected handler to be type , got: %#v", handler)
}
if myHandler.Rules[0].Path != "/" {
t.Errorf("Expected / as the Path")
}
if myHandler.Rules[0].Address != "127.0.0.1:9000" {
t.Errorf("Expected 127.0.0.1:9000 as the Address")
}
}
func TestFastcgiParse(t *testing.T) {
tests := []struct {
inputFastcgiConfig string
shouldErr bool
expectedFastcgiConfig []fastcgi.Rule
}{
{`fastcgi /blog 127.0.0.1:9000 php`,
false, []fastcgi.Rule{{
Path: "/blog",
Address: "127.0.0.1:9000",
Ext: ".php",
SplitPath: ".php",
IndexFiles: []string{"index.php"},
}}},
{`fastcgi / 127.0.0.1:9001 {
split .html
}`,
false, []fastcgi.Rule{{
Path: "/",
Address: "127.0.0.1:9001",
Ext: "",
SplitPath: ".html",
IndexFiles: []string{},
}}},
}
for i, test := range tests {
c := NewTestController(test.inputFastcgiConfig)
actualFastcgiConfigs, err := fastcgiParse(c)
if err == nil && test.shouldErr {
t.Errorf("Test %d didn't error, but it should have", i)
} else if err != nil && !test.shouldErr {
t.Errorf("Test %d errored, but it shouldn't have; got '%v'", i, err)
}
if len(actualFastcgiConfigs) != len(test.expectedFastcgiConfig) {
t.Fatalf("Test %d expected %d no of FastCGI configs, but got %d ",
i, len(test.expectedFastcgiConfig), len(actualFastcgiConfigs))
}
for j, actualFastcgiConfig := range actualFastcgiConfigs {
if actualFastcgiConfig.Path != test.expectedFastcgiConfig[j].Path {
t.Errorf("Test %d expected %dth FastCGI Path to be %s , but got %s",
i, j, test.expectedFastcgiConfig[j].Path, actualFastcgiConfig.Path)
}
if actualFastcgiConfig.Address != test.expectedFastcgiConfig[j].Address {
t.Errorf("Test %d expected %dth FastCGI Address to be %s , but got %s",
i, j, test.expectedFastcgiConfig[j].Address, actualFastcgiConfig.Address)
}
if actualFastcgiConfig.Ext != test.expectedFastcgiConfig[j].Ext {
t.Errorf("Test %d expected %dth FastCGI Ext to be %s , but got %s",
i, j, test.expectedFastcgiConfig[j].Ext, actualFastcgiConfig.Ext)
}
if actualFastcgiConfig.SplitPath != test.expectedFastcgiConfig[j].SplitPath {
t.Errorf("Test %d expected %dth FastCGI SplitPath to be %s , but got %s",
i, j, test.expectedFastcgiConfig[j].SplitPath, actualFastcgiConfig.SplitPath)
}
if fmt.Sprint(actualFastcgiConfig.IndexFiles) != fmt.Sprint(test.expectedFastcgiConfig[j].IndexFiles) {
t.Errorf("Test %d expected %dth FastCGI IndexFiles to be %s , but got %s",
i, j, test.expectedFastcgiConfig[j].IndexFiles, actualFastcgiConfig.IndexFiles)
}
}
}
}
-95
View File
@@ -1,95 +0,0 @@
package setup
import (
"fmt"
"strconv"
"strings"
"github.com/mholt/caddy/middleware"
"github.com/mholt/caddy/middleware/gzip"
)
// Gzip configures a new gzip middleware instance.
func Gzip(c *Controller) (middleware.Middleware, error) {
configs, err := gzipParse(c)
if err != nil {
return nil, err
}
return func(next middleware.Handler) middleware.Handler {
return gzip.Gzip{Next: next, Configs: configs}
}, nil
}
func gzipParse(c *Controller) ([]gzip.Config, error) {
var configs []gzip.Config
for c.Next() {
config := gzip.Config{}
pathFilter := gzip.PathFilter{IgnoredPaths: make(gzip.Set)}
extFilter := gzip.ExtFilter{Exts: make(gzip.Set)}
// No extra args expected
if len(c.RemainingArgs()) > 0 {
return configs, c.ArgErr()
}
for c.NextBlock() {
switch c.Val() {
case "ext":
exts := c.RemainingArgs()
if len(exts) == 0 {
return configs, c.ArgErr()
}
for _, e := range exts {
if !strings.HasPrefix(e, ".") && e != gzip.ExtWildCard {
return configs, fmt.Errorf(`gzip: invalid extension "%v" (must start with dot)`, e)
}
extFilter.Exts.Add(e)
}
case "not":
paths := c.RemainingArgs()
if len(paths) == 0 {
return configs, c.ArgErr()
}
for _, p := range paths {
if p == "/" {
return configs, fmt.Errorf(`gzip: cannot exclude path "/" - remove directive entirely instead`)
}
if !strings.HasPrefix(p, "/") {
return configs, fmt.Errorf(`gzip: invalid path "%v" (must start with /)`, p)
}
pathFilter.IgnoredPaths.Add(p)
}
case "level":
if !c.NextArg() {
return configs, c.ArgErr()
}
level, _ := strconv.Atoi(c.Val())
config.Level = level
default:
return configs, c.ArgErr()
}
}
config.Filters = []gzip.Filter{}
// If ignored paths are specified, put in front to filter with path first
if len(pathFilter.IgnoredPaths) > 0 {
config.Filters = []gzip.Filter{pathFilter}
}
// Then, if extensions are specified, use those to filter.
// Otherwise, use default extensions filter.
if len(extFilter.Exts) > 0 {
config.Filters = append(config.Filters, extFilter)
} else {
config.Filters = append(config.Filters, gzip.DefaultExtFilter())
}
configs = append(configs, config)
}
return configs, nil
}
-86
View File
@@ -1,86 +0,0 @@
package setup
import (
"testing"
"github.com/mholt/caddy/middleware/gzip"
)
func TestGzip(t *testing.T) {
c := NewTestController(`gzip`)
mid, err := Gzip(c)
if err != nil {
t.Errorf("Expected no errors, but got: %v", err)
}
if mid == nil {
t.Fatal("Expected middleware, was nil instead")
}
handler := mid(EmptyNext)
myHandler, ok := handler.(gzip.Gzip)
if !ok {
t.Fatalf("Expected handler to be type Gzip, got: %#v", handler)
}
if !SameNext(myHandler.Next, EmptyNext) {
t.Error("'Next' field of handler was not set properly")
}
tests := []struct {
input string
shouldErr bool
}{
{`gzip {`, true},
{`gzip {}`, true},
{`gzip a b`, true},
{`gzip a {`, true},
{`gzip { not f } `, true},
{`gzip { not } `, true},
{`gzip { not /file
ext .html
level 1
} `, false},
{`gzip { level 9 } `, false},
{`gzip { ext } `, true},
{`gzip { ext /f
} `, true},
{`gzip { not /file
ext .html
level 1
}
gzip`, false},
{`gzip { not /file
ext .html
level 1
}
gzip { not /file1
ext .htm
level 3
}
`, false},
{`gzip { not /file
ext .html
level 1
}
gzip { not /file1
ext .htm
level 3
}
`, false},
{`gzip { not /file
ext *
level 1
}
`, false},
}
for i, test := range tests {
c := NewTestController(test.input)
_, err := gzipParse(c)
if test.shouldErr && err == nil {
t.Errorf("Test %v: Expected error but found nil", i)
} else if !test.shouldErr && err != nil {
t.Errorf("Test %v: Expected no error but found error: %v", i, err)
}
}
}
-84
View File
@@ -1,84 +0,0 @@
package setup
import (
"github.com/mholt/caddy/middleware"
"github.com/mholt/caddy/middleware/headers"
)
// Headers configures a new Headers middleware instance.
func Headers(c *Controller) (middleware.Middleware, error) {
rules, err := headersParse(c)
if err != nil {
return nil, err
}
return func(next middleware.Handler) middleware.Handler {
return headers.Headers{Next: next, Rules: rules}
}, nil
}
func headersParse(c *Controller) ([]headers.Rule, error) {
var rules []headers.Rule
for c.NextLine() {
var head headers.Rule
var isNewPattern bool
if !c.NextArg() {
return rules, c.ArgErr()
}
pattern := c.Val()
// See if we already have a definition for this Path pattern...
for _, h := range rules {
if h.Path == pattern {
head = h
break
}
}
// ...otherwise, this is a new pattern
if head.Path == "" {
head.Path = pattern
isNewPattern = true
}
for c.NextBlock() {
// A block of headers was opened...
h := headers.Header{Name: c.Val()}
if c.NextArg() {
h.Value = c.Val()
}
head.Headers = append(head.Headers, h)
}
if c.NextArg() {
// ... or single header was defined as an argument instead.
h := headers.Header{Name: c.Val()}
h.Value = c.Val()
if c.NextArg() {
h.Value = c.Val()
}
head.Headers = append(head.Headers, h)
}
if isNewPattern {
rules = append(rules, head)
} else {
for i := 0; i < len(rules); i++ {
if rules[i].Path == pattern {
rules[i] = head
break
}
}
}
}
return rules, nil
}
-85
View File
@@ -1,85 +0,0 @@
package setup
import (
"fmt"
"testing"
"github.com/mholt/caddy/middleware/headers"
)
func TestHeaders(t *testing.T) {
c := NewTestController(`header / Foo Bar`)
mid, err := Headers(c)
if err != nil {
t.Errorf("Expected no errors, but got: %v", err)
}
if mid == nil {
t.Fatal("Expected middleware, was nil instead")
}
handler := mid(EmptyNext)
myHandler, ok := handler.(headers.Headers)
if !ok {
t.Fatalf("Expected handler to be type Headers, got: %#v", handler)
}
if !SameNext(myHandler.Next, EmptyNext) {
t.Error("'Next' field of handler was not set properly")
}
}
func TestHeadersParse(t *testing.T) {
tests := []struct {
input string
shouldErr bool
expected []headers.Rule
}{
{`header /foo Foo "Bar Baz"`,
false, []headers.Rule{
{Path: "/foo", Headers: []headers.Header{
{"Foo", "Bar Baz"},
}},
}},
{`header /bar { Foo "Bar Baz" Baz Qux }`,
false, []headers.Rule{
{Path: "/bar", Headers: []headers.Header{
{"Foo", "Bar Baz"},
{"Baz", "Qux"},
}},
}},
}
for i, test := range tests {
c := NewTestController(test.input)
actual, err := headersParse(c)
if err == nil && test.shouldErr {
t.Errorf("Test %d didn't error, but it should have", i)
} else if err != nil && !test.shouldErr {
t.Errorf("Test %d errored, but it shouldn't have; got '%v'", i, err)
}
if len(actual) != len(test.expected) {
t.Fatalf("Test %d expected %d rules, but got %d",
i, len(test.expected), len(actual))
}
for j, expectedRule := range test.expected {
actualRule := actual[j]
if actualRule.Path != expectedRule.Path {
t.Errorf("Test %d, rule %d: Expected path %s, but got %s",
i, j, expectedRule.Path, actualRule.Path)
}
expectedHeaders := fmt.Sprintf("%v", expectedRule.Headers)
actualHeaders := fmt.Sprintf("%v", actualRule.Headers)
if actualHeaders != expectedHeaders {
t.Errorf("Test %d, rule %d: Expected headers %s, but got %s",
i, j, expectedHeaders, actualHeaders)
}
}
}
}
-31
View File
@@ -1,31 +0,0 @@
package setup
import (
"github.com/mholt/caddy/middleware"
"github.com/mholt/caddy/middleware/inner"
)
// Internal configures a new Internal middleware instance.
func Internal(c *Controller) (middleware.Middleware, error) {
paths, err := internalParse(c)
if err != nil {
return nil, err
}
return func(next middleware.Handler) middleware.Handler {
return inner.Internal{Next: next, Paths: paths}
}, nil
}
func internalParse(c *Controller) ([]string, error) {
var paths []string
for c.Next() {
if !c.NextArg() {
return paths, c.ArgErr()
}
paths = append(paths, c.Val())
}
return paths, nil
}
-130
View File
@@ -1,130 +0,0 @@
package setup
import (
"io"
"log"
"os"
"github.com/hashicorp/go-syslog"
"github.com/mholt/caddy/middleware"
caddylog "github.com/mholt/caddy/middleware/log"
"github.com/mholt/caddy/server"
)
// Log sets up the logging middleware.
func Log(c *Controller) (middleware.Middleware, error) {
rules, err := logParse(c)
if err != nil {
return nil, err
}
// Open the log files for writing when the server starts
c.Startup = append(c.Startup, func() error {
for i := 0; i < len(rules); i++ {
var err error
var writer io.Writer
if rules[i].OutputFile == "stdout" {
writer = os.Stdout
} else if rules[i].OutputFile == "stderr" {
writer = os.Stderr
} else if rules[i].OutputFile == "syslog" {
writer, err = gsyslog.NewLogger(gsyslog.LOG_INFO, "LOCAL0", "caddy")
if err != nil {
return err
}
} else {
var file *os.File
file, err = os.OpenFile(rules[i].OutputFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
return err
}
if rules[i].Roller != nil {
file.Close()
rules[i].Roller.Filename = rules[i].OutputFile
writer = rules[i].Roller.GetLogWriter()
} else {
writer = file
}
}
rules[i].Log = log.New(writer, "", 0)
}
return nil
})
return func(next middleware.Handler) middleware.Handler {
return caddylog.Logger{Next: next, Rules: rules, ErrorFunc: server.DefaultErrorFunc}
}, nil
}
func logParse(c *Controller) ([]caddylog.Rule, error) {
var rules []caddylog.Rule
for c.Next() {
args := c.RemainingArgs()
var logRoller *middleware.LogRoller
if c.NextBlock() {
if c.Val() == "rotate" {
if c.NextArg() {
if c.Val() == "{" {
var err error
logRoller, err = parseRoller(c)
if err != nil {
return nil, err
}
// This part doesn't allow having something after the rotate block
if c.Next() {
if c.Val() != "}" {
return nil, c.ArgErr()
}
}
}
}
}
}
if len(args) == 0 {
// Nothing specified; use defaults
rules = append(rules, caddylog.Rule{
PathScope: "/",
OutputFile: caddylog.DefaultLogFilename,
Format: caddylog.DefaultLogFormat,
Roller: logRoller,
})
} else if len(args) == 1 {
// Only an output file specified
rules = append(rules, caddylog.Rule{
PathScope: "/",
OutputFile: args[0],
Format: caddylog.DefaultLogFormat,
Roller: logRoller,
})
} else {
// Path scope, output file, and maybe a format specified
format := caddylog.DefaultLogFormat
if len(args) > 2 {
switch args[2] {
case "{common}":
format = caddylog.CommonLogFormat
case "{combined}":
format = caddylog.CombinedLogFormat
default:
format = args[2]
}
}
rules = append(rules, caddylog.Rule{
PathScope: args[0],
OutputFile: args[1],
Format: format,
Roller: logRoller,
})
}
}
return rules, nil
}
-175
View File
@@ -1,175 +0,0 @@
package setup
import (
"testing"
"github.com/mholt/caddy/middleware"
caddylog "github.com/mholt/caddy/middleware/log"
)
func TestLog(t *testing.T) {
c := NewTestController(`log`)
mid, err := Log(c)
if err != nil {
t.Errorf("Expected no errors, got: %v", err)
}
if mid == nil {
t.Fatal("Expected middleware, was nil instead")
}
handler := mid(EmptyNext)
myHandler, ok := handler.(caddylog.Logger)
if !ok {
t.Fatalf("Expected handler to be type Logger, got: %#v", handler)
}
if myHandler.Rules[0].PathScope != "/" {
t.Errorf("Expected / as the default PathScope")
}
if myHandler.Rules[0].OutputFile != caddylog.DefaultLogFilename {
t.Errorf("Expected %s as the default OutputFile", caddylog.DefaultLogFilename)
}
if myHandler.Rules[0].Format != caddylog.DefaultLogFormat {
t.Errorf("Expected %s as the default Log Format", caddylog.DefaultLogFormat)
}
if myHandler.Rules[0].Roller != nil {
t.Errorf("Expected Roller to be nil, got: %v", *myHandler.Rules[0].Roller)
}
if !SameNext(myHandler.Next, EmptyNext) {
t.Error("'Next' field of handler was not set properly")
}
}
func TestLogParse(t *testing.T) {
tests := []struct {
inputLogRules string
shouldErr bool
expectedLogRules []caddylog.Rule
}{
{`log`, false, []caddylog.Rule{{
PathScope: "/",
OutputFile: caddylog.DefaultLogFilename,
Format: caddylog.DefaultLogFormat,
}}},
{`log log.txt`, false, []caddylog.Rule{{
PathScope: "/",
OutputFile: "log.txt",
Format: caddylog.DefaultLogFormat,
}}},
{`log /api log.txt`, false, []caddylog.Rule{{
PathScope: "/api",
OutputFile: "log.txt",
Format: caddylog.DefaultLogFormat,
}}},
{`log /serve stdout`, false, []caddylog.Rule{{
PathScope: "/serve",
OutputFile: "stdout",
Format: caddylog.DefaultLogFormat,
}}},
{`log /myapi log.txt {common}`, false, []caddylog.Rule{{
PathScope: "/myapi",
OutputFile: "log.txt",
Format: caddylog.CommonLogFormat,
}}},
{`log /test accesslog.txt {combined}`, false, []caddylog.Rule{{
PathScope: "/test",
OutputFile: "accesslog.txt",
Format: caddylog.CombinedLogFormat,
}}},
{`log /api1 log.txt
log /api2 accesslog.txt {combined}`, false, []caddylog.Rule{{
PathScope: "/api1",
OutputFile: "log.txt",
Format: caddylog.DefaultLogFormat,
}, {
PathScope: "/api2",
OutputFile: "accesslog.txt",
Format: caddylog.CombinedLogFormat,
}}},
{`log /api3 stdout {host}
log /api4 log.txt {when}`, false, []caddylog.Rule{{
PathScope: "/api3",
OutputFile: "stdout",
Format: "{host}",
}, {
PathScope: "/api4",
OutputFile: "log.txt",
Format: "{when}",
}}},
{`log access.log { rotate { size 2 age 10 keep 3 } }`, false, []caddylog.Rule{{
PathScope: "/",
OutputFile: "access.log",
Format: caddylog.DefaultLogFormat,
Roller: &middleware.LogRoller{
MaxSize: 2,
MaxAge: 10,
MaxBackups: 3,
LocalTime: true,
},
}}},
}
for i, test := range tests {
c := NewTestController(test.inputLogRules)
actualLogRules, err := logParse(c)
if err == nil && test.shouldErr {
t.Errorf("Test %d didn't error, but it should have", i)
} else if err != nil && !test.shouldErr {
t.Errorf("Test %d errored, but it shouldn't have; got '%v'", i, err)
}
if len(actualLogRules) != len(test.expectedLogRules) {
t.Fatalf("Test %d expected %d no of Log rules, but got %d ",
i, len(test.expectedLogRules), len(actualLogRules))
}
for j, actualLogRule := range actualLogRules {
if actualLogRule.PathScope != test.expectedLogRules[j].PathScope {
t.Errorf("Test %d expected %dth LogRule PathScope to be %s , but got %s",
i, j, test.expectedLogRules[j].PathScope, actualLogRule.PathScope)
}
if actualLogRule.OutputFile != test.expectedLogRules[j].OutputFile {
t.Errorf("Test %d expected %dth LogRule OutputFile to be %s , but got %s",
i, j, test.expectedLogRules[j].OutputFile, actualLogRule.OutputFile)
}
if actualLogRule.Format != test.expectedLogRules[j].Format {
t.Errorf("Test %d expected %dth LogRule Format to be %s , but got %s",
i, j, test.expectedLogRules[j].Format, actualLogRule.Format)
}
if actualLogRule.Roller != nil && test.expectedLogRules[j].Roller == nil || actualLogRule.Roller == nil && test.expectedLogRules[j].Roller != nil {
t.Fatalf("Test %d expected %dth LogRule Roller to be %v, but got %v",
i, j, test.expectedLogRules[j].Roller, actualLogRule.Roller)
}
if actualLogRule.Roller != nil && test.expectedLogRules[j].Roller != nil {
if actualLogRule.Roller.Filename != test.expectedLogRules[j].Roller.Filename {
t.Fatalf("Test %d expected %dth LogRule Roller Filename to be %s, but got %s",
i, j, test.expectedLogRules[j].Roller.Filename, actualLogRule.Roller.Filename)
}
if actualLogRule.Roller.MaxAge != test.expectedLogRules[j].Roller.MaxAge {
t.Fatalf("Test %d expected %dth LogRule Roller MaxAge to be %d, but got %d",
i, j, test.expectedLogRules[j].Roller.MaxAge, actualLogRule.Roller.MaxAge)
}
if actualLogRule.Roller.MaxBackups != test.expectedLogRules[j].Roller.MaxBackups {
t.Fatalf("Test %d expected %dth LogRule Roller MaxBackups to be %d, but got %d",
i, j, test.expectedLogRules[j].Roller.MaxBackups, actualLogRule.Roller.MaxBackups)
}
if actualLogRule.Roller.MaxSize != test.expectedLogRules[j].Roller.MaxSize {
t.Fatalf("Test %d expected %dth LogRule Roller MaxSize to be %d, but got %d",
i, j, test.expectedLogRules[j].Roller.MaxSize, actualLogRule.Roller.MaxSize)
}
if actualLogRule.Roller.LocalTime != test.expectedLogRules[j].Roller.LocalTime {
t.Fatalf("Test %d expected %dth LogRule Roller LocalTime to be %t, but got %t",
i, j, test.expectedLogRules[j].Roller.LocalTime, actualLogRule.Roller.LocalTime)
}
}
}
}
}
-157
View File
@@ -1,157 +0,0 @@
package setup
import (
"net/http"
"path"
"path/filepath"
"strings"
"github.com/mholt/caddy/middleware"
"github.com/mholt/caddy/middleware/markdown"
"github.com/russross/blackfriday"
)
// Markdown configures a new Markdown middleware instance.
func Markdown(c *Controller) (middleware.Middleware, error) {
mdconfigs, err := markdownParse(c)
if err != nil {
return nil, err
}
md := markdown.Markdown{
Root: c.Root,
FileSys: http.Dir(c.Root),
Configs: mdconfigs,
IndexFiles: []string{"index.md"},
}
// Sweep the whole path at startup to at least generate link index, maybe generate static site
c.Startup = append(c.Startup, func() error {
for i := range mdconfigs {
cfg := mdconfigs[i]
// Generate link index and static files (if enabled)
if err := markdown.GenerateStatic(md, cfg); err != nil {
return err
}
// Watch file changes for static site generation if not in development mode.
if !cfg.Development {
markdown.Watch(md, cfg, markdown.DefaultInterval)
}
}
return nil
})
return func(next middleware.Handler) middleware.Handler {
md.Next = next
return md
}, nil
}
func markdownParse(c *Controller) ([]*markdown.Config, error) {
var mdconfigs []*markdown.Config
for c.Next() {
md := &markdown.Config{
Renderer: blackfriday.HtmlRenderer(0, "", ""),
Templates: make(map[string]string),
StaticFiles: make(map[string]string),
}
// Get the path scope
args := c.RemainingArgs()
switch len(args) {
case 0:
md.PathScope = "/"
case 1:
md.PathScope = args[0]
default:
return mdconfigs, c.ArgErr()
}
// Load any other configuration parameters
for c.NextBlock() {
if err := loadParams(c, md); err != nil {
return mdconfigs, err
}
}
// If no extensions were specified, assume some defaults
if len(md.Extensions) == 0 {
md.Extensions = []string{".md", ".markdown", ".mdown"}
}
mdconfigs = append(mdconfigs, md)
}
return mdconfigs, nil
}
func loadParams(c *Controller, mdc *markdown.Config) error {
switch c.Val() {
case "ext":
exts := c.RemainingArgs()
if len(exts) == 0 {
return c.ArgErr()
}
mdc.Extensions = append(mdc.Extensions, exts...)
return nil
case "css":
if !c.NextArg() {
return c.ArgErr()
}
mdc.Styles = append(mdc.Styles, c.Val())
return nil
case "js":
if !c.NextArg() {
return c.ArgErr()
}
mdc.Scripts = append(mdc.Scripts, c.Val())
return nil
case "template":
tArgs := c.RemainingArgs()
switch len(tArgs) {
case 0:
return c.ArgErr()
case 1:
if _, ok := mdc.Templates[markdown.DefaultTemplate]; ok {
return c.Err("only one default template is allowed, use alias.")
}
fpath := filepath.ToSlash(filepath.Clean(c.Root + string(filepath.Separator) + tArgs[0]))
mdc.Templates[markdown.DefaultTemplate] = fpath
return nil
case 2:
fpath := filepath.ToSlash(filepath.Clean(c.Root + string(filepath.Separator) + tArgs[1]))
mdc.Templates[tArgs[0]] = fpath
return nil
default:
return c.ArgErr()
}
case "sitegen":
if c.NextArg() {
mdc.StaticDir = path.Join(c.Root, c.Val())
} else {
mdc.StaticDir = path.Join(c.Root, markdown.DefaultStaticDir)
}
if c.NextArg() {
// only 1 argument allowed
return c.ArgErr()
}
return nil
case "dev":
if c.NextArg() {
mdc.Development = strings.ToLower(c.Val()) == "true"
} else {
mdc.Development = true
}
if c.NextArg() {
// only 1 argument allowed
return c.ArgErr()
}
return nil
default:
return c.Err("Expected valid markdown configuration property")
}
}
-184
View File
@@ -1,184 +0,0 @@
package setup
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"testing"
"github.com/mholt/caddy/middleware"
"github.com/mholt/caddy/middleware/markdown"
)
func TestMarkdown(t *testing.T) {
c := NewTestController(`markdown /blog`)
mid, err := Markdown(c)
if err != nil {
t.Errorf("Expected no errors, got: %v", err)
}
if mid == nil {
t.Fatal("Expected middleware, was nil instead")
}
handler := mid(EmptyNext)
myHandler, ok := handler.(markdown.Markdown)
if !ok {
t.Fatalf("Expected handler to be type Markdown, got: %#v", handler)
}
if myHandler.Configs[0].PathScope != "/blog" {
t.Errorf("Expected /blog as the Path Scope")
}
if fmt.Sprint(myHandler.Configs[0].Extensions) != fmt.Sprint([]string{".md", ".markdown", ".mdown"}) {
t.Errorf("Expected .md, .markdown, and .mdown as default extensions")
}
}
func TestMarkdownStaticGen(t *testing.T) {
c := NewTestController(`markdown /blog {
ext .md
template tpl_with_include.html
sitegen
}`)
c.Root = "./testdata"
mid, err := Markdown(c)
if err != nil {
t.Errorf("Expected no errors, got: %v", err)
}
if mid == nil {
t.Fatal("Expected middleware, was nil instead")
}
for _, start := range c.Startup {
err := start()
if err != nil {
t.Errorf("Startup error: %v", err)
}
}
next := middleware.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
t.Fatalf("Next shouldn't be called")
return 0, nil
})
hndlr := mid(next)
mkdwn, ok := hndlr.(markdown.Markdown)
if !ok {
t.Fatalf("Was expecting a markdown.Markdown but got %T", hndlr)
}
expectedStaticFiles := map[string]string{"/blog/first_post.md": "testdata/generated_site/blog/first_post.md/index.html"}
if fmt.Sprint(expectedStaticFiles) != fmt.Sprint(mkdwn.Configs[0].StaticFiles) {
t.Fatalf("Test expected StaticFiles to be %s, but got %s",
fmt.Sprint(expectedStaticFiles), fmt.Sprint(mkdwn.Configs[0].StaticFiles))
}
filePath := "testdata/generated_site/blog/first_post.md/index.html"
if _, err := os.Stat(filePath); err != nil {
t.Fatalf("An error occured when getting the file information: %v", err)
}
html, err := ioutil.ReadFile(filePath)
if err != nil {
t.Fatalf("An error occured when getting the file content: %v", err)
}
expectedBody := []byte(`<!DOCTYPE html>
<html>
<head>
<title>first_post</title>
</head>
<body>
<h1>Header title</h1>
<h1>Test h1</h1>
</body>
</html>
`)
if !bytes.Equal(html, expectedBody) {
t.Fatalf("Expected file content: %s got: %s", string(expectedBody), string(html))
}
fp := filepath.Join(c.Root, markdown.DefaultStaticDir)
if err = os.RemoveAll(fp); err != nil {
t.Errorf("Error while removing the generated static files: %v", err)
}
}
func TestMarkdownParse(t *testing.T) {
tests := []struct {
inputMarkdownConfig string
shouldErr bool
expectedMarkdownConfig []markdown.Config
}{
{`markdown /blog {
ext .md .txt
css /resources/css/blog.css
js /resources/js/blog.js
}`, false, []markdown.Config{{
PathScope: "/blog",
Extensions: []string{".md", ".txt"},
Styles: []string{"/resources/css/blog.css"},
Scripts: []string{"/resources/js/blog.js"},
}}},
{`markdown /blog {
ext .md
template tpl_with_include.html
sitegen
}`, false, []markdown.Config{{
PathScope: "/blog",
Extensions: []string{".md"},
Templates: map[string]string{markdown.DefaultTemplate: "testdata/tpl_with_include.html"},
StaticDir: markdown.DefaultStaticDir,
}}},
}
for i, test := range tests {
c := NewTestController(test.inputMarkdownConfig)
c.Root = "./testdata"
actualMarkdownConfigs, err := markdownParse(c)
if err == nil && test.shouldErr {
t.Errorf("Test %d didn't error, but it should have", i)
} else if err != nil && !test.shouldErr {
t.Errorf("Test %d errored, but it shouldn't have; got '%v'", i, err)
}
if len(actualMarkdownConfigs) != len(test.expectedMarkdownConfig) {
t.Fatalf("Test %d expected %d no of WebSocket configs, but got %d ",
i, len(test.expectedMarkdownConfig), len(actualMarkdownConfigs))
}
for j, actualMarkdownConfig := range actualMarkdownConfigs {
if actualMarkdownConfig.PathScope != test.expectedMarkdownConfig[j].PathScope {
t.Errorf("Test %d expected %dth Markdown PathScope to be %s , but got %s",
i, j, test.expectedMarkdownConfig[j].PathScope, actualMarkdownConfig.PathScope)
}
if fmt.Sprint(actualMarkdownConfig.Styles) != fmt.Sprint(test.expectedMarkdownConfig[j].Styles) {
t.Errorf("Test %d expected %dth Markdown Config Styles to be %s , but got %s",
i, j, fmt.Sprint(test.expectedMarkdownConfig[j].Styles), fmt.Sprint(actualMarkdownConfig.Styles))
}
if fmt.Sprint(actualMarkdownConfig.Scripts) != fmt.Sprint(test.expectedMarkdownConfig[j].Scripts) {
t.Errorf("Test %d expected %dth Markdown Config Scripts to be %s , but got %s",
i, j, fmt.Sprint(test.expectedMarkdownConfig[j].Scripts), fmt.Sprint(actualMarkdownConfig.Scripts))
}
if fmt.Sprint(actualMarkdownConfig.Templates) != fmt.Sprint(test.expectedMarkdownConfig[j].Templates) {
t.Errorf("Test %d expected %dth Markdown Config Templates to be %s , but got %s",
i, j, fmt.Sprint(test.expectedMarkdownConfig[j].Templates), fmt.Sprint(actualMarkdownConfig.Templates))
}
}
}
}
-62
View File
@@ -1,62 +0,0 @@
package setup
import (
"fmt"
"strings"
"github.com/mholt/caddy/middleware"
"github.com/mholt/caddy/middleware/mime"
)
// Mime configures a new mime middleware instance.
func Mime(c *Controller) (middleware.Middleware, error) {
configs, err := mimeParse(c)
if err != nil {
return nil, err
}
return func(next middleware.Handler) middleware.Handler {
return mime.Mime{Next: next, Configs: configs}
}, nil
}
func mimeParse(c *Controller) ([]mime.Config, error) {
var configs []mime.Config
for c.Next() {
// At least one extension is required
args := c.RemainingArgs()
switch len(args) {
case 2:
if err := validateExt(args[0]); err != nil {
return configs, err
}
configs = append(configs, mime.Config{Ext: args[0], ContentType: args[1]})
case 1:
return configs, c.ArgErr()
case 0:
for c.NextBlock() {
ext := c.Val()
if err := validateExt(ext); err != nil {
return configs, err
}
if !c.NextArg() {
return configs, c.ArgErr()
}
configs = append(configs, mime.Config{Ext: ext, ContentType: c.Val()})
}
}
}
return configs, nil
}
// validateExt checks for valid file name extension.
func validateExt(ext string) error {
if !strings.HasPrefix(ext, ".") {
return fmt.Errorf(`mime: invalid extension "%v" (must start with dot)`, ext)
}
return nil
}
-59
View File
@@ -1,59 +0,0 @@
package setup
import (
"testing"
"github.com/mholt/caddy/middleware/mime"
)
func TestMime(t *testing.T) {
c := NewTestController(`mime .txt text/plain`)
mid, err := Mime(c)
if err != nil {
t.Errorf("Expected no errors, but got: %v", err)
}
if mid == nil {
t.Fatal("Expected middleware, was nil instead")
}
handler := mid(EmptyNext)
myHandler, ok := handler.(mime.Mime)
if !ok {
t.Fatalf("Expected handler to be type Mime, got: %#v", handler)
}
if !SameNext(myHandler.Next, EmptyNext) {
t.Error("'Next' field of handler was not set properly")
}
tests := []struct {
input string
shouldErr bool
}{
{`mime {`, true},
{`mime {}`, true},
{`mime a b`, true},
{`mime a {`, true},
{`mime { txt f } `, true},
{`mime { html } `, true},
{`mime {
.html text/html
.txt text/plain
} `, false},
{`mime { .html text/html } `, false},
{`mime { .html
} `, true},
{`mime .txt text/plain`, false},
}
for i, test := range tests {
c := NewTestController(test.input)
m, err := mimeParse(c)
if test.shouldErr && err == nil {
t.Errorf("Test %v: Expected error but found nil %v", i, m)
} else if !test.shouldErr && err != nil {
t.Errorf("Test %v: Expected no error but found error: %v", i, err)
}
}
}
-17
View File
@@ -1,17 +0,0 @@
package setup
import (
"github.com/mholt/caddy/middleware"
"github.com/mholt/caddy/middleware/proxy"
)
// Proxy configures a new Proxy middleware instance.
func Proxy(c *Controller) (middleware.Middleware, error) {
upstreams, err := proxy.NewStaticUpstreams(c.Dispenser)
if err != nil {
return nil, err
}
return func(next middleware.Handler) middleware.Handler {
return proxy.Proxy{Next: next, Upstreams: upstreams}
}, nil
}
-173
View File
@@ -1,173 +0,0 @@
package setup
import (
"net/http"
"github.com/mholt/caddy/middleware"
"github.com/mholt/caddy/middleware/redirect"
)
// Redir configures a new Redirect middleware instance.
func Redir(c *Controller) (middleware.Middleware, error) {
rules, err := redirParse(c)
if err != nil {
return nil, err
}
return func(next middleware.Handler) middleware.Handler {
return redirect.Redirect{Next: next, Rules: rules}
}, nil
}
func redirParse(c *Controller) ([]redirect.Rule, error) {
var redirects []redirect.Rule
// setRedirCode sets the redirect code for rule if it can, or returns an error
setRedirCode := func(code string, rule *redirect.Rule) error {
if code == "meta" {
rule.Meta = true
} else if codeNumber, ok := httpRedirs[code]; ok {
rule.Code = codeNumber
} else {
return c.Errf("Invalid redirect code '%v'", code)
}
return nil
}
// checkAndSaveRule checks the rule for validity (except the redir code)
// and saves it if it's valid, or returns an error.
checkAndSaveRule := func(rule redirect.Rule) error {
if rule.FromPath == rule.To {
return c.Err("'from' and 'to' values of redirect rule cannot be the same")
}
for _, otherRule := range redirects {
if otherRule.FromPath == rule.FromPath {
return c.Errf("rule with duplicate 'from' value: %s -> %s", otherRule.FromPath, otherRule.To)
}
}
redirects = append(redirects, rule)
return nil
}
for c.Next() {
args := c.RemainingArgs()
var hadOptionalBlock bool
for c.NextBlock() {
hadOptionalBlock = true
var rule redirect.Rule
if c.Config.TLS.Enabled {
rule.FromScheme = "https"
} else {
rule.FromScheme = "http"
}
// Set initial redirect code
// BUG: If the code is specified for a whole block and that code is invalid,
// the line number will appear on the first line inside the block, even if that
// line overwrites the block-level code with a valid redirect code. The program
// still functions correctly, but the line number in the error reporting is
// misleading to the user.
if len(args) == 1 {
err := setRedirCode(args[0], &rule)
if err != nil {
return redirects, err
}
} else {
rule.Code = http.StatusMovedPermanently // default code
}
// RemainingArgs only gets the values after the current token, but in our
// case we want to include the current token to get an accurate count.
insideArgs := append([]string{c.Val()}, c.RemainingArgs()...)
switch len(insideArgs) {
case 1:
// To specified (catch-all redirect)
// Not sure why user is doing this in a table, as it causes all other redirects to be ignored.
// As such, this feature remains undocumented.
rule.FromPath = "/"
rule.To = insideArgs[0]
case 2:
// From and To specified
rule.FromPath = insideArgs[0]
rule.To = insideArgs[1]
case 3:
// From, To, and Code specified
rule.FromPath = insideArgs[0]
rule.To = insideArgs[1]
err := setRedirCode(insideArgs[2], &rule)
if err != nil {
return redirects, err
}
default:
return redirects, c.ArgErr()
}
err := checkAndSaveRule(rule)
if err != nil {
return redirects, err
}
}
if !hadOptionalBlock {
var rule redirect.Rule
if c.Config.TLS.Enabled {
rule.FromScheme = "https"
} else {
rule.FromScheme = "http"
}
rule.Code = http.StatusMovedPermanently // default
switch len(args) {
case 1:
// To specified (catch-all redirect)
rule.FromPath = "/"
rule.To = args[0]
case 2:
// To and Code specified (catch-all redirect)
rule.FromPath = "/"
rule.To = args[0]
err := setRedirCode(args[1], &rule)
if err != nil {
return redirects, err
}
case 3:
// From, To, and Code specified
rule.FromPath = args[0]
rule.To = args[1]
err := setRedirCode(args[2], &rule)
if err != nil {
return redirects, err
}
default:
return redirects, c.ArgErr()
}
err := checkAndSaveRule(rule)
if err != nil {
return redirects, err
}
}
}
return redirects, nil
}
// httpRedirs is a list of supported HTTP redirect codes.
var httpRedirs = map[string]int{
"300": http.StatusMultipleChoices,
"301": http.StatusMovedPermanently,
"302": http.StatusFound, // (NOT CORRECT for "Temporary Redirect", see 307)
"303": http.StatusSeeOther,
"304": http.StatusNotModified,
"305": http.StatusUseProxy,
"307": http.StatusTemporaryRedirect,
"308": 308, // Permanent Redirect
}
-67
View File
@@ -1,67 +0,0 @@
package setup
import (
"testing"
"github.com/mholt/caddy/middleware/redirect"
)
func TestRedir(t *testing.T) {
for j, test := range []struct {
input string
shouldErr bool
expectedRules []redirect.Rule
}{
// test case #0 tests the recognition of a valid HTTP status code defined outside of block statement
{"redir 300 {\n/ /foo\n}", false, []redirect.Rule{redirect.Rule{FromPath: "/", To: "/foo", Code: 300}}},
// test case #1 tests the recognition of an invalid HTTP status code defined outside of block statement
{"redir 9000 {\n/ /foo\n}", true, []redirect.Rule{redirect.Rule{}}},
// test case #2 tests the detection of a valid HTTP status code outside of a block statement being overriden by an invalid HTTP status code inside statement of a block statement
{"redir 300 {\n/ /foo 9000\n}", true, []redirect.Rule{redirect.Rule{}}},
// test case #3 tests the detection of an invalid HTTP status code outside of a block statement being overriden by a valid HTTP status code inside statement of a block statement
{"redir 9000 {\n/ /foo 300\n}", true, []redirect.Rule{redirect.Rule{}}},
// test case #4 tests the recognition of a TO redirection in a block statement.The HTTP status code is set to the default of 301 - MovedPermanently
{"redir 302 {\n/foo\n}", false, []redirect.Rule{redirect.Rule{FromPath: "/", To: "/foo", Code: 302}}},
// test case #5 tests the recognition of a TO and From redirection in a block statement
{"redir {\n/bar /foo 303\n}", false, []redirect.Rule{redirect.Rule{FromPath: "/bar", To: "/foo", Code: 303}}},
// test case #6 tests the recognition of a TO redirection in a non-block statement. The HTTP status code is set to the default of 301 - MovedPermanently
{"redir /foo", false, []redirect.Rule{redirect.Rule{FromPath: "/", To: "/foo", Code: 301}}},
// test case #7 tests the recognition of a TO and From redirection in a non-block statement
{"redir /bar /foo 303", false, []redirect.Rule{redirect.Rule{FromPath: "/bar", To: "/foo", Code: 303}}},
// test case #8 tests the recognition of multiple redirections
{"redir {\n / /foo 304 \n} \n redir {\n /bar /foobar 305 \n}", false, []redirect.Rule{redirect.Rule{FromPath: "/", To: "/foo", Code: 304}, redirect.Rule{FromPath: "/bar", To: "/foobar", Code: 305}}},
// test case #9 tests the detection of duplicate redirections
{"redir {\n /bar /foo 304 \n} redir {\n /bar /foo 304 \n}", true, []redirect.Rule{redirect.Rule{}}},
} {
recievedFunc, err := Redir(NewTestController(test.input))
if err != nil && !test.shouldErr {
t.Errorf("Test case #%d recieved an error of %v", j, err)
} else if test.shouldErr {
continue
}
recievedRules := recievedFunc(nil).(redirect.Redirect).Rules
for i, recievedRule := range recievedRules {
if recievedRule.FromPath != test.expectedRules[i].FromPath {
t.Errorf("Test case #%d.%d expected a from path of %s, but recieved a from path of %s", j, i, test.expectedRules[i].FromPath, recievedRule.FromPath)
}
if recievedRule.To != test.expectedRules[i].To {
t.Errorf("Test case #%d.%d expected a TO path of %s, but recieved a TO path of %s", j, i, test.expectedRules[i].To, recievedRule.To)
}
if recievedRule.Code != test.expectedRules[i].Code {
t.Errorf("Test case #%d.%d expected a HTTP status code of %d, but recieved a code of %d", j, i, test.expectedRules[i].Code, recievedRule.Code)
}
}
}
}
-79
View File
@@ -1,79 +0,0 @@
package setup
import (
"github.com/mholt/caddy/middleware"
"github.com/mholt/caddy/middleware/rewrite"
)
// Rewrite configures a new Rewrite middleware instance.
func Rewrite(c *Controller) (middleware.Middleware, error) {
rewrites, err := rewriteParse(c)
if err != nil {
return nil, err
}
return func(next middleware.Handler) middleware.Handler {
return rewrite.Rewrite{Next: next, Rules: rewrites}
}, nil
}
func rewriteParse(c *Controller) ([]rewrite.Rule, error) {
var simpleRules []rewrite.Rule
var regexpRules []rewrite.Rule
for c.Next() {
var rule rewrite.Rule
var err error
var base = "/"
var pattern, to string
var ext []string
args := c.RemainingArgs()
switch len(args) {
case 2:
rule = rewrite.NewSimpleRule(args[0], args[1])
simpleRules = append(simpleRules, rule)
case 1:
base = args[0]
fallthrough
case 0:
for c.NextBlock() {
switch c.Val() {
case "r", "regexp":
if !c.NextArg() {
return nil, c.ArgErr()
}
pattern = c.Val()
case "to":
if !c.NextArg() {
return nil, c.ArgErr()
}
to = c.Val()
case "ext":
args1 := c.RemainingArgs()
if len(args1) == 0 {
return nil, c.ArgErr()
}
ext = args1
default:
return nil, c.ArgErr()
}
}
// ensure pattern and to are specified
if pattern == "" || to == "" {
return nil, c.ArgErr()
}
if rule, err = rewrite.NewRegexpRule(base, pattern, to, ext); err != nil {
return nil, err
}
regexpRules = append(regexpRules, rule)
default:
return nil, c.ArgErr()
}
}
// put simple rules in front to avoid regexp computation for them
return append(simpleRules, regexpRules...), nil
}
-185
View File
@@ -1,185 +0,0 @@
package setup
import (
"testing"
"fmt"
"regexp"
"github.com/mholt/caddy/middleware/rewrite"
)
func TestRewrite(t *testing.T) {
c := NewTestController(`rewrite /from /to`)
mid, err := Rewrite(c)
if err != nil {
t.Errorf("Expected no errors, but got: %v", err)
}
if mid == nil {
t.Fatal("Expected middleware, was nil instead")
}
handler := mid(EmptyNext)
myHandler, ok := handler.(rewrite.Rewrite)
if !ok {
t.Fatalf("Expected handler to be type Rewrite, got: %#v", handler)
}
if !SameNext(myHandler.Next, EmptyNext) {
t.Error("'Next' field of handler was not set properly")
}
if len(myHandler.Rules) != 1 {
t.Errorf("Expected handler to have %d rule, has %d instead", 1, len(myHandler.Rules))
}
}
func TestRewriteParse(t *testing.T) {
simpleTests := []struct {
input string
shouldErr bool
expected []rewrite.Rule
}{
{`rewrite /from /to`, false, []rewrite.Rule{
rewrite.SimpleRule{From: "/from", To: "/to"},
}},
{`rewrite /from /to
rewrite a b`, false, []rewrite.Rule{
rewrite.SimpleRule{From: "/from", To: "/to"},
rewrite.SimpleRule{From: "a", To: "b"},
}},
{`rewrite a`, true, []rewrite.Rule{}},
{`rewrite`, true, []rewrite.Rule{}},
{`rewrite a b c`, true, []rewrite.Rule{
rewrite.SimpleRule{From: "a", To: "b"},
}},
}
for i, test := range simpleTests {
c := NewTestController(test.input)
actual, err := rewriteParse(c)
if err == nil && test.shouldErr {
t.Errorf("Test %d didn't error, but it should have", i)
} else if err != nil && !test.shouldErr {
t.Errorf("Test %d errored, but it shouldn't have; got '%v'", i, err)
} else if err != nil && test.shouldErr {
continue
}
if len(actual) != len(test.expected) {
t.Fatalf("Test %d expected %d rules, but got %d",
i, len(test.expected), len(actual))
}
for j, e := range test.expected {
actualRule := actual[j].(rewrite.SimpleRule)
expectedRule := e.(rewrite.SimpleRule)
if actualRule.From != expectedRule.From {
t.Errorf("Test %d, rule %d: Expected From=%s, got %s",
i, j, expectedRule.From, actualRule.From)
}
if actualRule.To != expectedRule.To {
t.Errorf("Test %d, rule %d: Expected To=%s, got %s",
i, j, expectedRule.To, actualRule.To)
}
}
}
regexpTests := []struct {
input string
shouldErr bool
expected []rewrite.Rule
}{
{`rewrite {
r .*
to /to
}`, false, []rewrite.Rule{
&rewrite.RegexpRule{Base: "/", To: "/to", Regexp: regexp.MustCompile(".*")},
}},
{`rewrite {
regexp .*
to /to
ext / html txt
}`, false, []rewrite.Rule{
&rewrite.RegexpRule{Base: "/", To: "/to", Exts: []string{"/", "html", "txt"}, Regexp: regexp.MustCompile(".*")},
}},
{`rewrite /path {
r rr
to /dest
}
rewrite / {
regexp [a-z]+
to /to
}
`, false, []rewrite.Rule{
&rewrite.RegexpRule{Base: "/path", To: "/dest", Regexp: regexp.MustCompile("rr")},
&rewrite.RegexpRule{Base: "/", To: "/to", Regexp: regexp.MustCompile("[a-z]+")},
}},
{`rewrite {
to /to
}`, true, []rewrite.Rule{
&rewrite.RegexpRule{},
}},
{`rewrite {
r .*
}`, true, []rewrite.Rule{
&rewrite.RegexpRule{},
}},
{`rewrite {
}`, true, []rewrite.Rule{
&rewrite.RegexpRule{},
}},
{`rewrite /`, true, []rewrite.Rule{
&rewrite.RegexpRule{},
}},
}
for i, test := range regexpTests {
c := NewTestController(test.input)
actual, err := rewriteParse(c)
if err == nil && test.shouldErr {
t.Errorf("Test %d didn't error, but it should have", i)
} else if err != nil && !test.shouldErr {
t.Errorf("Test %d errored, but it shouldn't have; got '%v'", i, err)
} else if err != nil && test.shouldErr {
continue
}
if len(actual) != len(test.expected) {
t.Fatalf("Test %d expected %d rules, but got %d",
i, len(test.expected), len(actual))
}
for j, e := range test.expected {
actualRule := actual[j].(*rewrite.RegexpRule)
expectedRule := e.(*rewrite.RegexpRule)
if actualRule.Base != expectedRule.Base {
t.Errorf("Test %d, rule %d: Expected Base=%s, got %s",
i, j, expectedRule.Base, actualRule.Base)
}
if actualRule.To != expectedRule.To {
t.Errorf("Test %d, rule %d: Expected To=%s, got %s",
i, j, expectedRule.To, actualRule.To)
}
if fmt.Sprint(actualRule.Exts) != fmt.Sprint(expectedRule.Exts) {
t.Errorf("Test %d, rule %d: Expected Ext=%v, got %v",
i, j, expectedRule.To, actualRule.To)
}
if actualRule.String() != expectedRule.String() {
t.Errorf("Test %d, rule %d: Expected Pattern=%s, got %s",
i, j, expectedRule.String(), actualRule.String())
}
}
}
}
-40
View File
@@ -1,40 +0,0 @@
package setup
import (
"strconv"
"github.com/mholt/caddy/middleware"
)
func parseRoller(c *Controller) (*middleware.LogRoller, error) {
var size, age, keep int
// This is kind of a hack to support nested blocks:
// As we are already in a block: either log or errors,
// c.nesting > 0 but, as soon as c meets a }, it thinks
// the block is over and return false for c.NextBlock.
for c.NextBlock() {
what := c.Val()
if !c.NextArg() {
return nil, c.ArgErr()
}
value := c.Val()
var err error
switch what {
case "size":
size, err = strconv.Atoi(value)
case "age":
age, err = strconv.Atoi(value)
case "keep":
keep, err = strconv.Atoi(value)
}
if err != nil {
return nil, err
}
}
return &middleware.LogRoller{
MaxSize: size,
MaxAge: age,
MaxBackups: keep,
LocalTime: true,
}, nil
}
-32
View File
@@ -1,32 +0,0 @@
package setup
import (
"log"
"os"
"github.com/mholt/caddy/middleware"
)
// Root sets up the root file path of the server.
func Root(c *Controller) (middleware.Middleware, error) {
for c.Next() {
if !c.NextArg() {
return nil, c.ArgErr()
}
c.Root = c.Val()
}
// Check if root path exists
_, err := os.Stat(c.Root)
if err != nil {
if os.IsNotExist(err) {
// Allow this, because the folder might appear later.
// But make sure the user knows!
log.Printf("[WARNING] Root path does not exist: %s", c.Root)
} else {
return nil, c.Errf("Unable to access root path '%s': %v", c.Root, err)
}
}
return nil, nil
}
-108
View File
@@ -1,108 +0,0 @@
package setup
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
)
func TestRoot(t *testing.T) {
// Predefined error substrings
parseErrContent := "Parse error:"
unableToAccessErrContent := "Unable to access root path"
existingDirPath, err := getTempDirPath()
if err != nil {
t.Fatalf("BeforeTest: Failed to find an existing directory for testing! Error was: %v", err)
}
nonExistingDir := filepath.Join(existingDirPath, "highly_unlikely_to_exist_dir")
existingFile, err := ioutil.TempFile("", "root_test")
if err != nil {
t.Fatalf("BeforeTest: Failed to create temp file for testing! Error was: %v", err)
}
defer func() {
existingFile.Close()
os.Remove(existingFile.Name())
}()
inaccessiblePath := getInaccessiblePath(existingFile.Name())
tests := []struct {
input string
shouldErr bool
expectedRoot string // expected root, set to the controller. Empty for negative cases.
expectedErrContent string // substring from the expected error. Empty for positive cases.
}{
// positive
{
fmt.Sprintf(`root %s`, nonExistingDir), false, nonExistingDir, "",
},
{
fmt.Sprintf(`root %s`, existingDirPath), false, existingDirPath, "",
},
// negative
{
`root `, true, "", parseErrContent,
},
{
fmt.Sprintf(`root %s`, inaccessiblePath), true, "", unableToAccessErrContent,
},
{
fmt.Sprintf(`root {
%s
}`, existingDirPath), true, "", parseErrContent,
},
}
for i, test := range tests {
c := NewTestController(test.input)
mid, err := Root(c)
if test.shouldErr && err == nil {
t.Errorf("Test %d: Expected error but found %s for input %s", i, err, test.input)
}
if err != nil {
if !test.shouldErr {
t.Errorf("Test %d: Expected no error but found one for input %s. Error was: %v", i, test.input, err)
}
if !strings.Contains(err.Error(), test.expectedErrContent) {
t.Errorf("Test %d: Expected error to contain: %v, found error: %v, input: %s", i, test.expectedErrContent, err, test.input)
}
}
// the Root method always returns a nil middleware
if mid != nil {
t.Errorf("Middware, returned from Root() was not nil: %v", mid)
}
// check c.Root only if we are in a positive test.
if !test.shouldErr && test.expectedRoot != c.Root {
t.Errorf("Root not correctly set for input %s. Expected: %s, actual: %s", test.input, test.expectedRoot, c.Root)
}
}
}
// getTempDirPath returnes the path to the system temp directory. If it does not exists - an error is returned.
func getTempDirPath() (string, error) {
tempDir := os.TempDir()
_, err := os.Stat(tempDir)
if err != nil {
return "", err
}
return tempDir, nil
}
func getInaccessiblePath(file string) string {
// null byte in filename is not allowed on Windows AND unix
return filepath.Join("C:", "file\x00name")
}
-64
View File
@@ -1,64 +0,0 @@
package setup
import (
"os"
"os/exec"
"strings"
"github.com/mholt/caddy/middleware"
)
// Startup registers a startup callback to execute during server start.
func Startup(c *Controller) (middleware.Middleware, error) {
return nil, registerCallback(c, &c.FirstStartup)
}
// Shutdown registers a shutdown callback to execute during process exit.
func Shutdown(c *Controller) (middleware.Middleware, error) {
return nil, registerCallback(c, &c.Shutdown)
}
// registerCallback registers a callback function to execute by
// using c to parse the line. It appends the callback function
// to the list of callback functions passed in by reference.
func registerCallback(c *Controller, list *[]func() error) error {
var funcs []func() error
for c.Next() {
args := c.RemainingArgs()
if len(args) == 0 {
return c.ArgErr()
}
nonblock := false
if len(args) > 1 && args[len(args)-1] == "&" {
// Run command in background; non-blocking
nonblock = true
args = args[:len(args)-1]
}
command, args, err := middleware.SplitCommandAndArgs(strings.Join(args, " "))
if err != nil {
return c.Err(err.Error())
}
fn := func() error {
cmd := exec.Command(command, args...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if nonblock {
return cmd.Start()
}
return cmd.Run()
}
funcs = append(funcs, fn)
}
return c.OncePerServerBlock(func() error {
*list = append(*list, funcs...)
return nil
})
}
-58
View File
@@ -1,58 +0,0 @@
package setup
import (
"os"
"os/exec"
"path/filepath"
"strconv"
"testing"
"time"
)
// The Startup function's tests are symmetrical to Shutdown tests,
// because the Startup and Shutdown functions share virtually the
// same functionality
func TestStartup(t *testing.T) {
tempDirPath, err := getTempDirPath()
if err != nil {
t.Fatalf("BeforeTest: Failed to find an existing directory for testing! Error was: %v", err)
}
testDir := filepath.Join(tempDirPath, "temp_dir_for_testing_startupshutdown.go")
osSenitiveTestDir := filepath.FromSlash(testDir)
exec.Command("rm", "-r", osSenitiveTestDir).Run() // removes osSenitiveTestDir from the OS's temp directory, if the osSenitiveTestDir already exists
tests := []struct {
input string
shouldExecutionErr bool
shouldRemoveErr bool
}{
// test case #0 tests proper functionality blocking commands
{"startup mkdir " + osSenitiveTestDir, false, false},
// test case #1 tests proper functionality of non-blocking commands
{"startup mkdir " + osSenitiveTestDir + " &", false, true},
// test case #2 tests handling of non-existant commands
{"startup " + strconv.Itoa(int(time.Now().UnixNano())), true, true},
}
for i, test := range tests {
c := NewTestController(test.input)
_, err = Startup(c)
if err != nil {
t.Errorf("Expected no errors, got: %v", err)
}
err = c.FirstStartup[0]()
if err != nil && !test.shouldExecutionErr {
t.Errorf("Test %d recieved an error of:\n%v", i, err)
}
err = os.Remove(osSenitiveTestDir)
if err != nil && !test.shouldRemoveErr {
t.Errorf("Test %d recieved an error of:\n%v", i, err)
}
}
}
-1
View File
@@ -1 +0,0 @@
# Test h1
-10
View File
@@ -1,10 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>{{.Doc.title}}</title>
</head>
<body>
{{.Include "header.html"}}
{{.Doc.body}}
</body>
</html>
-161
View File
@@ -1,161 +0,0 @@
package setup
import (
"crypto/tls"
"log"
"strings"
"github.com/mholt/caddy/middleware"
"github.com/mholt/caddy/server"
)
// TLS sets up the TLS configuration (but does not activate Let's Encrypt; that is handled elsewhere).
func TLS(c *Controller) (middleware.Middleware, error) {
if c.Port == "http" {
c.TLS.Enabled = false
log.Printf("[WARNING] TLS disabled for %s://%s. To force TLS over the plaintext HTTP port, "+
"specify port 80 explicitly (https://%s:80).", c.Port, c.Host, c.Host)
} else {
c.TLS.Enabled = true // they had a tls directive, so assume it's on unless we confirm otherwise later
}
for c.Next() {
args := c.RemainingArgs()
switch len(args) {
case 1:
c.TLS.LetsEncryptEmail = args[0]
// user can force-disable LE activation this way
if c.TLS.LetsEncryptEmail == "off" {
c.TLS.Enabled = false
}
case 2:
c.TLS.Certificate = args[0]
c.TLS.Key = args[1]
// manual HTTPS configuration without port specified should be
// served on the HTTPS port; that is what user would expect, and
// makes it consistent with how the letsencrypt package works.
if c.Port == "" {
c.Port = "https"
}
default:
return nil, c.ArgErr()
}
// Optional block
for c.NextBlock() {
switch c.Val() {
case "protocols":
args := c.RemainingArgs()
if len(args) != 2 {
return nil, c.ArgErr()
}
value, ok := supportedProtocols[strings.ToLower(args[0])]
if !ok {
return nil, c.Errf("Wrong protocol name or protocol not supported '%s'", c.Val())
}
c.TLS.ProtocolMinVersion = value
value, ok = supportedProtocols[strings.ToLower(args[1])]
if !ok {
return nil, c.Errf("Wrong protocol name or protocol not supported '%s'", c.Val())
}
c.TLS.ProtocolMaxVersion = value
case "ciphers":
for c.NextArg() {
value, ok := supportedCiphersMap[strings.ToUpper(c.Val())]
if !ok {
return nil, c.Errf("Wrong cipher name or cipher not supported '%s'", c.Val())
}
c.TLS.Ciphers = append(c.TLS.Ciphers, value)
}
case "clients":
c.TLS.ClientCerts = c.RemainingArgs()
if len(c.TLS.ClientCerts) == 0 {
return nil, c.ArgErr()
}
default:
return nil, c.Errf("Unknown keyword '%s'", c.Val())
}
}
}
SetDefaultTLSParams(c.Config)
return nil, nil
}
// SetDefaultTLSParams sets the default TLS cipher suites, protocol versions and server preferences
// of a server.Config if they were not previously set.
func SetDefaultTLSParams(c *server.Config) {
// If no ciphers provided, use all that Caddy supports for the protocol
if len(c.TLS.Ciphers) == 0 {
c.TLS.Ciphers = supportedCiphers
}
// Not a cipher suite, but still important for mitigating protocol downgrade attacks
c.TLS.Ciphers = append(c.TLS.Ciphers, tls.TLS_FALLBACK_SCSV)
// Set default protocol min and max versions - must balance compatibility and security
if c.TLS.ProtocolMinVersion == 0 {
c.TLS.ProtocolMinVersion = tls.VersionTLS10
}
if c.TLS.ProtocolMaxVersion == 0 {
c.TLS.ProtocolMaxVersion = tls.VersionTLS12
}
// Prefer server cipher suites
c.TLS.PreferServerCipherSuites = true
}
// Map of supported protocols
// SSLv3 will be not supported in future release
// HTTP/2 only supports TLS 1.2 and higher
var supportedProtocols = map[string]uint16{
"ssl3.0": tls.VersionSSL30,
"tls1.0": tls.VersionTLS10,
"tls1.1": tls.VersionTLS11,
"tls1.2": tls.VersionTLS12,
}
// Map of supported ciphers, used only for parsing config.
//
// Note that, at time of writing, HTTP/2 blacklists 276 cipher suites,
// including all but two of the suites below (the two GCM suites).
// See https://http2.github.io/http2-spec/#BadCipherSuites
//
// TLS_FALLBACK_SCSV is not in this list because we manually ensure
// it is always added (even though it is not technically a cipher suite).
//
// This map, like any map, is NOT ORDERED. Do not range over this map.
var supportedCiphersMap = map[string]uint16{
"ECDHE-RSA-AES128-GCM-SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
"ECDHE-ECDSA-AES128-GCM-SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
"ECDHE-RSA-AES128-CBC-SHA": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
"ECDHE-RSA-AES256-CBC-SHA": tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
"ECDHE-ECDSA-AES256-CBC-SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
"ECDHE-ECDSA-AES128-CBC-SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
"RSA-AES128-CBC-SHA": tls.TLS_RSA_WITH_AES_128_CBC_SHA,
"RSA-AES256-CBC-SHA": tls.TLS_RSA_WITH_AES_256_CBC_SHA,
"ECDHE-RSA-3DES-EDE-CBC-SHA": tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
"RSA-3DES-EDE-CBC-SHA": tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
}
// List of supported cipher suites in descending order of preference.
// Ordering is very important! Getting the wrong order will break
// mainstream clients, especially with HTTP/2.
//
// Note that TLS_FALLBACK_SCSV is not in this list since it is always
// added manually.
var supportedCiphers = []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_RSA_WITH_AES_128_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
}
-153
View File
@@ -1,153 +0,0 @@
package setup
import (
"crypto/tls"
"testing"
)
func TestTLSParseBasic(t *testing.T) {
c := NewTestController(`tls cert.pem key.pem`)
_, err := TLS(c)
if err != nil {
t.Errorf("Expected no errors, got: %v", err)
}
// Basic checks
if c.TLS.Certificate != "cert.pem" {
t.Errorf("Expected certificate arg to be 'cert.pem', was '%s'", c.TLS.Certificate)
}
if c.TLS.Key != "key.pem" {
t.Errorf("Expected key arg to be 'key.pem', was '%s'", c.TLS.Key)
}
if !c.TLS.Enabled {
t.Error("Expected TLS Enabled=true, but was false")
}
// Security defaults
if c.TLS.ProtocolMinVersion != tls.VersionTLS10 {
t.Errorf("Expected 'tls1.0 (0x0301)' as ProtocolMinVersion, got %#v", c.TLS.ProtocolMinVersion)
}
if c.TLS.ProtocolMaxVersion != tls.VersionTLS12 {
t.Errorf("Expected 'tls1.2 (0x0303)' as ProtocolMaxVersion, got %v", c.TLS.ProtocolMaxVersion)
}
// Cipher checks
expectedCiphers := []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_RSA_WITH_AES_128_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
tls.TLS_FALLBACK_SCSV,
}
// Ensure count is correct (plus one for TLS_FALLBACK_SCSV)
if len(c.TLS.Ciphers) != len(supportedCiphers)+1 {
t.Errorf("Expected %v Ciphers (including TLS_FALLBACK_SCSV), got %v",
len(supportedCiphers)+1, len(c.TLS.Ciphers))
}
// Ensure ordering is correct
for i, actual := range c.TLS.Ciphers {
if actual != expectedCiphers[i] {
t.Errorf("Expected cipher in position %d to be %0x, got %0x", i, expectedCiphers[i], actual)
}
}
if !c.TLS.PreferServerCipherSuites {
t.Error("Expected PreferServerCipherSuites = true, but was false")
}
}
func TestTLSParseIncompleteParams(t *testing.T) {
c := NewTestController(`tls`)
_, err := TLS(c)
if err == nil {
t.Errorf("Expected errors (first check), but no error returned")
}
}
func TestTLSParseWithOptionalParams(t *testing.T) {
params := `tls cert.crt cert.key {
protocols ssl3.0 tls1.2
ciphers RSA-3DES-EDE-CBC-SHA RSA-AES256-CBC-SHA ECDHE-RSA-AES128-GCM-SHA256
}`
c := NewTestController(params)
_, err := TLS(c)
if err != nil {
t.Errorf("Expected no errors, got: %v", err)
}
if c.TLS.ProtocolMinVersion != tls.VersionSSL30 {
t.Errorf("Expected 'ssl3.0 (0x0300)' as ProtocolMinVersion, got %#v", c.TLS.ProtocolMinVersion)
}
if c.TLS.ProtocolMaxVersion != tls.VersionTLS12 {
t.Errorf("Expected 'tls1.2 (0x0302)' as ProtocolMaxVersion, got %#v", c.TLS.ProtocolMaxVersion)
}
if len(c.TLS.Ciphers)-1 != 3 {
t.Errorf("Expected 3 Ciphers (not including TLS_FALLBACK_SCSV), got %v", len(c.TLS.Ciphers))
}
}
func TestTLSParseWithWrongOptionalParams(t *testing.T) {
// Test protocols wrong params
params := `tls cert.crt cert.key {
protocols ssl tls
}`
c := NewTestController(params)
_, err := TLS(c)
if err == nil {
t.Errorf("Expected errors, but no error returned")
}
// Test ciphers wrong params
params = `tls cert.crt cert.key {
ciphers not-valid-cipher
}`
c = NewTestController(params)
_, err = TLS(c)
if err == nil {
t.Errorf("Expected errors, but no error returned")
}
}
func TestTLSParseWithClientAuth(t *testing.T) {
params := `tls cert.crt cert.key {
clients client_ca.crt client2_ca.crt
}`
c := NewTestController(params)
_, err := TLS(c)
if err != nil {
t.Errorf("Expected no errors, got: %v", err)
}
if count := len(c.TLS.ClientCerts); count != 2 {
t.Fatalf("Expected two client certs, had %d", count)
}
if actual := c.TLS.ClientCerts[0]; actual != "client_ca.crt" {
t.Errorf("Expected first client cert file to be '%s', but was '%s'", "client_ca.crt", actual)
}
if actual := c.TLS.ClientCerts[1]; actual != "client2_ca.crt" {
t.Errorf("Expected second client cert file to be '%s', but was '%s'", "client2_ca.crt", actual)
}
// Test missing client cert file
params = `tls cert.crt cert.key {
clients
}`
c = NewTestController(params)
_, err = TLS(c)
if err == nil {
t.Errorf("Expected an error, but no error returned")
}
}
-87
View File
@@ -1,87 +0,0 @@
package setup
import (
"github.com/mholt/caddy/middleware"
"github.com/mholt/caddy/middleware/websocket"
)
// WebSocket configures a new WebSocket middleware instance.
func WebSocket(c *Controller) (middleware.Middleware, error) {
websocks, err := webSocketParse(c)
if err != nil {
return nil, err
}
websocket.GatewayInterface = c.AppName + "-CGI/1.1"
websocket.ServerSoftware = c.AppName + "/" + c.AppVersion
return func(next middleware.Handler) middleware.Handler {
return websocket.WebSocket{Next: next, Sockets: websocks}
}, nil
}
func webSocketParse(c *Controller) ([]websocket.Config, error) {
var websocks []websocket.Config
var respawn bool
optionalBlock := func() (hadBlock bool, err error) {
for c.NextBlock() {
hadBlock = true
if c.Val() == "respawn" {
respawn = true
} else {
return true, c.Err("Expected websocket configuration parameter in block")
}
}
return
}
for c.Next() {
var val, path, command string
// Path or command; not sure which yet
if !c.NextArg() {
return nil, c.ArgErr()
}
val = c.Val()
// Extra configuration may be in a block
hadBlock, err := optionalBlock()
if err != nil {
return nil, err
}
if !hadBlock {
// The next argument on this line will be the command or an open curly brace
if c.NextArg() {
path = val
command = c.Val()
} else {
path = "/"
command = val
}
// Okay, check again for optional block
hadBlock, err = optionalBlock()
if err != nil {
return nil, err
}
}
// Split command into the actual command and its arguments
cmd, args, err := middleware.SplitCommandAndArgs(command)
if err != nil {
return nil, err
}
websocks = append(websocks, websocket.Config{
Path: path,
Command: cmd,
Arguments: args,
Respawn: respawn, // TODO: This isn't used currently
})
}
return websocks, nil
}
-63
View File
@@ -1,63 +0,0 @@
package caddy
import (
"log"
"os"
"os/signal"
"sync"
"github.com/mholt/caddy/server"
)
// TrapSignals create signal handlers for all applicable signals for this
// system. If your Go program uses signals, this is a rather invasive
// function; best to implement them yourself in that case. Signals are not
// required for the caddy package to function properly, but this is a
// convenient way to allow the user to control this package of your program.
func TrapSignals() {
trapSignalsCrossPlatform()
trapSignalsPosix()
}
// trapSignalsCrossPlatform captures SIGINT, which triggers forceful
// shutdown that executes shutdown callbacks first. A second interrupt
// signal will exit the process immediately.
func trapSignalsCrossPlatform() {
go func() {
shutdown := make(chan os.Signal, 1)
signal.Notify(shutdown, os.Interrupt)
for i := 0; true; i++ {
<-shutdown
if i > 0 {
log.Println("[INFO] SIGINT: Force quit")
os.Exit(1)
}
log.Println("[INFO] SIGINT: Shutting down")
go os.Exit(executeShutdownCallbacks("SIGINT"))
}
}()
}
// executeShutdownCallbacks executes the shutdown callbacks as initiated
// by signame. It logs any errors and returns the recommended exit status.
// This function is idempotent; subsequent invocations always return 0.
func executeShutdownCallbacks(signame string) (exitCode int) {
shutdownCallbacksOnce.Do(func() {
serversMu.Lock()
errs := server.ShutdownCallbacks(servers)
serversMu.Unlock()
if len(errs) > 0 {
for _, err := range errs {
log.Printf("[ERROR] %s shutdown: %v", signame, err)
}
exitCode = 1
}
})
return
}
var shutdownCallbacksOnce sync.Once
-73
View File
@@ -1,73 +0,0 @@
// +build !windows
package caddy
import (
"io/ioutil"
"log"
"os"
"os/signal"
"syscall"
)
// trapSignalsPosix captures POSIX-only signals.
func trapSignalsPosix() {
go func() {
sigchan := make(chan os.Signal, 1)
signal.Notify(sigchan, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGUSR1)
for sig := range sigchan {
switch sig {
case syscall.SIGTERM:
log.Println("[INFO] SIGTERM: Terminating process")
os.Exit(0)
case syscall.SIGQUIT:
log.Println("[INFO] SIGQUIT: Shutting down")
exitCode := executeShutdownCallbacks("SIGQUIT")
err := Stop()
if err != nil {
log.Printf("[ERROR] SIGQUIT stop: %v", err)
exitCode = 1
}
os.Exit(exitCode)
case syscall.SIGHUP:
log.Println("[INFO] SIGHUP: Hanging up")
err := Stop()
if err != nil {
log.Printf("[ERROR] SIGHUP stop: %v", err)
}
case syscall.SIGUSR1:
log.Println("[INFO] SIGUSR1: Reloading")
var updatedCaddyfile Input
caddyfileMu.Lock()
if caddyfile == nil {
// Hmm, did spawing process forget to close stdin? Anyhow, this is unusual.
log.Println("[ERROR] SIGUSR1: no Caddyfile to reload (was stdin left open?)")
caddyfileMu.Unlock()
continue
}
if caddyfile.IsFile() {
body, err := ioutil.ReadFile(caddyfile.Path())
if err == nil {
updatedCaddyfile = CaddyfileInput{
Filepath: caddyfile.Path(),
Contents: body,
RealFile: true,
}
}
}
caddyfileMu.Unlock()
err := Restart(updatedCaddyfile)
if err != nil {
log.Printf("[ERROR] SIGUSR1: %v", err)
}
}
}
}()
}
-3
View File
@@ -1,3 +0,0 @@
package caddy
func trapSignalsPosix() {}
+204
View File
@@ -0,0 +1,204 @@
// 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.
package caddy
import (
"fmt"
"reflect"
"sync"
"testing"
"github.com/mholt/caddy/caddyfile"
)
/*
// TODO
func TestCaddyStartStop(t *testing.T) {
caddyfile := "localhost:1984"
for i := 0; i < 2; i++ {
_, err := Start(CaddyfileInput{Contents: []byte(caddyfile)})
if err != nil {
t.Fatalf("Error starting, iteration %d: %v", i, err)
}
client := http.Client{
Timeout: time.Duration(2 * time.Second),
}
resp, err := client.Get("http://localhost:1984")
if err != nil {
t.Fatalf("Expected GET request to succeed (iteration %d), but it failed: %v", i, err)
}
resp.Body.Close()
err = Stop()
if err != nil {
t.Fatalf("Error stopping, iteration %d: %v", i, err)
}
}
}
*/
// 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
})
c.instance.Restart(CaddyfileInput{Contents: []byte(""), ServerTypeName: serverName})
if !reflect.DeepEqual(calls, test.expectedCalls) {
t.Errorf("Test %d: Callbacks expected: %v, got: %v", i, test.expectedCalls, calls)
}
c.instance.Stop()
c.instance.Wait()
}
}
func TestIsLoopback(t *testing.T) {
for i, test := range []struct {
input string
expect bool
}{
{"example.com", false},
{"localhost", true},
{"localhost:1234", true},
{"localhost:", true},
{"127.0.0.1", true},
{"127.0.0.1:443", true},
{"127.0.1.5", true},
{"10.0.0.5", false},
{"12.7.0.1", false},
{"[::1]", true},
{"[::1]:1234", true},
{"::1", true},
{"::", false},
{"[::]", false},
{"local", false},
} {
if got, want := IsLoopback(test.input), test.expect; got != want {
t.Errorf("Test %d (%s): expected %v but was %v", i, test.input, want, got)
}
}
}
func TestIsInternal(t *testing.T) {
for i, test := range []struct {
input string
expect bool
}{
{"9.255.255.255", false},
{"10.0.0.0", true},
{"10.0.0.1", true},
{"10.255.255.254", true},
{"10.255.255.255", true},
{"11.0.0.0", false},
{"10.0.0.5:1234", true},
{"11.0.0.5:1234", false},
{"172.15.255.255", false},
{"172.16.0.0", true},
{"172.16.0.1", true},
{"172.31.255.254", true},
{"172.31.255.255", true},
{"172.32.0.0", false},
{"172.16.0.1:1234", true},
{"192.167.255.255", false},
{"192.168.0.0", true},
{"192.168.0.1", true},
{"192.168.255.254", true},
{"192.168.255.255", true},
{"192.169.0.0", false},
{"192.168.0.1:1234", true},
{"fbff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", false},
{"fc00::", true},
{"fc00::1", true},
{"[fc00::1]", true},
{"[fc00::1]:8888", true},
{"fdff:ffff:ffff:ffff:ffff:ffff:ffff:fffe", true},
{"fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", true},
{"fe00::", false},
{"fd12:3456:789a:1::1:1234", true},
{"example.com", false},
{"localhost", false},
{"localhost:1234", false},
{"localhost:", false},
{"127.0.0.1", false},
{"127.0.0.1:443", false},
{"127.0.1.5", false},
{"12.7.0.1", false},
{"[::1]", false},
{"[::1]:1234", false},
{"::1", false},
{"::", false},
{"[::]", false},
{"local", false},
} {
if got, want := IsInternal(test.input), test.expect; got != want {
t.Errorf("Test %d (%s): expected %v but was %v", i, test.input, want, got)
}
}
}
@@ -1,4 +1,18 @@
package parse
// 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.
package caddyfile
import (
"errors"
@@ -12,22 +26,23 @@ import (
// some really convenient methods.
type Dispenser struct {
filename string
tokens []token
tokens []Token
cursor int
nesting int
}
// NewDispenser returns a Dispenser, ready to use for parsing the given input.
func NewDispenser(filename string, input io.Reader) Dispenser {
tokens, _ := allTokens(input) // ignoring error because nothing to do with it
return Dispenser{
filename: filename,
tokens: allTokens(input),
tokens: tokens,
cursor: -1,
}
}
// NewDispenserTokens returns a Dispenser filled with the given tokens.
func NewDispenserTokens(filename string, tokens []token) Dispenser {
func NewDispenserTokens(filename string, tokens []Token) Dispenser {
return Dispenser{
filename: filename,
tokens: tokens,
@@ -59,8 +74,8 @@ func (d *Dispenser) NextArg() bool {
return false
}
if d.cursor < len(d.tokens)-1 &&
d.tokens[d.cursor].file == d.tokens[d.cursor+1].file &&
d.tokens[d.cursor].line+d.numLineBreaks(d.cursor) == d.tokens[d.cursor+1].line {
d.tokens[d.cursor].File == d.tokens[d.cursor+1].File &&
d.tokens[d.cursor].Line+d.numLineBreaks(d.cursor) == d.tokens[d.cursor+1].Line {
d.cursor++
return true
}
@@ -80,8 +95,8 @@ func (d *Dispenser) NextLine() bool {
return false
}
if d.cursor < len(d.tokens)-1 &&
(d.tokens[d.cursor].file != d.tokens[d.cursor+1].file ||
d.tokens[d.cursor].line+d.numLineBreaks(d.cursor) < d.tokens[d.cursor+1].line) {
(d.tokens[d.cursor].File != d.tokens[d.cursor+1].File ||
d.tokens[d.cursor].Line+d.numLineBreaks(d.cursor) < d.tokens[d.cursor+1].Line) {
d.cursor++
return true
}
@@ -119,19 +134,13 @@ func (d *Dispenser) NextBlock() bool {
return true
}
// IncrNest adds a level of nesting to the dispenser.
func (d *Dispenser) IncrNest() {
d.nesting++
return
}
// Val gets the text of the current token. If there is no token
// loaded, it returns empty string.
func (d *Dispenser) Val() string {
if d.cursor < 0 || d.cursor >= len(d.tokens) {
return ""
}
return d.tokens[d.cursor].text
return d.tokens[d.cursor].Text
}
// Line gets the line number of the current token. If there is no token
@@ -140,7 +149,7 @@ func (d *Dispenser) Line() int {
if d.cursor < 0 || d.cursor >= len(d.tokens) {
return 0
}
return d.tokens[d.cursor].line
return d.tokens[d.cursor].Line
}
// File gets the filename of the current token. If there is no token loaded,
@@ -149,7 +158,7 @@ func (d *Dispenser) File() string {
if d.cursor < 0 || d.cursor >= len(d.tokens) {
return d.filename
}
if tokenFilename := d.tokens[d.cursor].file; tokenFilename != "" {
if tokenFilename := d.tokens[d.cursor].File; tokenFilename != "" {
return tokenFilename
}
return d.filename
@@ -215,9 +224,9 @@ func (d *Dispenser) EOFErr() error {
return d.Errf("Unexpected EOF")
}
// Err generates a custom parse error with a message of msg.
// Err generates a custom parse-time error with a message of msg.
func (d *Dispenser) Err(msg string) error {
msg = fmt.Sprintf("%s:%d - Parse error: %s", d.File(), d.Line(), msg)
msg = fmt.Sprintf("%s:%d - Error during parsing: %s", d.File(), d.Line(), msg)
return errors.New(msg)
}
@@ -233,7 +242,7 @@ func (d *Dispenser) numLineBreaks(tknIdx int) int {
if tknIdx < 0 || tknIdx >= len(d.tokens) {
return 0
}
return strings.Count(d.tokens[tknIdx].text, "\n")
return strings.Count(d.tokens[tknIdx].Text, "\n")
}
// isNewLine determines whether the current token is on a different
@@ -246,6 +255,6 @@ func (d *Dispenser) isNewLine() bool {
if d.cursor > len(d.tokens)-1 {
return false
}
return d.tokens[d.cursor-1].file != d.tokens[d.cursor].file ||
d.tokens[d.cursor-1].line+d.numLineBreaks(d.cursor-1) < d.tokens[d.cursor].line
return d.tokens[d.cursor-1].File != d.tokens[d.cursor].File ||
d.tokens[d.cursor-1].Line+d.numLineBreaks(d.cursor-1) < d.tokens[d.cursor].Line
}
@@ -1,4 +1,18 @@
package parse
// 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.
package caddyfile
import (
"reflect"
@@ -64,7 +78,7 @@ func TestDispenser_NextArg(t *testing.T) {
}
assertNextArg := func(expectedVal string, loadAnother bool, expectedCursor int) {
if d.NextArg() != true {
if !d.NextArg() {
t.Error("NextArg(): Should load next argument but got false instead")
}
if d.cursor != expectedCursor {
@@ -74,7 +88,7 @@ func TestDispenser_NextArg(t *testing.T) {
t.Errorf("Val(): Expected '%s' but got '%s'", expectedVal, val)
}
if !loadAnother {
if d.NextArg() != false {
if d.NextArg() {
t.Fatalf("NextArg(): Should NOT load another argument, but got true instead (val: '%s')", d.Val())
}
if d.cursor != expectedCursor {
+49 -31
View File
@@ -1,34 +1,43 @@
// 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.
package caddyfile
import (
"bytes"
"encoding/json"
"fmt"
"net"
"sort"
"strconv"
"strings"
"github.com/mholt/caddy/caddy/parse"
)
const filename = "Caddyfile"
// ToJSON converts caddyfile to its JSON representation.
func ToJSON(caddyfile []byte) ([]byte, error) {
var j Caddyfile
var j EncodedCaddyfile
serverBlocks, err := parse.ServerBlocks(filename, bytes.NewReader(caddyfile), false)
serverBlocks, err := Parse(filename, bytes.NewReader(caddyfile), nil)
if err != nil {
return nil, err
}
for _, sb := range serverBlocks {
block := ServerBlock{Body: [][]interface{}{}}
// Fill up host list
for _, host := range sb.HostList() {
block.Hosts = append(block.Hosts, strings.TrimSuffix(host, ":"))
block := EncodedServerBlock{
Keys: sb.Keys,
Body: [][]interface{}{},
}
// Extract directives deterministically by sorting them
@@ -40,7 +49,7 @@ func ToJSON(caddyfile []byte) ([]byte, error) {
// Convert each directive's tokens into our JSON structure
for _, dir := range directives {
disp := parse.NewDispenserTokens(filename, sb.Tokens[dir])
disp := NewDispenserTokens(filename, sb.Tokens[dir])
for disp.Next() {
block.Body = append(block.Body, constructLine(&disp))
}
@@ -62,8 +71,7 @@ func ToJSON(caddyfile []byte) ([]byte, error) {
// but only one line at a time, to be used at the top-level of
// a server block only (where the first token on each line is a
// directive) - not to be used at any other nesting level.
// goes to end of line
func constructLine(d *parse.Dispenser) []interface{} {
func constructLine(d *Dispenser) []interface{} {
var args []interface{}
args = append(args, d.Val())
@@ -80,9 +88,9 @@ func constructLine(d *parse.Dispenser) []interface{} {
}
// constructBlock recursively processes tokens into a
// JSON-encodable structure.
// goes to end of block
func constructBlock(d *parse.Dispenser) [][]interface{} {
// JSON-encodable structure. To be used in a directive's
// block. Goes to end of block.
func constructBlock(d *Dispenser) [][]interface{} {
block := [][]interface{}{}
for d.Next() {
@@ -97,7 +105,7 @@ func constructBlock(d *parse.Dispenser) [][]interface{} {
// FromJSON converts JSON-encoded jsonBytes to Caddyfile text
func FromJSON(jsonBytes []byte) ([]byte, error) {
var j Caddyfile
var j EncodedCaddyfile
var result string
err := json.Unmarshal(jsonBytes, &j)
@@ -109,16 +117,12 @@ func FromJSON(jsonBytes []byte) ([]byte, error) {
if sbPos > 0 {
result += "\n\n"
}
for i, host := range sb.Hosts {
if hostname, port, err := net.SplitHostPort(host); err == nil {
if port == "http" || port == "https" {
host = port + "://" + hostname
}
}
for i, key := range sb.Keys {
if i > 0 {
result += ", "
}
result += strings.TrimSuffix(host, ":")
//result += standardizeScheme(key)
result += key
}
result += jsonToText(sb.Body, 1)
}
@@ -170,11 +174,25 @@ func jsonToText(scope interface{}, depth int) string {
return result
}
// Caddyfile encapsulates a slice of ServerBlocks.
type Caddyfile []ServerBlock
// ServerBlock represents a server block.
type ServerBlock struct {
Hosts []string `json:"hosts"`
Body [][]interface{} `json:"body"`
// TODO: Will this function come in handy somewhere else?
/*
// standardizeScheme turns an address like host:https into https://host,
// or "host:" into "host".
func standardizeScheme(addr string) string {
if hostname, port, err := net.SplitHostPort(addr); err == nil {
if port == "http" || port == "https" {
addr = port + "://" + hostname
}
}
return strings.TrimSuffix(addr, ":")
}
*/
// EncodedCaddyfile encapsulates a slice of EncodedServerBlocks.
type EncodedCaddyfile []EncodedServerBlock
// EncodedServerBlock represents a server block ripe for encoding.
type EncodedServerBlock struct {
Keys []string `json:"keys"`
Body [][]interface{} `json:"body"`
}
+178
View File
@@ -0,0 +1,178 @@
// 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.
package caddyfile
import "testing"
var tests = []struct {
caddyfile, json string
}{
{ // 0
caddyfile: `foo {
root /bar
}`,
json: `[{"keys":["foo"],"body":[["root","/bar"]]}]`,
},
{ // 1
caddyfile: `host1, host2 {
dir {
def
}
}`,
json: `[{"keys":["host1","host2"],"body":[["dir",[["def"]]]]}]`,
},
{ // 2
caddyfile: `host1, host2 {
dir abc {
def ghi
jkl
}
}`,
json: `[{"keys":["host1","host2"],"body":[["dir","abc",[["def","ghi"],["jkl"]]]]}]`,
},
{ // 3
caddyfile: `host1:1234, host2:5678 {
dir abc {
}
}`,
json: `[{"keys":["host1:1234","host2:5678"],"body":[["dir","abc",[]]]}]`,
},
{ // 4
caddyfile: `host {
foo "bar baz"
}`,
json: `[{"keys":["host"],"body":[["foo","bar baz"]]}]`,
},
{ // 5
caddyfile: `host, host:80 {
foo "bar \"baz\""
}`,
json: `[{"keys":["host","host:80"],"body":[["foo","bar \"baz\""]]}]`,
},
{ // 6
caddyfile: `host {
foo "bar
baz"
}`,
json: `[{"keys":["host"],"body":[["foo","bar\nbaz"]]}]`,
},
{ // 7
caddyfile: `host {
dir 123 4.56 true
}`,
json: `[{"keys":["host"],"body":[["dir","123","4.56","true"]]}]`, // NOTE: I guess we assume numbers and booleans should be encoded as strings...?
},
{ // 8
caddyfile: `http://host, https://host {
}`,
json: `[{"keys":["http://host","https://host"],"body":[]}]`, // hosts in JSON are always host:port format (if port is specified), for consistency
},
{ // 9
caddyfile: `host {
dir1 a b
dir2 c d
}`,
json: `[{"keys":["host"],"body":[["dir1","a","b"],["dir2","c","d"]]}]`,
},
{ // 10
caddyfile: `host {
dir a b
dir c d
}`,
json: `[{"keys":["host"],"body":[["dir","a","b"],["dir","c","d"]]}]`,
},
{ // 11
caddyfile: `host {
dir1 a b
dir2 {
c
d
}
}`,
json: `[{"keys":["host"],"body":[["dir1","a","b"],["dir2",[["c"],["d"]]]]}]`,
},
{ // 12
caddyfile: `host1 {
dir1
}
host2 {
dir2
}`,
json: `[{"keys":["host1"],"body":[["dir1"]]},{"keys":["host2"],"body":[["dir2"]]}]`,
},
}
func TestToJSON(t *testing.T) {
for i, test := range tests {
output, err := ToJSON([]byte(test.caddyfile))
if err != nil {
t.Errorf("Test %d: %v", i, err)
}
if string(output) != test.json {
t.Errorf("Test %d\nExpected:\n'%s'\nActual:\n'%s'", i, test.json, string(output))
}
}
}
func TestFromJSON(t *testing.T) {
for i, test := range tests {
output, err := FromJSON([]byte(test.json))
if err != nil {
t.Errorf("Test %d: %v", i, err)
}
if string(output) != test.caddyfile {
t.Errorf("Test %d\nExpected:\n'%s'\nActual:\n'%s'", i, test.caddyfile, string(output))
}
}
}
// TODO: Will these tests come in handy somewhere else?
/*
func TestStandardizeAddress(t *testing.T) {
// host:https should be converted to https://host
output, err := ToJSON([]byte(`host:https`))
if err != nil {
t.Fatal(err)
}
if expected, actual := `[{"keys":["https://host"],"body":[]}]`, string(output); expected != actual {
t.Errorf("Expected:\n'%s'\nActual:\n'%s'", expected, actual)
}
output, err = FromJSON([]byte(`[{"keys":["https://host"],"body":[]}]`))
if err != nil {
t.Fatal(err)
}
if expected, actual := "https://host {\n}", string(output); expected != actual {
t.Errorf("Expected:\n'%s'\nActual:\n'%s'", expected, actual)
}
// host: should be converted to just host
output, err = ToJSON([]byte(`host:`))
if err != nil {
t.Fatal(err)
}
if expected, actual := `[{"keys":["host"],"body":[]}]`, string(output); expected != actual {
t.Errorf("Expected:\n'%s'\nActual:\n'%s'", expected, actual)
}
output, err = FromJSON([]byte(`[{"keys":["host:"],"body":[]}]`))
if err != nil {
t.Fatal(err)
}
if expected, actual := "host {\n}", string(output); expected != actual {
t.Errorf("Expected:\n'%s'\nActual:\n'%s'", expected, actual)
}
}
*/
+37 -9
View File
@@ -1,4 +1,18 @@
package parse
// 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.
package caddyfile
import (
"bufio"
@@ -13,22 +27,36 @@ type (
// in quotes if it contains whitespace.
lexer struct {
reader *bufio.Reader
token token
token Token
line int
}
// token represents a single parsable unit.
token struct {
file string
line int
text string
// Token represents a single parsable unit.
Token struct {
File string
Line int
Text string
}
)
// load prepares the lexer to scan an input for tokens.
// It discards any leading byte order mark.
func (l *lexer) load(input io.Reader) error {
l.reader = bufio.NewReader(input)
l.line = 1
// discard byte order mark, if present
firstCh, _, err := l.reader.ReadRune()
if err != nil {
return err
}
if firstCh != 0xFEFF {
err := l.reader.UnreadRune()
if err != nil {
return err
}
}
return nil
}
@@ -47,7 +75,7 @@ func (l *lexer) next() bool {
var comment, quoted, escaped bool
makeToken := func() bool {
l.token.text = string(val)
l.token.Text = string(val)
return true
}
@@ -110,7 +138,7 @@ func (l *lexer) next() bool {
}
if len(val) == 0 {
l.token = token{line: l.line}
l.token = Token{Line: l.line}
if ch == '"' {
quoted = true
continue
+185
View File
@@ -0,0 +1,185 @@
// 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.
package caddyfile
import (
"strings"
"testing"
)
type lexerTestCase struct {
input string
expected []Token
}
func TestLexer(t *testing.T) {
testCases := []lexerTestCase{
{
input: `host:123`,
expected: []Token{
{Line: 1, Text: "host:123"},
},
},
{
input: `host:123
directive`,
expected: []Token{
{Line: 1, Text: "host:123"},
{Line: 3, Text: "directive"},
},
},
{
input: `host:123 {
directive
}`,
expected: []Token{
{Line: 1, Text: "host:123"},
{Line: 1, Text: "{"},
{Line: 2, Text: "directive"},
{Line: 3, Text: "}"},
},
},
{
input: `host:123 { directive }`,
expected: []Token{
{Line: 1, Text: "host:123"},
{Line: 1, Text: "{"},
{Line: 1, Text: "directive"},
{Line: 1, Text: "}"},
},
},
{
input: `host:123 {
#comment
directive
# comment
foobar # another comment
}`,
expected: []Token{
{Line: 1, Text: "host:123"},
{Line: 1, Text: "{"},
{Line: 3, Text: "directive"},
{Line: 5, Text: "foobar"},
{Line: 6, Text: "}"},
},
},
{
input: `a "quoted value" b
foobar`,
expected: []Token{
{Line: 1, Text: "a"},
{Line: 1, Text: "quoted value"},
{Line: 1, Text: "b"},
{Line: 2, Text: "foobar"},
},
},
{
input: `A "quoted \"value\" inside" B`,
expected: []Token{
{Line: 1, Text: "A"},
{Line: 1, Text: `quoted "value" inside`},
{Line: 1, Text: "B"},
},
},
{
input: `"don't\escape"`,
expected: []Token{
{Line: 1, Text: `don't\escape`},
},
},
{
input: `"don't\\escape"`,
expected: []Token{
{Line: 1, Text: `don't\\escape`},
},
},
{
input: `A "quoted value with line
break inside" {
foobar
}`,
expected: []Token{
{Line: 1, Text: "A"},
{Line: 1, Text: "quoted value with line\n\t\t\t\t\tbreak inside"},
{Line: 2, Text: "{"},
{Line: 3, Text: "foobar"},
{Line: 4, Text: "}"},
},
},
{
input: `"C:\php\php-cgi.exe"`,
expected: []Token{
{Line: 1, Text: `C:\php\php-cgi.exe`},
},
},
{
input: `empty "" string`,
expected: []Token{
{Line: 1, Text: `empty`},
{Line: 1, Text: ``},
{Line: 1, Text: `string`},
},
},
{
input: "skip those\r\nCR characters",
expected: []Token{
{Line: 1, Text: "skip"},
{Line: 1, Text: "those"},
{Line: 2, Text: "CR"},
{Line: 2, Text: "characters"},
},
},
{
input: "\xEF\xBB\xBF:8080", // test with leading byte order mark
expected: []Token{
{Line: 1, Text: ":8080"},
},
},
}
for i, testCase := range testCases {
actual := tokenize(testCase.input)
lexerCompare(t, i, testCase.expected, actual)
}
}
func tokenize(input string) (tokens []Token) {
l := lexer{}
l.load(strings.NewReader(input))
for l.next() {
tokens = append(tokens, l.token)
}
return
}
func lexerCompare(t *testing.T, n int, expected, actual []Token) {
if len(expected) != len(actual) {
t.Errorf("Test case %d: expected %d token(s) but got %d", n, len(expected), len(actual))
}
for i := 0; i < len(actual) && i < len(expected); i++ {
if actual[i].Line != expected[i].Line {
t.Errorf("Test case %d token %d ('%s'): expected line %d but was line %d",
n, i, expected[i].Text, expected[i].Line, actual[i].Line)
break
}
if actual[i].Text != expected[i].Text {
t.Errorf("Test case %d token %d: expected text '%s' but was '%s'",
n, i, expected[i].Text, actual[i].Text)
break
}
}
}
+493
View File
@@ -0,0 +1,493 @@
// 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.
package caddyfile
import (
"io"
"log"
"os"
"path/filepath"
"strings"
"github.com/mholt/caddy/telemetry"
)
// Parse parses the input just enough to group tokens, in
// order, by server block. No further parsing is performed.
// Server blocks are returned in the order in which they appear.
// Directives that do not appear in validDirectives will cause
// an error. If you do not want to check for valid directives,
// pass in nil instead.
func Parse(filename string, input io.Reader, validDirectives []string) ([]ServerBlock, error) {
p := parser{Dispenser: NewDispenser(filename, input), validDirectives: validDirectives}
return p.parseAll()
}
// allTokens lexes the entire input, but does not parse it.
// It returns all the tokens from the input, unstructured
// and in order.
func allTokens(input io.Reader) ([]Token, error) {
l := new(lexer)
err := l.load(input)
if err != nil {
return nil, err
}
var tokens []Token
for l.next() {
tokens = append(tokens, l.token)
}
return tokens, nil
}
type parser struct {
Dispenser
block ServerBlock // current server block being parsed
validDirectives []string // a directive must be valid or it's an error
eof bool // if we encounter a valid EOF in a hard place
definedSnippets map[string][]Token
}
func (p *parser) parseAll() ([]ServerBlock, error) {
var blocks []ServerBlock
for p.Next() {
err := p.parseOne()
if err != nil {
return blocks, err
}
if len(p.block.Keys) > 0 {
blocks = append(blocks, p.block)
}
}
return blocks, nil
}
func (p *parser) parseOne() error {
p.block = ServerBlock{Tokens: make(map[string][]Token)}
return p.begin()
}
func (p *parser) begin() error {
if len(p.tokens) == 0 {
return nil
}
err := p.addresses()
if err != nil {
return err
}
if p.eof {
// this happens if the Caddyfile consists of only
// a line of addresses and nothing else
return nil
}
if ok, name := p.isSnippet(); ok {
if p.definedSnippets == nil {
p.definedSnippets = map[string][]Token{}
}
if _, found := p.definedSnippets[name]; found {
return p.Errf("redeclaration of previously declared snippet %s", name)
}
// consume all tokens til matched close brace
tokens, err := p.snippetTokens()
if err != nil {
return err
}
p.definedSnippets[name] = tokens
// empty block keys so we don't save this block as a real server.
p.block.Keys = nil
return nil
}
return p.blockContents()
}
func (p *parser) addresses() error {
var expectingAnother bool
for {
tkn := replaceEnvVars(p.Val())
// special case: import directive replaces tokens during parse-time
if tkn == "import" && p.isNewLine() {
err := p.doImport()
if err != nil {
return err
}
continue
}
// Open brace definitely indicates end of addresses
if tkn == "{" {
if expectingAnother {
return p.Errf("Expected another address but had '%s' - check for extra comma", tkn)
}
break
}
if tkn != "" { // empty token possible if user typed ""
// Trailing comma indicates another address will follow, which
// may possibly be on the next line
if tkn[len(tkn)-1] == ',' {
tkn = tkn[:len(tkn)-1]
expectingAnother = true
} else {
expectingAnother = false // but we may still see another one on this line
}
p.block.Keys = append(p.block.Keys, tkn)
}
// Advance token and possibly break out of loop or return error
hasNext := p.Next()
if expectingAnother && !hasNext {
return p.EOFErr()
}
if !hasNext {
p.eof = true
break // EOF
}
if !expectingAnother && p.isNewLine() {
break
}
}
return nil
}
func (p *parser) blockContents() error {
errOpenCurlyBrace := p.openCurlyBrace()
if errOpenCurlyBrace != nil {
// single-server configs don't need curly braces
p.cursor--
}
err := p.directives()
if err != nil {
return err
}
// Only look for close curly brace if there was an opening
if errOpenCurlyBrace == nil {
err = p.closeCurlyBrace()
if err != nil {
return err
}
}
return nil
}
// directives parses through all the lines for directives
// and it expects the next token to be the first
// directive. It goes until EOF or closing curly brace
// which ends the server block.
func (p *parser) directives() error {
for p.Next() {
// end of server block
if p.Val() == "}" {
break
}
// special case: import directive replaces tokens during parse-time
if p.Val() == "import" {
err := p.doImport()
if err != nil {
return err
}
p.cursor-- // cursor is advanced when we continue, so roll back one more
continue
}
// normal case: parse a directive on this line
if err := p.directive(); err != nil {
return err
}
}
return nil
}
// doImport swaps out the import directive and its argument
// (a total of 2 tokens) with the tokens in the specified file
// or globbing pattern. When the function returns, the cursor
// is on the token before where the import directive was. In
// other words, call Next() to access the first token that was
// imported.
func (p *parser) doImport() error {
// syntax checks
if !p.NextArg() {
return p.ArgErr()
}
importPattern := replaceEnvVars(p.Val())
if importPattern == "" {
return p.Err("Import requires a non-empty filepath")
}
if p.NextArg() {
return p.Err("Import takes only one argument (glob pattern or file)")
}
// splice out the import directive and its argument (2 tokens total)
tokensBefore := p.tokens[:p.cursor-1]
tokensAfter := p.tokens[p.cursor+1:]
var importedTokens []Token
// first check snippets. That is a simple, non-recursive replacement
if p.definedSnippets != nil && p.definedSnippets[importPattern] != nil {
importedTokens = p.definedSnippets[importPattern]
} else {
// 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)
}
var matches []string
var globPattern string
if !filepath.IsAbs(importPattern) {
globPattern = filepath.Join(filepath.Dir(absFile), importPattern)
} 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.ContainsAny(globPattern, "*?[]") {
log.Printf("[WARNING] No files matching import glob pattern: %s", importPattern)
} else {
return p.Errf("File to import not found: %s", importPattern)
}
}
// collect all the imported tokens
for _, importFile := range matches {
newTokens, err := p.doSingleImport(importFile)
if err != nil {
return err
}
importedTokens = append(importedTokens, newTokens...)
}
}
// splice the imported tokens in the place of the import statement
// and rewind cursor so Next() will land on first imported token
p.tokens = append(tokensBefore, append(importedTokens, tokensAfter...)...)
p.cursor--
return nil
}
// doSingleImport lexes the individual file at importFile and returns
// its tokens or an error, if any.
func (p *parser) doSingleImport(importFile string) ([]Token, error) {
file, err := os.Open(importFile)
if err != nil {
return nil, p.Errf("Could not import %s: %v", importFile, err)
}
defer file.Close()
if info, err := file.Stat(); err != nil {
return nil, p.Errf("Could not import %s: %v", importFile, err)
} else if info.IsDir() {
return nil, p.Errf("Could not import %s: is a directory", importFile)
}
importedTokens, err := allTokens(file)
if err != nil {
return nil, p.Errf("Could not read tokens while importing %s: %v", importFile, err)
}
// Tack the file path onto these tokens so errors show the imported file's name
// (we use full, absolute path to avoid bugs: issue #1892)
filename, err := filepath.Abs(importFile)
if err != nil {
return nil, p.Errf("Failed to get absolute path of file: %s: %v", p.Dispenser.filename, err)
}
for i := 0; i < len(importedTokens); i++ {
importedTokens[i].File = filename
}
return importedTokens, nil
}
// directive collects tokens until the directive's scope
// closes (either end of line or end of curly brace block).
// It expects the currently-loaded token to be a directive
// (or } that ends a server block). The collected tokens
// are loaded into the current server block for later use
// by directive setup functions.
func (p *parser) directive() error {
dir := replaceEnvVars(p.Val())
nesting := 0
// TODO: More helpful error message ("did you mean..." or "maybe you need to install its server type")
if !p.validDirective(dir) {
return p.Errf("Unknown directive '%s'", dir)
}
// The directive itself is appended as a relevant token
p.block.Tokens[dir] = append(p.block.Tokens[dir], p.tokens[p.cursor])
telemetry.AppendUnique("directives", dir)
for p.Next() {
if p.Val() == "{" {
nesting++
} else if p.isNewLine() && nesting == 0 {
p.cursor-- // read too far
break
} else if p.Val() == "}" && nesting > 0 {
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])
}
if nesting > 0 {
return p.EOFErr()
}
return nil
}
// openCurlyBrace expects the current token to be an
// opening curly brace. This acts like an assertion
// because it returns an error if the token is not
// a opening curly brace. It does NOT advance the token.
func (p *parser) openCurlyBrace() error {
if p.Val() != "{" {
return p.SyntaxErr("{")
}
return nil
}
// closeCurlyBrace expects the current token to be
// a closing curly brace. This acts like an assertion
// because it returns an error if the token is not
// a closing curly brace. It does NOT advance the token.
func (p *parser) closeCurlyBrace() error {
if p.Val() != "}" {
return p.SyntaxErr("}")
}
return nil
}
// validDirective returns true if dir is in p.validDirectives.
func (p *parser) validDirective(dir string) bool {
if p.validDirectives == nil {
return true
}
for _, d := range p.validDirectives {
if d == dir {
return true
}
}
return false
}
// replaceEnvVars replaces environment variables that appear in the token
// and understands both the $UNIX and %WINDOWS% syntaxes.
func replaceEnvVars(s string) string {
s = replaceEnvReferences(s, "{%", "%}")
s = replaceEnvReferences(s, "{$", "}")
return s
}
// replaceEnvReferences performs the actual replacement of env variables
// in s, given the placeholder start and placeholder end strings.
func replaceEnvReferences(s, refStart, refEnd string) string {
index := strings.Index(s, refStart)
for index != -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 {
return s
}
index = strings.Index(s, refStart)
}
return s
}
// ServerBlock associates any number of keys (usually addresses
// of some sort) with tokens (grouped by directive name).
type ServerBlock struct {
Keys []string
Tokens map[string][]Token
}
func (p *parser) isSnippet() (bool, string) {
keys := p.block.Keys
// A snippet block is a single key with parens. Nothing else qualifies.
if len(keys) == 1 && strings.HasPrefix(keys[0], "(") && strings.HasSuffix(keys[0], ")") {
return true, strings.TrimSuffix(keys[0][1:], ")")
}
return false, ""
}
// read and store everything in a block for later replay.
func (p *parser) snippetTokens() ([]Token, error) {
// TODO: disallow imports in snippets for simplicity at import time
// snippet must have curlies.
err := p.openCurlyBrace()
if err != nil {
return nil, err
}
count := 1
tokens := []Token{}
for p.Next() {
if p.Val() == "}" {
count--
if count == 0 {
break
}
}
if p.Val() == "{" {
count++
}
tokens = append(tokens, p.tokens[p.cursor])
}
// make sure we're matched up
if count != 0 {
return nil, p.SyntaxErr("}")
}
return tokens, nil
}
+720
View File
@@ -0,0 +1,720 @@
// 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.
package caddyfile
import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
)
func TestAllTokens(t *testing.T) {
input := strings.NewReader("a b c\nd e")
expected := []string{"a", "b", "c", "d", "e"}
tokens, err := allTokens(input)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if len(tokens) != len(expected) {
t.Fatalf("Expected %d tokens, got %d", len(expected), len(tokens))
}
for i, val := range expected {
if tokens[i].Text != val {
t.Errorf("Token %d should be '%s' but was '%s'", i, val, tokens[i].Text)
}
}
}
func TestParseOneAndImport(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
}
for i, test := range []struct {
input string
shouldErr bool
keys []string
tokens map[string]int // map of directive name to number of tokens expected
}{
{`localhost`, false, []string{
"localhost",
}, map[string]int{}},
{`localhost
dir1`, false, []string{
"localhost",
}, map[string]int{
"dir1": 1,
}},
{`localhost:1234
dir1 foo bar`, false, []string{
"localhost:1234",
}, map[string]int{
"dir1": 3,
}},
{`localhost {
dir1
}`, false, []string{
"localhost",
}, map[string]int{
"dir1": 1,
}},
{`localhost:1234 {
dir1 foo bar
dir2
}`, false, []string{
"localhost:1234",
}, map[string]int{
"dir1": 3,
"dir2": 1,
}},
{`http://localhost https://localhost
dir1 foo bar`, false, []string{
"http://localhost",
"https://localhost",
}, map[string]int{
"dir1": 3,
}},
{`http://localhost https://localhost {
dir1 foo bar
}`, false, []string{
"http://localhost",
"https://localhost",
}, map[string]int{
"dir1": 3,
}},
{`http://localhost, https://localhost {
dir1 foo bar
}`, false, []string{
"http://localhost",
"https://localhost",
}, map[string]int{
"dir1": 3,
}},
{`http://localhost, {
}`, true, []string{
"http://localhost",
}, map[string]int{}},
{`host1:80, http://host2.com
dir1 foo bar
dir2 baz`, false, []string{
"host1:80",
"http://host2.com",
}, map[string]int{
"dir1": 3,
"dir2": 2,
}},
{`http://host1.com,
http://host2.com,
https://host3.com`, false, []string{
"http://host1.com",
"http://host2.com",
"https://host3.com",
}, map[string]int{}},
{`http://host1.com:1234, https://host2.com
dir1 foo {
bar baz
}
dir2`, false, []string{
"http://host1.com:1234",
"https://host2.com",
}, map[string]int{
"dir1": 6,
"dir2": 1,
}},
{`127.0.0.1
dir1 {
bar baz
}
dir2 {
foo bar
}`, false, []string{
"127.0.0.1",
}, map[string]int{
"dir1": 5,
"dir2": 5,
}},
{`localhost
dir1 {
foo`, true, []string{
"localhost",
}, map[string]int{
"dir1": 3,
}},
{`localhost
dir1 {
}`, false, []string{
"localhost",
}, map[string]int{
"dir1": 3,
}},
{`localhost
dir1 {
} }`, true, []string{
"localhost",
}, map[string]int{
"dir1": 3,
}},
{`localhost
dir1 {
nested {
foo
}
}
dir2 foo bar`, false, []string{
"localhost",
}, map[string]int{
"dir1": 7,
"dir2": 3,
}},
{``, false, []string{}, map[string]int{}},
{`localhost
dir1 arg1
import testdata/import_test1.txt`, false, []string{
"localhost",
}, map[string]int{
"dir1": 2,
"dir2": 3,
"dir3": 1,
}},
{`import testdata/import_test2.txt`, false, []string{
"host1",
}, map[string]int{
"dir1": 1,
"dir2": 2,
}},
{`import testdata/import_test1.txt testdata/import_test2.txt`, true, []string{}, map[string]int{}},
{`import testdata/not_found.txt`, true, []string{}, map[string]int{}},
{`""`, 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)
if test.shouldErr && err == nil {
t.Errorf("Test %d: Expected an error, but didn't get one", i)
}
if !test.shouldErr && err != nil {
t.Errorf("Test %d: Expected no error, but got: %v", i, err)
}
if len(result.Keys) != len(test.keys) {
t.Errorf("Test %d: Expected %d keys, got %d",
i, len(test.keys), len(result.Keys))
continue
}
for j, addr := range result.Keys {
if addr != test.keys[j] {
t.Errorf("Test %d, key %d: Expected '%s', but was '%s'",
i, j, test.keys[j], addr)
}
}
if len(result.Tokens) != len(test.tokens) {
t.Errorf("Test %d: Expected %d directives, had %d",
i, len(test.tokens), len(result.Tokens))
continue
}
for directive, tokens := range result.Tokens {
if len(tokens) != test.tokens[directive] {
t.Errorf("Test %d, directive '%s': Expected %d tokens, counted %d",
i, directive, test.tokens[directive], len(tokens))
continue
}
}
}
}
func TestRecursiveImport(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["dir2"]) != 2 {
t.Errorf("got unexpect tokens: %v", got.Tokens)
return false
}
return true
}
recursiveFile1, err := filepath.Abs("testdata/recursive_import_test1")
if err != nil {
t.Fatal(err)
}
recursiveFile2, err := filepath.Abs("testdata/recursive_import_test2")
if err != nil {
t.Fatal(err)
}
// test relative recursive import
err = ioutil.WriteFile(recursiveFile1, []byte(
`localhost
dir1
import recursive_import_test2`), 0644)
if err != nil {
t.Fatal(err)
}
defer os.Remove(recursiveFile1)
err = ioutil.WriteFile(recursiveFile2, []byte("dir2 1"), 0644)
if err != nil {
t.Fatal(err)
}
defer os.Remove(recursiveFile2)
// import absolute path
result, err := testParseOne("import " + recursiveFile1)
if err != nil {
t.Fatal(err)
}
if !isExpected(result) {
t.Error("absolute+relative import failed")
}
// import relative path
result, err = testParseOne("import testdata/recursive_import_test1")
if err != nil {
t.Fatal(err)
}
if !isExpected(result) {
t.Error("relative+relative import failed")
}
// test absolute recursive import
err = ioutil.WriteFile(recursiveFile1, []byte(
`localhost
dir1
import `+recursiveFile2), 0644)
if err != nil {
t.Fatal(err)
}
// import absolute path
result, err = testParseOne("import " + recursiveFile1)
if err != nil {
t.Fatal(err)
}
if !isExpected(result) {
t.Error("absolute+absolute import failed")
}
// import relative path
result, err = testParseOne("import testdata/recursive_import_test1")
if err != nil {
t.Fatal(err)
}
if !isExpected(result) {
t.Error("relative+absolute import failed")
}
}
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
shouldErr bool
keys [][]string // keys per server block, in order
}{
{`localhost`, false, [][]string{
{"localhost"},
}},
{`localhost:1234`, false, [][]string{
{"localhost:1234"},
}},
{`localhost:1234 {
}
localhost:2015 {
}`, false, [][]string{
{"localhost:1234"},
{"localhost:2015"},
}},
{`localhost:1234, http://host2`, false, [][]string{
{"localhost:1234", "http://host2"},
}},
{`localhost:1234, http://host2,`, true, [][]string{}},
{`http://host1.com, http://host2.com {
}
https://host3.com, https://host4.com {
}`, false, [][]string{
{"http://host1.com", "http://host2.com"},
{"https://host3.com", "https://host4.com"},
}},
{`import testdata/import_glob*.txt`, false, [][]string{
{"glob0.host0"},
{"glob0.host1"},
{"glob1.host0"},
{"glob2.host0"},
}},
{`import notfound/*`, false, [][]string{}}, // glob needn't error with no matches
{`import notfound/file.conf`, true, [][]string{}}, // but a specific file should
} {
p := testParser(test.input)
blocks, err := p.parseAll()
if test.shouldErr && err == nil {
t.Errorf("Test %d: Expected an error, but didn't get one", i)
}
if !test.shouldErr && err != nil {
t.Errorf("Test %d: Expected no error, but got: %v", i, err)
}
if len(blocks) != len(test.keys) {
t.Errorf("Test %d: Expected %d server blocks, got %d",
i, len(test.keys), len(blocks))
continue
}
for j, block := range blocks {
if len(block.Keys) != len(test.keys[j]) {
t.Errorf("Test %d: Expected %d keys in block %d, got %d",
i, len(test.keys[j]), j, len(block.Keys))
continue
}
for k, addr := range block.Keys {
if addr != test.keys[j][k] {
t.Errorf("Test %d, block %d, key %d: Expected '%s', but got '%s'",
i, j, k, test.keys[j][k], addr)
}
}
}
}
}
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}`)
blocks, _ := p.parseAll()
if actual, expected := blocks[0].Keys[0], "servername.com"; expected != actual {
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()
if actual, expected := blocks[0].Keys[0], "servername.com:8080"; expected != actual {
t.Errorf("Expected key to be '%s' but was '%s'", expected, actual)
}
// windows-style var and unix style in same token
p = testParser(`{%ADDRESS%}:{$PORT}`)
blocks, _ = p.parseAll()
if actual, expected := blocks[0].Keys[0], "servername.com:8080"; expected != actual {
t.Errorf("Expected key to be '%s' but was '%s'", expected, actual)
}
// reverse order
p = testParser(`{$ADDRESS}:{%PORT%}`)
blocks, _ = p.parseAll()
if actual, expected := blocks[0].Keys[0], "servername.com:8080"; expected != actual {
t.Errorf("Expected key to be '%s' but was '%s'", expected, actual)
}
// env var in server block body as argument
p = testParser(":{%PORT%}\ndir1 {$FOOBAR}")
blocks, _ = p.parseAll()
if actual, expected := blocks[0].Keys[0], ":8080"; expected != actual {
t.Errorf("Expected key to be '%s' but was '%s'", expected, actual)
}
if actual, expected := blocks[0].Tokens["dir1"][1].Text, "foobar"; expected != actual {
t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
}
// combined windows env vars in argument
p = testParser(":{%PORT%}\ndir1 {%ADDRESS%}/{%FOOBAR%}")
blocks, _ = p.parseAll()
if actual, expected := blocks[0].Tokens["dir1"][1].Text, "servername.com/foobar"; expected != actual {
t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
}
// malformed env var (windows)
p = testParser(":1234\ndir1 {%ADDRESS}")
blocks, _ = p.parseAll()
if actual, expected := blocks[0].Tokens["dir1"][1].Text, "{%ADDRESS}"; expected != actual {
t.Errorf("Expected host to be '%s' but was '%s'", expected, actual)
}
// malformed (non-existent) env var (unix)
p = testParser(`:{$PORT$}`)
blocks, _ = p.parseAll()
if actual, expected := blocks[0].Keys[0], ":"; expected != actual {
t.Errorf("Expected key to be '%s' but was '%s'", expected, actual)
}
// in quoted field
p = testParser(":1234\ndir1 \"Test {$FOOBAR} test\"")
blocks, _ = p.parseAll()
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 {
buf := strings.NewReader(input)
p := parser{Dispenser: NewDispenser("Caddyfile", buf)}
return p
}
func TestSnippets(t *testing.T) {
p := testParser(`
(common) {
gzip foo
errors stderr
}
http://example.com {
import common
}
`)
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) != 2 {
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)
}
if actual, expected := blocks[0].Tokens["errors"][1].Text, "stderr"; expected != actual {
t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
}
}
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)
}
}
+6
View File
@@ -0,0 +1,6 @@
glob0.host0 {
dir2 arg1
}
glob0.host1 {
}
+4
View File
@@ -0,0 +1,4 @@
glob1.host0 {
dir1
dir2 arg1
}
+3
View File
@@ -0,0 +1,3 @@
glob2.host0 {
dir2 arg1
}
+204
View File
@@ -0,0 +1,204 @@
// 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.
// Package basicauth implements HTTP Basic Authentication for Caddy.
//
// This is useful for simple protections on a website, like requiring
// a password to access an admin interface. This package assumes a
// fairly small threat model.
package basicauth
import (
"bufio"
"context"
"crypto/sha1"
"crypto/subtle"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strings"
"sync"
"github.com/jimstudt/http-authentication/basic"
"github.com/mholt/caddy/caddyhttp/httpserver"
)
// BasicAuth is middleware to protect resources with a username and password.
// Note that HTTP Basic Authentication is not secure by itself and should
// not be used to protect important assets without HTTPS. Even then, the
// security of HTTP Basic Auth is disputed. Use discretion when deciding
// what to protect with BasicAuth.
type BasicAuth struct {
Next httpserver.Handler
SiteRoot string
Rules []Rule
}
// ServeHTTP implements the httpserver.Handler interface.
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 {
if !httpserver.Path(r.URL.Path).Matches(res) {
continue
}
// path matches; this endpoint is protected
protected = true
realm = rule.Realm
// parse auth header
username, password, ok = r.BasicAuth()
// check credentials
if !ok ||
username != rule.Username ||
!rule.Password(password) {
continue
}
// by this point, authentication was successful
isAuthenticated = true
// let upstream middleware (e.g. fastcgi and cgi) know about authenticated
// user; this replaces the request with a wrapped instance
r = r.WithContext(context.WithValue(r.Context(),
httpserver.RemoteUserCtxKey, username))
// Provide username to be used in log by replacer
repl := httpserver.NewReplacer(r, nil, "-")
repl.Set("user", username)
}
}
if protected && !isAuthenticated {
// browsers show a message that says something like:
// "The website says: <realm>"
// which is kinda dumb, but whatever.
if realm == "" {
realm = "Restricted"
}
w.Header().Set("WWW-Authenticate", "Basic realm=\""+realm+"\"")
// 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
return a.Next.ServeHTTP(w, r)
}
// Rule represents a BasicAuth rule. A username and password
// combination protect the associated resources, which are
// file or directory paths.
type Rule struct {
Username string
Password func(string) bool
Resources []string
Realm string // See RFC 1945 and RFC 2617, default: "Restricted"
}
// PasswordMatcher determines whether a password matches a rule.
type PasswordMatcher func(pw string) bool
var (
htpasswords map[string]map[string]PasswordMatcher
htpasswordsMu sync.Mutex
)
// GetHtpasswdMatcher matches password rules.
func GetHtpasswdMatcher(filename, username, siteRoot string) (PasswordMatcher, error) {
filename = filepath.Join(siteRoot, filename)
htpasswordsMu.Lock()
if htpasswords == nil {
htpasswords = make(map[string]map[string]PasswordMatcher)
}
pm := htpasswords[filename]
if pm == nil {
fh, err := os.Open(filename)
if err != nil {
return nil, fmt.Errorf("open %q: %v", filename, err)
}
defer fh.Close()
pm = make(map[string]PasswordMatcher)
if err = parseHtpasswd(pm, fh); err != nil {
return nil, fmt.Errorf("parsing htpasswd %q: %v", fh.Name(), err)
}
htpasswords[filename] = pm
}
htpasswordsMu.Unlock()
if pm[username] == nil {
return nil, fmt.Errorf("username %q not found in %q", username, filename)
}
return pm[username], nil
}
func parseHtpasswd(pm map[string]PasswordMatcher, r io.Reader) error {
scanner := bufio.NewScanner(r)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" || strings.IndexByte(line, '#') == 0 {
continue
}
i := strings.IndexByte(line, ':')
if i <= 0 {
return fmt.Errorf("malformed line, no color: %q", line)
}
user, encoded := line[:i], line[i+1:]
for _, p := range basic.DefaultSystems {
matcher, err := p(encoded)
if err != nil {
return err
}
if matcher != nil {
pm[user] = matcher.MatchesPassword
break
}
}
}
return scanner.Err()
}
// PlainMatcher returns a PasswordMatcher that does a constant-time
// byte comparison against the password passw.
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))
passwSum := passwHash.Sum(nil)
return func(pw string) bool {
pwHash := sha1.New()
pwHash.Write([]byte(pw))
pwSum := pwHash.Sum(nil)
return subtle.ConstantTimeCompare([]byte(pwSum), []byte(passwSum)) == 1
}
}
+230
View File
@@ -0,0 +1,230 @@
// 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.
package basicauth
import (
"encoding/base64"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"github.com/mholt/caddy/caddyhttp/httpserver"
)
func TestBasicAuth(t *testing.T) {
var i int
// This handler is registered for tests in which the only authorized user is
// "okuser"
upstreamHandler := func(w http.ResponseWriter, r *http.Request) (int, error) {
remoteUser, _ := r.Context().Value(httpserver.RemoteUserCtxKey).(string)
if remoteUser != "okuser" {
t.Errorf("Test %d: expecting remote user 'okuser', got '%s'", i, remoteUser)
}
return http.StatusOK, nil
}
rws := []BasicAuth{
{
Next: httpserver.HandlerFunc(upstreamHandler),
Rules: []Rule{
{Username: "okuser", Password: PlainMatcher("okpass"),
Resources: []string{"/testing"}, Realm: "Resources"},
},
},
{
Next: httpserver.HandlerFunc(upstreamHandler),
Rules: []Rule{
{Username: "okuser", Password: PlainMatcher("okpass"),
Resources: []string{"/testing"}},
},
},
}
type testType struct {
from string
result int
user string
password string
haserror bool
}
tests := []testType{
{"/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
for _, rw := range rws {
expectRealm := rw.Rules[0].Realm
if expectRealm == "" {
expectRealm = "Restricted" // Default if Realm not specified in rule
}
for i, test = range tests {
req, err := http.NewRequest("GET", test.from, nil)
if err != nil {
t.Fatalf("Test %d: Could not create HTTP request: %v", i, err)
}
req.SetBasicAuth(test.user, test.password)
rec := httptest.NewRecorder()
result, err := rw.ServeHTTP(rec, req)
if err != nil {
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",
i, test.result, result)
}
if test.result == http.StatusUnauthorized {
headers := rec.Header()
if val, ok := headers["Www-Authenticate"]; ok {
if got, want := val[0], "Basic realm=\""+expectRealm+"\""; got != want {
t.Errorf("Test %d: Www-Authenticate header should be '%s', got: '%s'", i, want, got)
}
} else {
t.Errorf("Test %d: response should have a 'Www-Authenticate' header", i)
}
} else {
if req.Header.Get("Authorization") == "" {
// see issue #1508: https://github.com/mholt/caddy/issues/1508
t.Errorf("Test %d: Expected Authorization header to be retained after successful auth, but was empty", i)
}
}
}
}
}
func TestMultipleOverlappingRules(t *testing.T) {
rw := BasicAuth{
Next: httpserver.HandlerFunc(contentHandler),
Rules: []Rule{
{Username: "t", Password: PlainMatcher("p1"), Resources: []string{"/t"}},
{Username: "t1", Password: PlainMatcher("p2"), Resources: []string{"/t/t"}},
},
}
tests := []struct {
from string
result int
cred string
haserror bool
}{
{"/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 {
req, err := http.NewRequest("GET", test.from, nil)
if err != nil {
t.Fatalf("Test %d: Could not create HTTP request %v", i, err)
}
auth := "Basic " + base64.StdEncoding.EncodeToString([]byte(test.cred))
req.Header.Set("Authorization", auth)
rec := httptest.NewRecorder()
result, err := rw.ServeHTTP(rec, req)
if err != nil {
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'",
i, test.result, result)
}
}
}
func contentHandler(w http.ResponseWriter, r *http.Request) (int, error) {
fmt.Fprintf(w, r.URL.String())
return http.StatusOK, nil
}
func TestHtpasswd(t *testing.T) {
htpasswdPasswd := "IedFOuGmTpT8"
htpasswdFile := `sha1:{SHA}dcAUljwz99qFjYR0YLTXx0RqLww=
md5:$apr1$l42y8rex$pOA2VJ0x/0TwaFeAF9nX61`
htfh, err := ioutil.TempFile("", "basicauth-")
if err != nil {
t.Skip("Error creating temp file, will skip htpassword test")
return
}
defer os.Remove(htfh.Name())
if _, err = htfh.Write([]byte(htpasswdFile)); err != nil {
t.Fatalf("write htpasswd file %q: %v", htfh.Name(), err)
}
htfh.Close()
for i, username := range []string{"sha1", "md5"} {
rule := Rule{Username: username, Resources: []string{"/testing"}}
siteRoot := filepath.Dir(htfh.Name())
filename := filepath.Base(htfh.Name())
if rule.Password, err = GetHtpasswdMatcher(filename, rule.Username, siteRoot); err != nil {
t.Fatalf("GetHtpasswdMatcher(%q, %q): %v", htfh.Name(), rule.Username, err)
}
t.Logf("%d. username=%q", i, rule.Username)
if !rule.Password(htpasswdPasswd) || rule.Password(htpasswdPasswd+"!") {
t.Errorf("%d (%s) password does not match.", i, rule.Username)
}
}
}
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)
}
}
+113
View File
@@ -0,0 +1,113 @@
// 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.
package basicauth
import (
"strings"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
)
func init() {
caddy.RegisterPlugin("basicauth", caddy.Plugin{
ServerType: "http",
Action: setup,
})
}
// setup configures a new BasicAuth middleware instance.
func setup(c *caddy.Controller) error {
cfg := httpserver.GetConfig(c)
root := cfg.Root
rules, err := basicAuthParse(c)
if err != nil {
return err
}
basic := BasicAuth{Rules: rules}
cfg.AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
basic.Next = next
basic.SiteRoot = root
return basic
})
return nil
}
func basicAuthParse(c *caddy.Controller) ([]Rule, error) {
var rules []Rule
cfg := httpserver.GetConfig(c)
var err error
for c.Next() {
var rule Rule
args := c.RemainingArgs()
switch len(args) {
case 2:
rule.Username = args[0]
if rule.Password, err = passwordMatcher(rule.Username, args[1], cfg.Root); err != nil {
return rules, c.Errf("Get password matcher from %s: %v", c.Val(), err)
}
case 3:
rule.Resources = append(rule.Resources, args[0])
rule.Username = args[1]
if rule.Password, err = passwordMatcher(rule.Username, args[2], cfg.Root); err != nil {
return rules, c.Errf("Get password matcher from %s: %v", c.Val(), err)
}
default:
return rules, c.ArgErr()
}
// If nested block is present, process it here
for c.NextBlock() {
val := c.Val()
args = c.RemainingArgs()
switch len(args) {
case 0:
// Assume single argument is path resource
rule.Resources = append(rule.Resources, val)
case 1:
if val == "realm" {
if rule.Realm == "" {
rule.Realm = strings.Replace(args[0], `"`, `\"`, -1)
} else {
return rules, c.Errf("\"realm\" subdirective can only be specified once")
}
} else {
return rules, c.Errf("expecting \"realm\", got \"%s\"", val)
}
default:
return rules, c.ArgErr()
}
}
rules = append(rules, rule)
}
return rules, nil
}
func passwordMatcher(username, passw, siteRoot string) (PasswordMatcher, error) {
htpasswdPrefix := "htpasswd="
if !strings.HasPrefix(passw, htpasswdPrefix) {
return PlainMatcher(passw), nil
}
return GetHtpasswdMatcher(passw[len(htpasswdPrefix):], username, siteRoot)
}

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