Compare commits

..

1951 Commits

Author SHA1 Message Date
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
Matthew Holt 4d907d57fa Whoops, emergency bug fix
Made a faulty assumption that virualhosts could share acme proxy handlers; turns out they can't without fumbling up the middleware configuration (middleware chains overlap and cross over into other virtualhosts)!
2015-11-18 18:41:01 -07:00
Matthew Holt 24352e799a Remove SimpleHTTP and bump version to 0.8 beta 4! 2015-11-18 17:40:35 -07:00
Matthew Holt e17d43b58a Default host now empty string; default port now depends on host
Hosts which are eligible for automatic HTTPS have default port "https" but other hosts (wildcards, loopback, etc.) have the default port 2015. The default host of empty string should be more IPv6-compatible.
2015-11-18 10:05:13 -07:00
Matthew Holt 580b50ea20 letsencrypt: Support for http-01, awkwardly straddling that and SimpleHTTP for now 2015-11-17 18:11:19 -07:00
Matthew Holt 659df6967e letsencrypt: Don't assume default port of 443 2015-11-17 16:17:43 -07:00
Matthew Holt b9244cdf2e templates: Another context fix when host header is missing port 2015-11-17 14:35:18 -07:00
Matthew Holt a2ba00bdc8 Update docs n things 2015-11-17 10:19:03 -07:00
Matthew Holt 1d47e590e5 proxy: Make headers when upstream is created; avoid potential nil ptr deref 2015-11-17 10:18:13 -07:00
Matt Holt 280ba9db85 Merge pull request #345 from tw4452852/my_proxy
proxy: make http header block scoped
2015-11-17 08:20:29 -07:00
Matt Holt 7f98a6cccf Merge pull request #347 from tw4452852/my_health
proxy: make tests workable when offline
2015-11-17 08:14:50 -07:00
Tw a5b117fcdf proxy: make tests workable when offline
Instead of accessing the google website, we setup a local server
for test, then tests will work fine even we are offline.

Fix issue #346

Signed-off-by: Tw <tw19881113@gmail.com>
2015-11-17 15:18:02 +08:00
Tw f56d2090b6 proxy: make http header block scoped
Each proxy block should could specify its own http header
instead of sharing a global one.

Fix issue #341

Signed-off-by: Tw <tw19881113@gmail.com>
2015-11-17 14:07:32 +08:00
Matt Holt 37e3cf684d Merge pull request #343 from abiosoft/master
proxy: 'except' property to ignore subpaths
2015-11-16 18:06:58 -07:00
Abiola Ibrahim 7949388da8 Proxy: Allow ignored subpaths. 2015-11-16 17:22:06 +01:00
Matthew Holt dd119e04b1 Fix go vet 2015-11-15 11:06:50 -07:00
Matthew Holt f7cfe79905 websocket: Simple buildEnv test, and fix for addresses without port 2015-11-15 11:05:26 -07:00
Matthew Holt 3dc5e0e181 Added a few little tests 2015-11-15 10:55:15 -07:00
Matthew Holt 1ca34c4ecf Couple fixes for env var replacements and tests 2015-11-15 08:01:24 -07:00
Matt Holt 837ee9f042 Merge pull request #319 from mbanzon/issue-304
parser: Allow use of environment variables in tokens
2015-11-15 07:17:42 -07:00
Michael Banzon d448c919e8 Changed implementation of issue #304 fix
It no longer uses regular expressions.
It supports both the Unix `{$ENV_VAR}` _and_ the Windows `{%ENV_VAR%}`
syntax.
Added test for both Unix and Windows env. syntax.
2015-11-15 11:16:37 +01:00
Matthew Holt 7d5b6b96ea Make signal trapping optional
Go programs using the caddy package may not want the it to capture all the signals...
2015-11-14 21:59:43 -07:00
Matthew Holt 7b064535bf Changed SIGINT and added support for HUP, QUIT, and TERM 2015-11-14 20:56:34 -07:00
Matthew Holt b42334eb91 Several improvements and bug fixes related to graceful reloads
Added a -grace flag to customize graceful shutdown period, fixed bugs related to closing file descriptors (and dup'ed fds), improved healthcheck signaling to parent, fixed a race condition with the graceful listener, etc. These improvements mainly provide better support for frequent reloading or unusual use cases of Start and Stop after a Restart (POSIX systems). This forum thread was valuable help in debugging: https://forum.golangbridge.org/t/bind-address-already-in-use-even-after-listener-closed/1510?u=matt
2015-11-14 18:00:25 -07:00
Matthew Holt 94c746c44f letsencrypt: Return an error if making site folder fails 2015-11-14 18:00:25 -07:00
Matthew Holt 7d46a7d5f4 Much refactor; many fix; so wow
Fixed pidfile writing problem where a pidfile would be written even if child failed, also cleaned up restarts a bit and fixed a few bugs, it's more robust now in case of failures and with logging.
2015-11-14 18:00:25 -07:00
Matthew Holt 9e2cef38f6 Write pidfile only if server starts successfully
Whether the original parent process or a child process as part of a restart, the pidfile will not be written/changed until that process has started successfully. It is written every time caddy.Start() succeeds (may be reundant, but that's probably okay).
2015-11-14 18:00:24 -07:00
Michael Banzon e166ebf68b Added test for environment replacement.
Added test for the fix of issue #304
2015-11-14 18:54:29 +01:00
Matt Holt 33b1d4c55d Merge pull request #340 from cubicdaiya/setcpu-test-fix
fixed test failure on CPU 1 core machine.
2015-11-13 23:11:35 -07:00
Matt Holt ae2e0900c1 Merge pull request #339 from cubicdaiya/vary-accept-encoding
gzip: added Vary: Accept-Encoding to response header.
2015-11-13 23:10:59 -07:00
Tatsuhiko Kubo 91ac2c58fa fixed test failure.
When CPU is 1 core, expected value (int(0.5 * float32(maxCPU))) is zero.
But runtime.GOMAXPROCS(-1) returns always 1.
2015-11-14 11:38:26 +09:00
Tatsuhiko Kubo 69662d4d7d gzip: added Vary: Accept-Encoding to response header.
When the downstream is cache server or CDN, it is important.
2015-11-14 06:11:37 +09:00
Matt Holt fc6afe2a8b Merge pull request #333 from mholt/firststartup
startup: Only run commands at first startup
2015-11-10 23:03:17 -07:00
Matthew Holt 51d2ff4e47 markdown: Default extensions .md, .markdown, and .mdown 2015-11-10 22:39:27 -07:00
Matthew Holt d46967d1e2 core: Fixed minor restart bug
Actually, restart on posix systems failed entirely if caddy was executed without the path included; also fixed a related bug where a variable was declared but never assigned.
2015-11-10 21:26:22 -07:00
Matthew Holt 4d78013646 Clean up flags 2015-11-10 19:50:40 -07:00
Matthew Holt 5cced604e4 startup: Only run commands at first startup
We had to hack some special support into the server and caddy packages for this. There are some middlewares which should only execute commands when the original parent process first starts up. For example, someone using the startup directive to start a backend service would not expect the command to be executed every time the config was reloaded or changed - only once when they first started the original caddy process.

This commit adds FirstStartup to the virtualhost config
2015-11-10 19:46:18 -07:00
Matt Holt a39ed2823e Merge pull request #332 from coolaj86/coolaj86-alphabetize
Alphabetize command line options, add -pidfile
2015-11-10 19:02:39 -07:00
AJ ONeal 4bed399ca4 Alphabetize command line options, vars, and checks
As per https://github.com/mholt/caddy/issues/331
2015-11-10 17:52:29 -08:00
AJ ONeal 93c330c4ce add --pidfile string option
As per https://github.com/mholt/caddy/issues/317
2015-11-10 17:44:00 -08:00
Matthew Holt 76ec785e87 ext: Fix panic when URL path is empty 2015-11-10 16:04:02 -07:00
Matthew Holt e9b9432da5 "-conf stdin" required to pipe in Caddyfile
Some programs (Node.js, supervisor, etc.) open a stdin pipe by default and don't use it, causing Caddy to block. It is their error, but we have to try to accommodate unfortunately. To fix this more universally, parent must explicitly set -conf to "stdin" to read from pipe.
2015-11-10 15:06:47 -07:00
Matthew Holt c31e86db02 caddyfile: Change JSON format to use arrays, not objects
Since a directive can appear on multiple lines, the object syntax wasn't working well. This also fixes several other serialization bugs.
2015-11-10 11:49:01 -07:00
Matthew Holt 13557eb5ef core: Fix bug that caused parent process to block indefinitely
The error channel used when starting all the servers must be buffered so that, even if there are no errors at startup, the returns that insert into the error channel will not be blocked, since after startup, nobody is reading that channel anymore.
2015-11-09 11:52:43 -07:00
Matthew Holt 02213402e8 Unexport internal types; improved markdown summaries 2015-11-09 07:45:37 -07:00
Matt Holt e1f23a1eb7 Merge pull request #326 from PatelNDipen/master
Wrote tests for browse.go and redir.go
2015-11-08 09:37:04 -07:00
Dipen Patel 485af2c6ba removed comment from browse test 2015-11-08 08:40:18 -05:00
Matthew Holt 171fd34b3c markdown: Make base path optional, always generate links
The base path being optional in the Caddyfile is convenient when you just want the whole site to be markdown-enabled. The other change is to always generate links... this is because an index page for markdown files may not be statically generated, but it should still show links. Commit 09341fc was a regression, and this fixes it.
2015-11-07 20:24:17 -07:00
Dipen Patel 1017142d9b Made style adjustments to browse and redir tests 2015-11-07 22:17:26 -05:00
Matthew Holt be9f644425 -host and -port flags affect shorthand caddyfile 2015-11-07 20:03:02 -07:00
Matthew Holt 8628a50b7d Forgot this one 2015-11-07 17:25:44 -07:00
Matthew Holt 161db70c15 Latest Go 1.4 version 2015-11-07 17:23:16 -07:00
Matthew Holt e7b8be31cf This calls for a new beta version 2015-11-07 17:22:41 -07:00
Matthew Holt e56f7affc9 Show message when activating HTTPS
It can take a few seconds...
2015-11-07 17:12:21 -07:00
Dipen Patel 2b1cc77f4b Wrote tests for browse.go and redir.go 2015-11-07 18:03:21 -05:00
Matthew Holt 18e9aa4d57 Pipe and -conf flag together are an error (#315) 2015-11-06 13:22:37 -07:00
Matt Holt cf5aa1bed1 Merge pull request #323 from tpng/patch-1
use log instead of fmt to obey the -log flag
2015-11-06 08:38:51 -07:00
Benny Ng c35b201685 use log instead of fmt to obey the -log flag 2015-11-06 23:09:40 +08:00
Matt Holt a1481bc29e Merge pull request #306 from mholt/bug/websocket-races
fixed data races in websockets
2015-11-05 17:19:35 -07:00
Matthew Holt d34e92ee70 checkDirectives must've slipped through the cracks in big merge
Also we're not messing with log flags anymore during parsing. Timestamps could come in handy, plus concurrent logging is going on now.
2015-11-05 17:14:47 -07:00
Matthew Holt bcea5182c6 Don't truncate process log; more consistent output 2015-11-05 17:01:08 -07:00
Matthew Holt 2fb4810cdb Fixed racy error reporting at server startup
Previously, if a listener fails to bind (for example), there was a race in caddy.go between unblocking the startup waitgroup and returning the error and putting it into errChan. Now, an error is returned directly into errChan and the closing of the startup waitgroup is defered until after that return takes place.
2015-11-05 15:21:13 -07:00
Matthew Holt 411dd7dff5 New -log flag to direct log output to desired place
Log file can also be stdout or stderr. Log output is disabled by default now, which makes it more feasible to add more log statements to trace program flow in debugging situations.
2015-11-05 14:07:52 -07:00
Matt Holt 96f04cdc38 Merge pull request #321 from coolaj86/patch-1
add helpful suggestion to identify problem
2015-11-05 13:46:58 -07:00
AJ ONeal b963c7c9ac add helpful suggestion to identify problem 2015-11-05 12:39:17 -08:00
Matthew Holt fc7f7dffa8 Prevent panic in case of hung loading sequence (fixes #315)
This is known to happen if another program spawns Caddy with a stdin pipe but does not close its write end of Caddy's stdin.
2015-11-05 13:31:24 -07:00
Matt Holt 47c5b6c9c4 Merge pull request #305 from buddhamagnet/refactor/lint-vet
Lint pacification
2015-11-05 10:25:24 -07:00
Michael Banzon 8774c90709 Removed the Windows part. It wasn't working properly.
For issue #304.
2015-11-05 18:12:06 +01:00
Michael Banzon 01465932e7 Environment variables Windows style
Added the Windows style ({%%}) for environment variables in Caddyfiles
(for issue #304).
2015-11-05 18:01:44 +01:00
Michael Banzon 72c0527b7d Added support for env vars in Caddyfile
This is work in progress for #304
2015-11-05 08:26:10 +01:00
buddhamagnet e23af5e99a final corrections 2015-11-05 00:44:35 +00:00
buddhamagnet 57f1d3c205 pass golint
pass all tests

respond to maintainer comments

reinstate assignment of t

correct typo

correct typo

pass linter some more
2015-11-05 00:40:35 +00:00
Matt Holt 7a159ad934 Merge pull request #313 from mholt/fix-tls-defaults-le
Fix regression: Ensure TLS defaults are added by LE handlers.
2015-11-04 16:21:09 -07:00
xenolf 6fdc83faeb Fix regression: Ensure TLS defaults are added by LE handlers. 2015-11-04 23:53:03 +01:00
Matthew Holt d36685acdd letsencrypt: Fix bug if different emails used; beta 2 2015-11-04 12:19:43 -07:00
Matthew Holt 051d2a68c0 Fixed behavior with empty Caddyfile
If the -host flag is used, we might still have to set up Let's Encrypt, so this change is necessary.
2015-11-04 09:26:11 -07:00
Austin 34c369155c trim possible line terminators from message 2015-11-03 13:37:32 -08:00
Matthew Holt 7f7a6abafd Revised README 2015-11-03 12:39:25 -07:00
Matthew Holt 5e1573dd84 Better error handling at startup and fixed some bugs
Fixed bug where manually specifying port 443 disabled TLS (whoops); otherHostHasScheme was the culprit, since it would return true even if it was the same config that had that scheme.

Also, an error at startup (if not a restart) is now fatal, rather than keeping a half-alive zombie server.
2015-11-03 12:01:54 -07:00
Matthew Holt e8006acf80 Fix -port, -host, and -root flags when Caddyfile is missing 2015-11-03 08:10:16 -07:00
Matt Holt 295d21f37d Merge pull request #308 from mholt/letsencrypt
Let's Encrypt
2015-11-02 21:06:31 -07:00
Matthew Holt 866427491c Forgot something 2015-11-02 21:02:35 -07:00
Matthew Holt 9905f48c8e Update changelog and readme 2015-11-02 20:56:13 -07:00
Matthew Holt 0970c058f7 tls: Repair from messy merge 2015-11-02 20:54:38 -07:00
Matthew Holt ad057ab873 Merge branch 'master' into letsencrypt
Conflicts:
	caddy/parse/parse.go
	caddy/parse/parsing.go
	config/config.go
	config/setup/controller.go
	main.go
	server/server.go
2015-11-02 20:26:55 -07:00
Matthew Holt 09341fca12 markdown: Don't generate static site or links unless sitegen is enabled 2015-11-02 20:15:42 -07:00
Matthew Holt c3e6463676 A few comments, slight tweaks 2015-11-02 19:27:42 -07:00
Matthew Holt d18cf12f14 letsencrypt: Fixed renewals
By chaining in a middleware handler and using newly exposed hooks from the acme package, we're able to proxy ACME requests on port 443 to the ACME client listening on a different port.
2015-11-02 19:27:23 -07:00
Austin abc7c6a148 fixed data races in websockets 2015-11-02 14:19:38 -08:00
Matthew Holt b143bbdbaa letsencrypt: Better logic for handling issuance failures
This fixes a bug with the -agree flag
2015-11-02 14:09:35 -07:00
Matthew Holt be0fb0053d letsencrypt: Re-prompt user if obtaining certs fails due to updated SA 2015-11-02 11:06:42 -07:00
Matthew Holt 2712dcd1f5 tls: If port unspecified and user provides cert+key, use 443 2015-11-01 19:01:46 -07:00
xenolf cac58eaab9 Update to latest lego changes 2015-11-02 01:41:02 +01:00
Matthew Holt 9a4e26a518 letsencrypt: Don't store KeyFile as field in user; staying consistent 2015-11-01 10:58:58 -07:00
Matthew Holt a729be295a letsencrypt: Activate during config load just after tls directive
Before, we were activating Let's Encrypt after all the directives were executed. This means their setup functions had access to potentially erroneous information about the server's TLS setup, since the letsencrypt package makes changes to the port, etc. Now, we execute all directives up to and including tls, then activate letsencrypt, then finish with the rest of the directives. It's a bit ugly, but I do think it is more correct. It also fixes some bugs, for example: a host that only has a catch-all redirect.
2015-11-01 09:46:23 -07:00
Matt Holt b6078eded1 Merge pull request #301 from abiosoft/master
Windows Build: Remove PATH from output.
2015-10-31 16:50:31 -06:00
Abiola Ibrahim ea642f6e1d Remove PATH from build output 2015-10-31 23:46:55 +01:00
Matthew Holt 4d71620cb0 core (Windows): Retry every 100ms for 2s if listener fails to bind
In testing, I've found that Windows doesn't release the socket right away even though the listener is closed, so calling caddy.Start() right after caddy.Stop() can fail. This change has server.ListenAndServe() try up to 20 times every 100ms to bind the listener, and only return an error if it doesn't succeed after 2 seconds. This might be kind of nifty for Unix, too, but there hasn't been a need for it yet.
2015-10-31 13:22:23 -06:00
Matthew Holt e4028b23c7 letsencrypt: Email prompt includes link to SA 2015-10-31 13:15:47 -06:00
Matt Holt 96c7c2768c Merge pull request #300 from PatelNDipen/master
startup/shutdown: test file

Also modified NewTestController to include a value for OncePerServerBlock
2015-10-31 10:39:41 -06:00
Dipen Patel 78d857a374 debugged startupshutdown.go 2015-10-31 12:33:50 -04:00
Dipen Patel 19148eba44 wrote startupshutdown tests and added OncePerServerBlock value in the NewTestController function of the controller.go file 2015-10-31 10:48:25 -04:00
Abiola Ibrahim 6a32076271 Merge pull request #299 from abiosoft/master
Fix for issues #297 and #298
2015-10-31 07:53:17 +01:00
Abiola Ibrahim ef617f9ce4 Merge pull request #295 from guilhermebr/master
Add option to change delims in templates
2015-10-31 07:51:22 +01:00
Matthew Holt 3843cea959 letsencrypt: Allow (but warn about) empty emails 2015-10-30 23:44:00 -06:00
Abiola Ibrahim dd1c49bde9 Fix for issues #297 and #298 2015-10-31 02:24:37 +01:00
Matthew Holt e99b3af0a5 letsencrypt: Numerous bug fixes 2015-10-30 15:55:59 -06:00
Matthew Holt 88c646c86c core: Start() blocks until servers finish starting
Also improved/clarified some docs
2015-10-30 00:19:43 -06:00
Matthew Holt 64cded8246 letsencrypt: Don't maintain assets of sites we don't maintain 2015-10-29 17:24:11 -06:00
Matthew Holt e3be524447 core: Fix for graceful reload after first reload signal
The file path of the originally-loaded Caddyfile must be piped to the forked process; previously it was using stdin after the first fork, which wouldn't load the newest Caddyfile from disk, which is the point of SIGUSR1.
2015-10-29 17:23:20 -06:00
Guilherme Rezende a62a7f7cf1 Add new optional block tests to setup/templates_test.go 2015-10-29 20:38:15 -02:00
Guilherme Rezende 9d456bba9b Add argument in new optional block in templates midd to set delimiters 2015-10-29 20:33:01 -02:00
Matthew Holt 89ad7593bd Merge branch 'caddyfile' into letsencrypt 2015-10-29 15:41:34 -06:00
Matthew Holt d227bec0ff Move common function into existing file 2015-10-29 10:34:47 -06:00
Matt Holt a3f0fff734 Merge pull request #296 from Makpoc/last-modified
markdown, templates: Add Last-Modified header
2015-10-29 10:31:38 -06:00
Matthew Holt efeeece735 caddyfile: http and https hosts should render in URL format 2015-10-29 10:13:30 -06:00
Matthew Holt 234783548f markdown: Enable tables, fenced code, and strikethrough (closes #294) 2015-10-29 09:59:32 -06:00
makpoc 5a29107f3b Add Last-Modified header when serving markdown and templates 2015-10-29 11:06:35 +02:00
Matthew Holt 976f5182e1 caddyfile: Better string and number handling 2015-10-29 00:22:56 -06:00
Matthew Holt 30c949085c letsencrypt: Stubbed out OCSP staple updates
OCSP status is checked at a regular interval, and if the OCSP status changes for any of the certificates, the change callback is executed (restarts the server, updating the OCSP staple).
2015-10-28 23:43:26 -06:00
Matthew Holt 6762df415c Clean up leaking goroutines and safer Start()/Stop() 2015-10-28 22:54:27 -06:00
Matthew Holt 1818b1ea62 letsencrypt: Better error handling, prompt user for SA 2015-10-28 18:12:07 -06:00
xenolf b67543f81c Track the latest lego OCSP changes 2015-10-28 16:35:19 +01:00
Matt Holt 94ff7dc6fb Merge pull request #287 from Makpoc/parsewincmd
Fix windows command parsing
2015-10-27 23:50:21 -06:00
Matthew Holt cc229aefae templates: Parse host successfully when port is implicit (fixes #292) 2015-10-27 23:20:05 -06:00
Matt Holt 7d91cfb512 Merge pull request #290 from mholt/le-graceful
Graceful restarts/reloads, refactoring
2015-10-27 14:17:40 -06:00
Matthew Holt 8548641dc1 letsencrypt: Check for errors 2015-10-27 13:02:47 -06:00
Matthew Holt c46898592f Merge branch 'letsencrypt' into le-graceful
Conflicts:
	caddy/letsencrypt/letsencrypt.go
	caddy/letsencrypt/renew.go
2015-10-27 12:59:55 -06:00
Matthew Holt 362ead2760 Minor test improvements 2015-10-27 12:53:31 -06:00
Matthew Holt a6ea1e6b55 letsencrypt: -ca flag to customize CA server 2015-10-27 12:52:58 -06:00
Matthew Holt 0f19df8a81 Keep tests deterministic 2015-10-27 00:43:24 -06:00
Matthew Holt ee5c842c7d Code to convert between JSON and Caddyfile
This will be used by the API so clients have an easier time manipulating the configuration
2015-10-27 00:07:22 -06:00
Matthew Holt c487b702a2 Little cleanup 2015-10-27 00:05:22 -06:00
Matthew Holt bb6613d0ae core: Fix SIGUSR1 so it actually reloads config 2015-10-26 17:57:32 -06:00
Matthew Holt 821c0fab09 core: Refactoring POSIX-only code for build tags 2015-10-26 16:49:05 -06:00
Matthew Holt 5b1962303d core: More refactoring, code cleanup, docs 2015-10-26 14:55:03 -06:00
Matthew Holt 41c4484222 core: SIGUSR1 to reload config; some code cleanup 2015-10-26 14:28:50 -06:00
Matthew Holt 4ebff9a130 core: Major refactor for graceful restarts; numerous fixes
Merged config and app packages into one called caddy. Abstracted away caddy startup functionality making it easier to embed Caddy in any Go application and use it as a library. Graceful restart (should) now ensure child starts properly. Now piping a gob bundle to child process so that the child can match up inherited listeners to server address. Much cleanup still to do.
2015-10-26 13:34:31 -06:00
Matthew Holt 6936658019 letsencrypt: Work with latest lego changes 2015-10-25 19:30:29 -06:00
Matthew Holt b5b31e398c letsencrypt: Graceful restarts
Lots of refinement still needed and runs only on POSIX systems. Windows will not get true graceful restarts (for now), but we will opt for very, very quick forceful restarts. Also, server configs are no longer put into a map; it is critical that they stay ordered so that they can be matched with their sockets in the child process after forking.

This implementation of graceful restarts is probably not perfect, but it is a good start. Lots of details to attend to now.
2015-10-25 18:45:55 -06:00
xenolf f9f1aafe0c Update to lego update. DevMode no longer exists. 2015-10-26 00:53:36 +01:00
Makpoc d1b667fbce Two quotes next to each other result in one escaped quote; Add Split Example, add/refactor tests for every platform. 2015-10-24 15:33:04 +03:00
xenolf 91465d8e6f Support for OCSP Stapling. Fixes #280 2015-10-24 04:36:54 +02:00
xenolf f8ad050dda Update for latest lego changes (cert bundling) 2015-10-24 04:35:55 +02:00
makpoc 0d004ccbab Attempt to fix windows command parsing + add more tests 2015-10-23 20:21:05 +03:00
xenolf 2e5eb63850 Function name changed in lego 2015-10-23 16:29:05 +02:00
Matthew Holt f24ecee603 letsencrypt: Basic renewal failover and better error handling 2015-10-21 21:28:33 -06:00
Matt Holt c5635f21a3 Merge pull request #283 from mholt/le-simplerenew
letsencrypt: Simplify timing mechanism for checking renewals
2015-10-21 17:08:57 -06:00
Matthew Holt 605f1942ef Merge branch 'letsencrypt' into le-simplerenew
Conflicts:
	config/letsencrypt/letsencrypt.go
2015-10-21 16:35:32 -06:00
Matthew Holt fec491fb12 Removed another test that is Windows-specific
We're not trying to test the shlex library; just our wrapper function
2015-10-21 14:15:42 -06:00
Matthew Holt 794d271152 Remove extra tests that were Linux-specific
These tests with the backslash seem to assert that shlex (our Unix shell parsing library) is working properly, not our wrapper function (that parses commands for both Windows and non-Windows). These tests break on Windows so I have removed them.
2015-10-21 14:11:30 -06:00
Matthew Holt 29362e45bc Parse Windows commands differently than Unix commands
Stinkin' backslashes
2015-10-21 14:03:33 -06:00
Matthew Holt a16beb98de letsencrypt: Revoke certificate 2015-10-21 00:09:45 -06:00
Matthew Holt 38885e4301 Simplify timing mechanism for checking renewals 2015-10-20 20:16:01 -06:00
Matt Holt 136119f8ac Merge pull request #282 from Makpoc/fileserver_tests
core: fileServer tests
2015-10-20 18:18:11 -06:00
Makpoc e3ec7394ab fix go vet error 2015-10-21 02:18:33 +03:00
Makpoc ddd69d19c0 Add tests for fileserver.go 2015-10-21 02:08:36 +03:00
Makpoc 8ecc366582 Check and return the correct error if Stat method fails (see golang issue #12991) 2015-10-21 01:25:38 +03:00
Matt Holt 4db54f8ddc Merge pull request #281 from mem/master
Add TestNewDefault to config tests
2015-10-20 10:36:59 -06:00
Marcelo Magallon 8f9f6caa4e Update config_test.go 2015-10-20 10:31:21 -06:00
Marcelo E. Magallon 7e41f6ed62 Add TestNewDefault to config tests
Very simple test to make sure that NewDefault is populating the correct
fields with the correct values.
2015-10-20 05:13:00 -06:00
Matt Holt 159eb68a11 Merge pull request #279 from pcasaretto/coverage-up
Test app.SetCPU, config.makeOnces, config.makeStorages
2015-10-19 20:55:35 -06:00
Paulo L F Casaretto 815231b1e0 Test app.SetCPU function 2015-10-20 00:33:36 -02:00
Paulo L F Casaretto 0feb0d9244 Test validDirective function 2015-10-20 00:33:19 -02:00
Paulo L F Casaretto 1db138ed55 Test makeStorages function 2015-10-19 23:23:18 -02:00
Paulo L F Casaretto 4c0d4dd780 Test makeOnces function 2015-10-19 23:19:12 -02:00
xenolf c626774da2 First, raw renewal implementation. Pretty basic :D 2015-10-20 02:44:00 +02:00
Matthew Holt acf43857a3 Fix ServerBlockStorage so it actually stores stuff 2015-10-19 07:41:58 -06:00
Matthew Holt e2f6c51fb0 core: Controller has field to persist server state
Also added ServerBlockHostIndex
2015-10-19 07:41:58 -06:00
Matthew Holt c4a7378466 core: Disable TLS for sites where http is explicitly defined (fix) 2015-10-19 07:41:58 -06:00
Matthew Holt a17e9b6b02 Add ServerBlockIndex and ServerBlockHosts to Controller
This way, Setup functions have access to the list of hosts that share the server block, and also, if needed for some reason, the index of the server block in the input
2015-10-19 07:41:58 -06:00
Matthew Holt f978967e5e OncePerServerBlock may now return an error 2015-10-19 07:41:58 -06:00
Matthew Holt fc413e2403 First use of OncePerServerBlock in a Setup function
startup and shutdown commands should only be executed once per appearance in the Caddyfile (naturally meaning once per server block).

Notice that we support multiple occurrences of startup and shutdown in the same server block by building the callback array incrementally as we parse the Caddyfile, then we append all the callbacks all at once. Quite literally, the OncePerServerBlock function executes only once per server block!
2015-10-19 07:41:58 -06:00
Matthew Holt 38719765bf Don't share sync.Once with all directives
If each server block had only one sync.Once then all directives would refer to it and only the first directive would be able to use it! So this commit changes it to a map of sync.Once instances, keyed by directive. So by creating a new map for every server block, each directive in that block can get its own sync.Once which is exactly what is needed. They won't step on each other this way.
2015-10-19 07:41:58 -06:00
Matthew Holt f3596f734d Epic revert of 0ac8bf5 and adding OncePerServerBlock
Turns out having each server block share a single server.Config during initialization when the Setup functions are being called was a bad idea. Sure, startup and shutdown functions were only executed once, but they had no idea what their hostname or port was. So here we revert to the old way of doing things where Setup may be called multiple times per server block (once per host associated with the block, to be precise), but the Setup functions now know their host and port since the config belongs to exactly one virtualHost. To have something happen just once per server block, use OncePerServerBlock, a new function available on each Controller.
2015-10-19 07:41:58 -06:00
Matt Holt 1d15fe069a Merge pull request #278 from Makpoc/context_tests
Cover the rest of the (not one-liner) functions in context
2015-10-19 07:08:34 -06:00
makpoc 72a5579d83 Cover the rest of the (not one-liner) functions in context 2015-10-19 13:51:49 +03:00
Matthew Holt cd0b47d068 letsencrypt: Don't auto-configure loopback hosts or 'tls off'
User can specify 'tls off" in Caddyfile to force-disable automatic HTTPS configuration
2015-10-18 22:50:42 -06:00
Matthew Holt 4c93ab8c68 Merge branch 'configfix' into letsencrypt 2015-10-18 19:48:57 -06:00
Matthew Holt c0ebe31560 Fix ServerBlockStorage so it actually stores stuff 2015-10-18 19:27:51 -06:00
Matthew Holt cc1ff93250 letsencrypt: Fix Windows tests 2015-10-18 12:12:33 -06:00
Matthew Holt 42ac2d2dde letsencrypt: More tests, tests for user.go & slight refactoring 2015-10-18 12:09:06 -06:00
Matthew Holt d764111886 letsencrypt: Storage tests 2015-10-18 10:39:28 -06:00
Matthew Holt 8cd6b8aa99 letsencrypt: Tests for load/save RSA keys and redirPlaintextHost 2015-10-17 23:35:59 -06:00
Matthew Holt da8a4fafcc letsencrypt: Use existing certs & keys if already in storage 2015-10-17 22:55:50 -06:00
Matthew Holt 9f9de389d5 lego provides PEM-encoded certificate bytes for us 2015-10-17 21:18:46 -06:00
Matthew Holt 7568b0e215 Compatibility with latest lego commits (dev mode enabled) 2015-10-17 21:00:48 -06:00
Matthew Holt a75663501d Little more refactoring in letsencrypt 2015-10-17 20:51:46 -06:00
Matthew Holt 96ae288c4b More refactoring; cleaning up code, preparing for tests 2015-10-17 20:44:33 -06:00
Matthew Holt a3a826572f Refactor letsencrypt code into its own package 2015-10-17 20:17:24 -06:00
Matthew Holt fe7ad8ee05 core: Controller has field to persist server state
Also added ServerBlockHostIndex
2015-10-17 14:11:32 -06:00
Matt Holt 3614a093e3 Merge pull request #275 from mem/master
Add tests for websocket configuration
2015-10-17 13:14:43 -06:00
Marcelo E. Magallon 6325bcf5b2 Add tests for websocket configuration
Command arguments:

	websocket /api5 "cmd arg1 arg2 arg3"

Optional block:

	websocket /api6 cat {
		respawn
	}

Invalid option in optional block:

	websocket /api7 cat {
		invalid
	}
2015-10-17 12:09:55 -06:00
Matthew Holt 307c2ffe3c Remove obsolete test 2015-10-17 11:19:56 -06:00
Matthew Holt 06913ab74f Oops (pass a pointer) 2015-10-17 11:15:43 -06:00
Matthew Holt 506630200b Redirect HTTP requests to HTTPS by default 2015-10-17 09:36:25 -06:00
Matthew Holt df194d567f Don't forget to set port to "https" and indicate TLS enabled 2015-10-17 09:06:05 -06:00
Matthew Holt 9727603250 Try to use most recent user email if not provided
Also more comments and starting to clean up code
2015-10-17 00:01:32 -06:00
Matthew Holt a0c8428f8c Can issue and use SSL certs and serve sites
Code is a huge mess; much cleanup to follow.
2015-10-16 23:30:00 -06:00
Matthew Holt dd91812b11 Merge branch 'configfix' into letsencrypt 2015-10-16 11:47:32 -06:00
Matthew Holt 10619f06b4 core: Disable TLS for sites where http is explicitly defined (fix) 2015-10-16 11:47:13 -06:00
Matthew Holt 0a1e472fc2 Merge branch 'configfix' into letsencrypt
Conflicts:
	config/config.go
2015-10-16 11:40:44 -06:00
Matthew Holt 4e92c71259 LE flags, modified tis directive, moved LE stuff to own file 2015-10-16 11:38:56 -06:00
Matthew Holt 2236780190 Add ServerBlockIndex and ServerBlockHosts to Controller
This way, Setup functions have access to the list of hosts that share the server block, and also, if needed for some reason, the index of the server block in the input
2015-10-15 23:34:54 -06:00
Matt Holt 3faffdce2d Merge pull request #274 from Makpoc/context_tests
Add context.go tests
2015-10-15 15:29:23 -06:00
Makpoc d6242e9cac Apply review comments - change the used domain, remove obsolete function, remove commented tests 2015-10-15 23:09:02 +03:00
Matthew Holt 691204ceed OncePerServerBlock may now return an error 2015-10-15 11:38:17 -06:00
makpoc bd4d9c6fe2 add tests for context.Header,IP,URL,Host,Port,Method,PathMatches 2015-10-15 19:46:23 +03:00
makpoc 3440f5cfbe add tests for context.Cookie() and context.IP() 2015-10-15 18:26:13 +03:00
Matthew Holt a518049fa2 Merge branch 'master' into configfix 2015-10-15 00:13:40 -06:00
Matthew Holt 35e309cf87 First use of OncePerServerBlock in a Setup function
startup and shutdown commands should only be executed once per appearance in the Caddyfile (naturally meaning once per server block).

Notice that we support multiple occurrences of startup and shutdown in the same server block by building the callback array incrementally as we parse the Caddyfile, then we append all the callbacks all at once. Quite literally, the OncePerServerBlock function executes only once per server block!
2015-10-15 00:11:26 -06:00
Matthew Holt e0fdddc73f Don't share sync.Once with all directives
If each server block had only one sync.Once then all directives would refer to it and only the first directive would be able to use it! So this commit changes it to a map of sync.Once instances, keyed by directive. So by creating a new map for every server block, each directive in that block can get its own sync.Once which is exactly what is needed. They won't step on each other this way.
2015-10-15 00:07:26 -06:00
Matthew Holt 0c07f7adcc Epic revert of 0ac8bf5 and adding OncePerServerBlock
Turns out having each server block share a single server.Config during initialization when the Setup functions are being called was a bad idea. Sure, startup and shutdown functions were only executed once, but they had no idea what their hostname or port was. So here we revert to the old way of doing things where Setup may be called multiple times per server block (once per host associated with the block, to be precise), but the Setup functions now know their host and port since the config belongs to exactly one virtualHost. To have something happen just once per server block, use OncePerServerBlock, a new function available on each Controller.
2015-10-14 23:45:28 -06:00
Austin Cherry a48ed9a246 Merge pull request #273 from mem/master
Simplify websocket ticker shutdown code
2015-10-14 18:29:00 -07:00
Marcelo E. Magallon d4a14af14d Simplify websocket ticker shutdown code
"A receive from a closed channel returns the zero value immediately"

Close the tickerChan in the calling function, this causes "case <-c" to
unblock immediately, ending the goroutine and stopping the ticker.
2015-10-14 18:48:43 -06:00
Makpoc f7e3ed13f9 TestInclude 2 should fail. Update test data and fix error checking 2015-10-15 02:21:02 +03:00
Makpoc 71c4962ff6 tests for context.Include 2015-10-15 02:09:37 +03:00
Matthew Holt b713a7796e Change c:\go to c:\gopath to avoid conflicts 2015-10-14 13:03:30 -06:00
Matt Holt 65e812d3a9 Merge pull request #270 from Makpoc/master
Add tests for command splitting and fix root tests on Windows
2015-10-14 10:13:53 -06:00
Matt Holt 5c3085fe51 Merge pull request #271 from zmb3/windows_failures
Fix test failures on Windows.
2015-10-14 10:13:18 -06:00
makpoc 6af26e2306 Use null byte in filename to simulate 'unable to access' on both windows and linux 2015-10-14 09:35:50 +03:00
Matt Holt a914565f51 Merge pull request #269 from mholt/chore/gorilla-websocket
websocket: Refactored to use gorilla instead of golang/x
2015-10-13 23:43:41 -06:00
Austin 24893bf740 removed panics, cleaned up leaking ticker routine 2015-10-13 19:07:54 -07:00
Zac Bergquist 26cbea9e12 Re-enable test
I had commented out this check just to make sure the rest of the test cases were succeeding and forgot to add it back in.
2015-10-13 20:23:05 -04:00
Zac Bergquist f7fcd7447a Fix test failure on non-Windows OS.
NewTestController now sets the site root to '.' to accomodate Windows.  This introduced a failure on Linux because we join "." and an absolute path in /tmp/ and end up looking for the temp file in the wrong place.  This change puts the temp file under the current working directory, which should resolve the issue.
2015-10-13 20:16:43 -04:00
Zac Bergquist 16bd63fc26 Removed my debug prints 2015-10-13 20:04:34 -04:00
Zac Bergquist e158cda057 Fix test failures on Windows.
Most of the Windows test failures are due to the path separator not being "/".  The general approach I took here was to keep paths in "URL form" (ie using "/" separators) as much as possible, and only convert to native paths when we attempt to open a file.  This will allow the most consistency between different host OS.  For example, data structures that store paths still store them with "/" delimiters.  Functions that accepted paths as input and return them as outputs still use "/".

There are still a few test failures that need to be sorted out.

- config/setup/TestRoot (I hear this has already been fixed by someone else)
- middleware/basicauth/TestBrowseTemplate and middleware/templates/Test (a line endings issue that I'm still working through)
2015-10-13 19:49:53 -04:00
Matthew Holt 7121e2c770 Change c:\go to c:\gopath to avoid conflicts 2015-10-13 16:13:13 -06:00
Makpoc f122b3bbdf Fix failing test (windows) - simulate an error by executing stat on a filename with zero-byte in it. Fix cleanup of created files after the tests. 2015-10-13 23:35:24 +03:00
Matt Holt 6717edcb87 Add AppVeyor badge
Distinguish between windows and linux builds
2015-10-13 22:41:10 +03:00
Matt Holt dee2e8e67d Add AppVeyor badge
Distinguish between windows and linux builds
2015-10-13 09:52:47 -06:00
makpoc 4544dabd56 Add tests for command splitting 2015-10-13 14:39:18 +03:00
Austin 222781abca websocket refactored to use gorilla 2015-10-12 19:59:11 -07:00
Matthew Holt 55a098cae8 Add AppVeyor manifest 2015-10-12 19:11:02 -06:00
Matt Holt 837c17c396 Merge pull request #267 from zmb3/reportcard
Ran gofmt -s, fixed some golint warnings, refactored some large functions

Minor quality improvements (closes #253)
2015-10-11 16:00:58 -06:00
Zac Bergquist f9bc74626d Address various lint and gocyclo warnings. Fixes #253 2015-10-11 16:28:02 -04:00
Matt Holt 17c91152e0 Merge pull request #266 from Makpoc/master
Add tests for root.go
2015-10-10 20:00:33 -06:00
Matthew Holt d414ef0d0f browse: Fix tests that fail only in CI environment
... I think. Submitting as PR to double-check. This change changes file mod times on the testdata to ensure they are not all the same so that the sort is predictable!
2015-10-10 19:53:11 -06:00
Makpoc e66aa25fce Fail the test if the configuration fails. 2015-10-10 04:15:25 +03:00
Matt Holt 75d82e8666 Merge pull request #265 from Karthic-Hackintosh/master
browse: Better test coverage and fix #264
2015-10-09 12:38:05 -06:00
makpoc af42d2a54a Add tests for root.go 2015-10-09 18:20:28 +03:00
Karthic Rao f5cd4f17f8 Exhaustive test coverage to test the usage of sort,order and limit parameter for the browse middleware 2015-10-09 11:28:11 +05:30
Matthew Holt 02c7770b57 Update change list 2015-10-08 11:30:46 -06:00
Matt Holt 0f049856a4 Merge pull request #262 from pyed/patch-4
browse: allow consecutive spaces in filenames
2015-10-07 10:41:19 -06:00
pyed bd14171b88 allow consecutive spaces for browse 2015-10-07 19:16:49 +03:00
Matthew Holt e6ba930e65 Merge branch 'master' of github.com:mholt/caddy 2015-10-01 09:58:17 -07:00
Matthew Holt 61a6b9511a Commenting on the need for additional redirect tests 2015-10-01 09:58:07 -07:00
Matt Holt 87efc67f48 Merge pull request #259 from abiosoft/master
New core middleware, MIME.
2015-10-01 09:56:56 -07:00
Abiola Ibrahim 9e2da6ec48 New core middleware, MIME. 2015-09-30 18:37:10 +01:00
Matthew Holt 3f9f675c43 redir: Include scheme in redirect rules
And added tests for status code and scheme
2015-09-30 08:38:31 -06:00
Matthew Holt 698399e61f Move controller_test.go into controller.go
Turns out the stuff in the test file needs to be exported so external add-ons can use them
2015-09-28 21:16:40 -06:00
Matthew Holt ec676fa15e Version bump: 0.7.6 2015-09-28 14:57:00 -06:00
Matthew Holt 122e3a9430 rewrite: Make internal header field name a const 2015-09-28 14:54:48 -06:00
Matt Holt 79a7f8a460 Merge pull request #247 from DenBeke/master
fastcgi: Stripping PATH_INFO from SCRIPT_NAME
2015-09-28 14:33:27 -06:00
Mathias Beke bb85a84561 Merge remote-tracking branch 'upstream/master'
Conflicts:
	middleware/fastcgi/fastcgi.go
2015-09-28 22:11:05 +02:00
Matthew Holt be6fc35326 fastcgi: Fix REQUEST_URI if rewrite directive changes URL 2015-09-27 18:48:28 -06:00
Matthew Holt 79de2a5de2 Stubbed out basic code to obtain Let's Encrypt cert 2015-09-26 18:16:14 -06:00
Matt Holt ca1f1362cc Merge pull request #257 from tarfu/patch-1
core: change to new http2 repo
2015-09-25 06:22:16 -06:00
Tobias Breitwieser 0ca0d552eb change to official http2 repo
The golang.org/x/net/http2 is now the official http2 repo.
It is advised to change the imports to it.
2015-09-25 14:10:03 +02:00
Mathias Beke 8baead6107 Merge remote-tracking branch 'upstream/master' 2015-09-25 11:54:15 +02:00
Matthew Holt 4f5a29d6d1 errors: New 'visible' mode to write stack trace to response
Also updated change list and added/improved tests
2015-09-24 16:21:28 -06:00
Matthew Holt da7562367c errors: Restore http status text in test 2015-09-24 14:01:08 -06:00
Matthew Holt 6001c94f30 errors: Fix test 2015-09-24 13:46:54 -06:00
Matt Holt 104a5998cb Merge pull request #251 from abiosoft/master
rewrite: Use middleware.Replacer
2015-09-23 14:22:54 -06:00
Matthew Holt 6cbd3ab096 proxy: 64-bit word alignment for 32-bit systems (fixes #252) 2015-09-22 16:47:39 -06:00
Abiola Ibrahim 7f9fa5730b Rewrite: Use only fragment, remove frag. 2015-09-20 18:13:53 +01:00
Matthew Holt bdccc51437 More consistent error messages 2015-09-20 10:55:16 -06:00
Abiola Ibrahim 0e039a1868 Rewrite: Use middleware.Replacer.
Bug fix for regexps starting with '/'.
2015-09-20 08:49:55 +01:00
Matthew Holt 10ab037833 Moved fileServer and browse.IndexPages into middleware package 2015-09-19 20:35:48 -06:00
Matt Holt 540a651fdf Merge pull request #250 from hacdias/master
browse: User defined variables (for templates)
2015-09-19 20:11:44 -06:00
Matthew Holt ee893325c4 Update change list 2015-09-19 11:24:44 -06:00
Henrique Dias 8120e57850 add user defined variables into browse template 2015-09-18 08:52:12 +01:00
Henrique Dias 043e000459 Merge pull request #1 from mholt/master
Update
2015-09-18 08:44:47 +01:00
Matt Holt 66fb8f031b Merge pull request #248 from hacdias/master
browse: Option to ignore indexes
2015-09-17 19:01:12 -06:00
Matthew Holt 9e2bef146e middleware: Added StripHTML to Context type 2015-09-17 16:23:30 -06:00
Henrique Dias 4c642e9d3c browse IgnoreIndexes option 2015-09-17 20:37:49 +01:00
Henrique Dias 30b19190dc add ignoreIndexes option to browse 2015-09-17 20:33:39 +01:00
Matthew Holt 840bc505f6 This is a pretty cool change 2015-09-16 21:31:58 -06:00
Matthew Holt 8c843ceefd middleware: Add StripExt to Context type for stripping extensions from paths 2015-09-16 21:31:58 -06:00
Mathias Beke aa5a595762 middleware/fastcgi: Stripping PATH_INFO from SCRIPT_NAME 2015-09-16 20:25:40 +02:00
Matt Holt 9dfb940d80 Merge pull request #245 from LK4D4/update_go
Use latest Go minor versions for testing
2015-09-14 11:49:59 -06:00
Alexander Morozov 1dbfeb7ecd Use latest minors Go for testing
Signed-off-by: Alexander Morozov <lk4d4@docker.com>
2015-09-14 10:43:46 -07:00
Matt Holt f4054b6954 Merge pull request #241 from LK4D4/fix_race_test
markdown: Use less strict condition to avoid problems with concurrency
2015-09-11 15:58:13 -06:00
Alexander Morozov faaef83954 Use less strict condition to avoid problems with concurrency
In latest go versions TestWatcher fails pretty often, because it is
"more concurrent" now. Reproducible with go master:
while go test github.com/mholt/caddy/middleware/markdown; do :; done

Signed-off-by: Alexander Morozov <lk4d4@docker.com>
2015-09-11 10:25:13 -07:00
Matt Holt 287543a0e6 Merge pull request #238 from LK4D4/fix_vet
Fix all vet warnings and add vet check to CI
2015-09-11 10:21:22 -06:00
Abiola Ibrahim 7545755b00 Merge pull request #240 from LK4D4/fix_map_race
markdown: fix race in accessing map
2015-09-11 17:00:24 +01:00
Abiola Ibrahim 26dc212f4c Merge pull request #239 from LK4D4/race_test
Fix race in test
2015-09-11 16:58:19 +01:00
Alexander Morozov a5128da67a markdown: fix race in accessing map
Signed-off-by: Alexander Morozov <lk4d4@docker.com>
2015-09-11 08:34:52 -07:00
Alexander Morozov 37eedf5cdc Fix race in test
Signed-off-by: Alexander Morozov <lk4d4@docker.com>
2015-09-11 08:34:08 -07:00
Alexander Morozov f2e680430a Add vet check to CI
Signed-off-by: Alexander Morozov <lk4d4@docker.com>
2015-09-10 20:53:09 -07:00
Alexander Morozov 740a6a7ad5 Use func from stdlib to clone *tls.Config for calming vet
Signed-off-by: Alexander Morozov <lk4d4@docker.com>
2015-09-10 20:52:49 -07:00
Alexander Morozov 1236e492a9 Fix format verbs for funcs
Signed-off-by: Alexander Morozov <lk4d4@docker.com>
2015-09-10 19:59:19 -07:00
Alexander Morozov 80db177f5a Fix vet warnings about unkeyed fields
Signed-off-by: Alexander Morozov <lk4d4@docker.com>
2015-09-10 19:57:23 -07:00
Alexander Morozov 3d1cac313c Use %v instead of %p to calm vet
Signed-off-by: Alexander Morozov <lk4d4@docker.com>
2015-09-10 19:52:23 -07:00
Matt Holt dc4a5ae1fd Merge pull request #237 from LK4D4/fix_lock
markdown: Use markdown.Config as pointer everywhere
2015-09-10 18:23:26 -06:00
Alexander Morozov da7b9a6bbc Use markdown.Config as pointer everywhere
* As value mutex was copied and therefore synchronization worked wrong
* It's pretty big structure with reference types, so copying create unnecessary
  pressure on GC

Signed-off-by: Alexander Morozov <lk4d4@docker.com>
2015-09-10 15:12:50 -07:00
Matt Holt 55de037035 Merge pull request #236 from LK4D4/fix_format_in_test
basicauth: Fix format call in tests
2015-09-10 15:16:13 -06:00
Alexander Morozov c468b114e4 Fix format call in tests
Signed-off-by: Alexander Morozov <lk4d4@docker.com>
2015-09-10 14:04:33 -07:00
Matt Holt d96bd5269a Merge pull request #235 from Karthic-Hackintosh/master
middleware: Complete test coverage for replacer
2015-09-10 08:27:23 -06:00
Karthic Rao ed4148f20e Complete test coverage for replacer for Go 2015-09-10 10:28:13 +05:30
Matt Holt f8e2cc8008 Merge pull request #234 from humboldtux/browse
core: Configuration as command line arg #222
2015-09-08 14:44:28 -06:00
Benoit Benedetti 5d32af8a6b Fix typo in loadConfigs comment 2015-09-08 22:38:30 +02:00
Benoit Benedetti ed10863494 Configuration as command line arg #222 2015-09-08 22:27:05 +02:00
Matthew Holt 4e1717db4c basicauth: htpasswd path now relative to site root 2015-09-05 16:04:30 -06:00
Matt Holt 159b68aab4 Merge pull request #228 from tgulacsi/htpasswd
basicauth: Add htpasswd support
2015-09-05 14:56:23 -06:00
Matt Holt d76cf6d337 Merge pull request #230 from evermax/master
log, errors: Use localtime for the log roller timestamp
2015-09-04 11:27:23 -06:00
Maxime 69950e57f0 Use localtime for the log roller timestamp 2015-09-04 19:18:01 +02:00
Matt Holt d44ab3dbab Merge pull request #229 from LK4D4/fix_format
Fix formatting directives in tests
2015-09-04 10:07:30 -06:00
Alexander Morozov b199825c3b Fix formatting directives in tests
Signed-off-by: Alexander Morozov <lk4d4@docker.com>
2015-09-04 08:34:58 -07:00
Matthew Holt 94becb89f6 Add Go 1.5 to the Travis CI manifest 2015-09-02 18:28:03 -06:00
Matt Holt 1f4231e1f0 Merge pull request #216 from evermax/master
log, errors: Added lumberjack library for log rolling
2015-09-02 17:44:10 -06:00
Maxime bdcbd11d65 Merge branch 'master' of https://github.com/mholt/caddy 2015-09-02 15:16:06 +02:00
Maxime 008160998a Added LogRoller parser and entity.
The errors and logs can now have log rolling if provided by the user.
The current customisable parameter of it are:
The maximal size of the file before rolling.
The maximal age/time of the file before rolling.
The number of backups to keep.
2015-09-02 15:13:31 +02:00
Matt Holt 1cafb1eea5 Merge pull request #226 from Karthic-Hackintosh/master
browse: Initial test for json response in browse.go
2015-08-31 21:34:54 -06:00
Tamás Gulácsi 392f1d70eb Add htpasswd support for basic auth
If the password arg starts with htpasswd=, then the rest is treated as
the file name of the htpasswd file, and used for md5 and sha1 hashes.
2015-08-30 20:08:42 +02:00
Matthew Holt d79d2611ca Mention setcap in readme so it's more prominent 2015-08-30 10:55:30 -06:00
karthic rao e3cea042d6 Left over comments removed
Redundant comments in the code removed
2015-08-30 19:00:35 +05:30
Karthic Rao 679668e3c0 removed redundant comment lines 2015-08-30 18:57:20 +05:30
Karthic Rao 730269743f Json response initial test for browse.go 2015-08-29 08:04:01 +05:30
Matt Holt bfc61824b9 Merge pull request #225 from Karthic-Hackintosh/master
middleware: Initial test for replacer
2015-08-27 15:01:45 -06:00
Karthic Rao 444f9e40d5 initial test for replacer 2015-08-27 23:36:32 +05:30
Matt Holt c5006321a7 Merge pull request #224 from pyed/patch-2
Fixing comment
2015-08-27 10:13:38 -06:00
pyed b9d3e7721e Fixing my comment
the old comment might throw the source-reader off, my bad.
2015-08-27 18:01:46 +03:00
Matt Holt 49a229835a Merge pull request #220 from pyed/json
browse: Optional JSON output
2015-08-27 08:48:44 -06:00
Abdulelah Alfuntukh 414b47d653 adds json option for the browse middleware 2015-08-24 23:37:11 +03:00
Abiola Ibrahim b62d087bb6 Merge pull request #218 from Karthic-Hackintosh/master
Complete test coverage for recorder.go of middleware package
2015-08-14 07:23:27 +01:00
Karthic Rao 4704625e3a Complete test coverage for middleware/recorder.go 2015-08-14 09:59:22 +05:30
Karthic Rao 53c4797606 Initial setup of test for recorder.go of middleware package 2015-08-11 22:02:13 +05:30
Matthew Holt 60b6c0c03d Add link to joshix/caddy in readme 2015-08-10 16:33:36 -06:00
Matt Holt 5f3ef9c0da Merge pull request #215 from Karthic-Hackintosh/master
Initial test for middleware/middleware.go
2015-08-10 07:26:56 -06:00
Maxime bb5a322ce2 Added lumberjack library for log rolling 2015-08-08 16:13:10 +02:00
Karthic Rao bb072faeee Initial test for middleware/middleware.go 2015-08-08 00:56:59 +05:30
Matthew Holt a2be7b4548 Bumped version to 0.7.5 2015-08-05 13:26:12 -06:00
Matthew Holt 7796ff0f69 core: Disable TLS for http sites (again)
Fixes bug introduced in 0ac8bf5 - Also note that setup functions no longer have access to server port. Will need to fix later.
2015-08-05 11:33:51 -06:00
Matt Holt afd6b7ea27 Merge pull request #210 from evermax/master
log, errors: Support system log (except Windows)

Specify log file of 'syslog' to output to system log
2015-08-05 10:25:49 -06:00
Matthew Holt b62de4c521 New template action NowDate to get the time.Time of Now() 2015-08-05 08:27:13 -06:00
Matthew Holt 2e8a74ecff markdown: Prefix log messages, and slight refactor
Also change sha1 to md5 for the directory scans; slightly faster.
2015-08-05 08:15:52 -06:00
Matthew Holt e94e90b046 Add new docker link, DO link, and enhanced comment 2015-08-05 07:37:51 -06:00
Matthew Holt 7173764d6d markdown: Render lists as part of summary (upstream bug workaround)
See github.com/russross/blackfriday/issues/189
2015-08-05 07:37:51 -06:00
Matthew Holt e4643f048a core: Bind all listeners to wildcard host by default (closes #208)
This behavior can still be overridden by bind directive
2015-08-05 07:37:51 -06:00
Matt Holt 236c8c4eaf Merge pull request #214 from abiosoft/master
markdown: generate static sites after links
2015-08-05 07:24:56 -06:00
Abiola Ibrahim 3b910645e7 Markdown: generate static sites after links. 2015-08-05 09:55:04 +01:00
Matt Holt 9669363504 Merge pull request #213 from abiosoft/master
markdown: Watch for file changes for links. Removed sitegen requirement for link index.
2015-08-04 18:06:37 -06:00
Abiola Ibrahim b5d79bdccc Markdown: Removed unused constant. 2015-08-05 01:01:57 +01:00
Abiola Ibrahim 1d3d705aae Markdown: Rename 'development' to 'dev' in config. 2015-08-05 00:49:42 +01:00
Abiola Ibrahim 2ab466599d Markdown: Modify development mode to generate links on page requests. 2015-08-05 00:41:04 +01:00
Abiola Ibrahim 851026d3fa Markdown: Watch for file changes. Removed sitegen dependency for links. 2015-08-04 23:35:09 +01:00
Matthew Holt 32da2ed706 Updated change log 2015-08-03 17:51:33 -06:00
Matthew Holt d8c50264cc Fix parser when address is empty token
Bug detected by go-fuzz (more fuzzing is required; need larger corpus)
2015-08-03 17:31:10 -06:00
Matthew Holt 8d81ae88da fastcgi: Add HTTPS env variable on HTTPS connections 2015-08-03 17:16:24 -06:00
Maxime f1f1eb040a Changed the facility of syslog to be LOCAL0 2015-08-03 12:00:17 +02:00
Matthew Holt 36fa6e857b markdown: Sitegen only occurs if directory exists
We do this by returning an error and stopping link generation (which scans the markdown path for files)
2015-08-01 16:09:10 -06:00
Matthew Holt b401267aa4 License is a text file 2015-08-01 16:07:59 -06:00
Matthew Holt e63b83c8c4 markdown: report errors during initial sitegen 2015-08-01 13:08:55 -06:00
Matthew Holt 0ac8bf58ea core: Run startup/shutdown functions only once
Even if defined for multiple hosts. Startup or shutdown callbacks registered by any directive (startup, shutdown, markdown, git, log, etc.) will only run as many times as it appears in the Caddyfile, not repeated for each host that shares that server block. Fixing this involved refactoring three packages (yeesh) and we need to restore some tests that are no longer valid (that used to verify splitting a multiServerBlock into multiple serverBlocks).
2015-08-01 13:08:31 -06:00
Maxime 7fb1c7e91d Change access log level to INFO 2015-07-30 22:01:15 +02:00
Maxime 60690c78ae Merge branch 'master' of https://github.com/mholt/caddy 2015-07-30 20:32:15 +02:00
Maxime 4b92808bbf Add syslog support for log and errors directives 2015-07-30 20:29:41 +02:00
Matthew Holt 73397a0973 New version 2015-07-30 10:29:28 -06:00
Matthew Holt 35e25be1a4 browse: Size displayed in IEC format
Powers of 2, or base 1024, rather than powers of 10 or base 1000. The powers of 2 are technically more accurate.
2015-07-29 19:15:38 -06:00
Matthew Holt f7129b219e Fix markdown tests that I broke 2015-07-29 17:47:33 -06:00
Matthew Holt e1153f8797 Update change list... 2015-07-29 17:43:00 -06:00
Matthew Holt ff28bc8b0a markdown: Change .Url -> .URL, increase summary length
Also, summary truncated at nearest space instead of middle of word, and code spans become part of summary.
2015-07-29 17:43:00 -06:00
Matthew Holt 0b01489f7d templates: Date -> Now, add Replace and Truncate 2015-07-29 17:43:00 -06:00
Matt Holt 7a69770026 Merge pull request #207 from evermax/master
markdown: Added test on static files generation
2015-07-29 16:58:14 -06:00
Maxime ec51e14451 Merge branch 'master' of https://github.com/mholt/caddy 2015-07-29 23:43:26 +02:00
Matt Holt 86e9749d6c Merge pull request #204 from abiosoft/master
markdown: Add .Links action and flattened metadata structure

.Links available only for generated sites and variables no longer in a [variables] category in metadata (flat structure).
2015-07-29 11:12:02 -06:00
Abiola Ibrahim aa89f30f2a renamed sorter to byDate. 2015-07-29 18:06:53 +01:00
Maxime da794a866e Change date of the files in test to serve the static file and not generate a new one 2015-07-29 18:00:08 +02:00
Maxime 705cd16dee Fixed path issue: was absolute path but should be relative path 2015-07-29 17:12:32 +02:00
Maxime 0168a627a4 Added test on markdown static files generation 2015-07-29 16:21:35 +02:00
Abiola Ibrahim 7b29568eb1 Code cleanups.
Fix more race conditions.
2015-07-29 12:21:34 +01:00
Matthew Holt a585379bbb proxy: Parse error if property is unknown 2015-07-28 13:50:40 -06:00
Abiola Ibrahim e240cd5ba2 Metadata variables flattened.
Fix race condition on parsers.
Added page links generator.
2015-07-28 05:21:09 +01:00
Matt Holt e043bbdd62 Merge pull request #203 from Karthic-Hackintosh/master
Test setup func for fastcgi.go
2015-07-27 09:01:18 -06:00
Karthic Rao 094436c23a Complete Test set For config/setup/fastcgi.go 2015-07-27 20:19:20 +05:30
Matt Holt c6b2600c62 Merge pull request #202 from evermax/master
markdown: Change metadata variables type to map[string]string

Instead of map[string]interface{}
2015-07-26 12:03:14 -06:00
Maxime d1eb2ea9e2 Changed metadata variables type:
from map[string]interface{} to map[string]string
2015-07-26 18:32:34 +02:00
Matthew Holt 453d3eb567 markdown: Fix when md file has front matter but empty body 2015-07-25 15:47:33 -06:00
Matthew Holt 53b7b131cb Update change list, and note in comment 2015-07-25 15:18:10 -06:00
Matthew Holt bec9b9a3f7 redir: Redirect tables
Open a redir block to bulk-specify a bunch of redirects that share a status code
2015-07-25 15:18:10 -06:00
Matt Holt bf47951f3a Merge pull request #196 from evermax/master
markdown, browse: Integrated Context struct for templating
2015-07-25 15:15:51 -06:00
Maxime 604c8abb59 Remove debug line, add file name as default title 2015-07-25 22:39:13 +02:00
Maxime ef4a4b0ab8 Removed attribute not needed. 2015-07-24 22:17:14 +02:00
Maxime 24bdb433c9 Changed .Var to .Doc in Markdown templates
Put the title into the .Doc variables as well.
Changed the test template file to use new names.
2015-07-24 22:14:05 +02:00
Matthew Holt 6006de5795 Update change list 2015-07-24 10:27:19 -06:00
Matthew Holt a578c43810 browse: Add txt files to list of default files 2015-07-24 10:27:19 -06:00
Matthew Holt 74b758034e redir: Allows replacements; defaults to exact match redirects
This is a breaking change for those who expect catch-all redirects to preserve path; use {uri} variable explicitly now
2015-07-24 10:27:19 -06:00
Matthew Holt 04571ff393 NewReplacer takes third argument for empty value string 2015-07-24 10:27:19 -06:00
Matt Holt 7adff28aa9 Merge pull request #198 from pyed/sort
browse: Save sorting preferences as cookies
2015-07-23 11:06:56 -06:00
pyed 1589129ea1 Save sorting preferences as cookies
To be used across directories
2015-07-23 13:25:03 +03:00
Maxime 97dcc79a7f Remove undesired committed debug logs 2015-07-23 11:53:15 +02:00
Maxime bc2feece4b Moved test files to testdata folder.
Changed the tests accordingly.
2015-07-23 09:35:46 +02:00
Matt Holt 1f17895b12 Merge pull request #197 from Karthic-Hackintosh/master
initial test for config/setup/fastcgi.go
2015-07-22 23:25:04 -06:00
Karthic Rao 665f24d85f initial test for config/setup/fastcgi.go 2015-07-23 10:27:42 +05:30
Maxime 2df30d186e Added a test on markdown for the default template 2015-07-21 17:45:32 +02:00
Maxime 6451e10d3e Add context to markdown template
Created a struct containing middleware.Context, Title, Markdown and the
variables from the user to use to render the template.
The title now can be accessed via {{.Title}}.
The variables can now be accessed via {{.Var.myVariableName}}.
2015-07-21 07:58:34 +02:00
Matt Holt d222a9e9f2 Merge pull request #192 from netmml/patch-2
specify go versions to use via travis
2015-07-19 23:13:49 -06:00
Matthew Holt 00997db5ae markdown: Fix large markdown files that got truncated 2015-07-18 12:57:16 -06:00
Maxime 2d5320c454 Added test for the browse directive
Created sample files for the test
2015-07-18 11:37:05 +02:00
Maxime 2fa6e278d2 Merge branch 'master' of https://github.com/mholt/caddy 2015-07-18 09:58:27 +02:00
netmml ef381fbb54 Update .travis.yml 2015-07-18 03:37:22 -04:00
Matthew Holt a74b20f278 .zip for Win/Mac, .tar.gz for Linux/BSD 2015-07-17 17:26:33 -06:00
Maxime f536bc94b2 Added the Context to the browse directive
Moved the Context type to middleware and exported it.
Users can use .Include and others in browse directive templating
Created test for the templates directive.
2015-07-17 20:07:24 +02:00
Matthew Holt 95140f948f version bump 0.7.3 2015-07-15 09:52:47 -06:00
Matthew Holt afc540f6b7 Updated changes 2015-07-15 09:36:34 -06:00
Matt Holt fcf2622c26 Merge pull request #187 from evermax/master
redir: Preserve query string on catch-all redirect (fixes #180)
2015-07-13 09:42:19 -06:00
Maxime d9ebc5398a Changes regarding review
Use path.Join and then check if the request had a slash at the end to
place it again.
2015-07-12 21:22:15 +02:00
Maxime eea68c34ad Changes regarding comment.
Used http status code instead of a hardcoded value.
Used url.Parse instead of url.ParseRequestURI, so that you can parse
both absolute and relative URL.
2015-07-12 16:43:35 +02:00
Maxime 8a2d0890a2 Changes regarding issue 180
The get parameters are now forwarded when redirected.
Added some tests to validate this behavior.
2015-07-12 16:01:32 +02:00
Matthew Holt 1a82943db2 core: Simplify Server initializer 2015-07-11 12:00:21 -06:00
Matt Holt 3c36edec1f Merge pull request #185 from Karthic-Hackintosh/master
Complete test coverage for config/setup/markdown.go
2015-07-11 10:39:52 -06:00
Matt Holt 0f913e89db Merge pull request #183 from peterhellberg/extensions_tests
ext: Initial tests for the extensions middleware
2015-07-11 10:38:42 -06:00
Karthic Rao 2b7ec1b023 Complete test coverage for config/setup/markdown.go 2015-07-11 00:44:31 +05:30
Peter Hellberg 33fa29fda0 extensions: Initial tests 2015-07-10 12:05:06 -06:00
Matthew Holt d3c229375c Fixed import command, added tests 2015-07-07 22:38:48 -06:00
Matthew Holt c82d7c2dd2 templates: Better error handling for missing files 2015-07-06 23:37:27 -06:00
Matt Holt 553d76dab3 Merge pull request #179 from abiosoft/master
gzip: Const for wildcard extension (and added test)
2015-07-05 23:46:30 -06:00
Abiola Ibrahim d4f0ac2303 Merge remote-tracking branch 'upstream/master' 2015-07-06 06:43:02 +01:00
Abiola Ibrahim 4588812d24 Gzip: Fix wildcard extension bug. 2015-07-06 06:36:48 +01:00
Matthew Holt 9467dbdd40 Fix errors tests 2015-07-05 23:23:35 -06:00
Matthew Holt 68c416e414 gzip: Fix to allow wildcard extension 2015-07-05 23:23:21 -06:00
Matthew Holt 71c4fdbc85 errors: Prepend timestamp to log entry 2015-07-05 23:20:37 -06:00
Matt Holt 7dbe42286d Merge pull request #174 from mholt/gzipfix
gzip: Make it gzip again

Removes mimes filter in favor of extensions filter (might be temporary)
2015-07-05 23:19:51 -06:00
Matthew Holt b5579ca910 gzip: Remove mimes 2015-07-03 18:13:30 -06:00
Matt Holt cd2b255ae3 Merge pull request #177 from Karthic-Hackintosh/master
preliminary test case for config/setup/markdown.go
2015-07-03 13:29:53 -06:00
Karthic Rao 93f29a7598 preliminary test case for config/setup/markdown.go 2015-07-03 10:03:22 +05:30
Matthew Holt 32ef35b952 gzip: Fix tests 2015-07-01 19:05:31 -06:00
Matthew Holt abf22909f1 gzip: Make it gzip again 2015-07-01 18:56:30 -06:00
Matthew Holt a86e16ddde That's a wrap; version 0.7.2 2015-07-01 12:09:31 -06:00
Matt Holt 263fa064cd Merge pull request #169 from abiosoft/master
git: Remove from core (available as add-on)
2015-07-01 11:44:49 -06:00
Matthew Holt 915172e9ef templates: Close files after done including them 2015-07-01 11:36:37 -06:00
Matthew Holt 4d066b7e30 ext: Only append extension if request is not for directory (fixes #173) 2015-07-01 11:35:52 -06:00
Abiola Ibrahim a7f0705bcf Merge remote-tracking branch 'upstream/master' 2015-07-01 14:01:45 +01:00
Matthew Holt 16f18bfeff Update change list 2015-06-30 18:21:18 -06:00
Matthew Holt 7a42e60bcb templates: Support for nested include files
i.e. included files are also parsed as templates
2015-06-30 18:21:18 -06:00
Abiola Ibrahim aecdecbdf8 Merge remote-tracking branch 'upstream/master' 2015-06-30 17:31:40 +01:00
Matt Holt a790db134d Merge pull request #171 from bigtiger/patch-1
Massage some verbiage
2015-06-30 10:24:59 -06:00
Matt Holt 75b77d508d Merge pull request #172 from abiosoft/fastcgi-fix
fastcgi: Fix for Issue 141: index not found, 502 Bad Gateway.
2015-06-30 10:23:49 -06:00
Abiola Ibrahim 4240817a3a Fix for Issue 141: index not found, 502 Bad Gateway. 2015-06-30 11:54:50 +01:00
Jim Remsik 24307e85b7 Massage some verbiage
The grammar felt off on the sentence I changed. You can take or leave this PR, I won't be offended if you dislike my suggestion.
2015-06-29 16:52:40 -07:00
Abiola Ibrahim b030d0cf79 Merge remote-tracking branch 'upstream/master' 2015-06-29 09:16:59 +01:00
Matt Holt b0d3a8e1b5 Merge pull request #170 from Karthic-Hackintosh/master
Complete test coverage for config/setup/templates.go
2015-06-28 10:00:33 -06:00
Karthic Rao 62456c1bed Complete test coverage for config/setup/templates.go 2015-06-28 07:03:42 +05:30
Abiola Ibrahim 3f1f6720ee Decouple git middleware from caddy core. Now available as an add-on at https://github.com/abiosoft/caddy-git. 2015-06-28 00:31:52 +01:00
Matthew Holt ab0cbf3e12 Updated change list 2015-06-26 16:32:49 -06:00
Matt Holt c12257a1a7 Merge pull request #168 from Karthic-Hackintosh/master
Initial Test case for config/setup/templates.go
2015-06-26 16:23:39 -06:00
Karthic Rao e039577d66 export of variables undone 2015-06-27 03:49:44 +05:30
Karthic Rao 9995466a18 Initial Test case for config/setup/templates.go 2015-06-27 02:27:02 +05:30
Matthew Holt f424f450f2 Fix typo 2015-06-25 08:55:02 -06:00
Matt Holt 677f67db48 Merge pull request #166 from mholt/graceful-resolve
core: Handle address lookup and bind errors more gracefully (fixes #136 and #164)
2015-06-25 08:18:41 -06:00
Matthew Holt 47d1f5eecf Removed tests that are not cross-platform compatible 2015-06-23 22:13:29 -06:00
Matthew Holt d8391d6fbd core: Handle address lookup and bind errors more gracefully (fixes #136 and #164)
Addresses which fail to resolve are handled more gracefully in the two most common cases: the hostname doesn't resolve or the port is unknown (like "http" on a system that doesn't support that port name). If the hostname doesn't resolve, the host is served on the listener at host 0.0.0.0. If the port is unknown, we attempt to rewrite it as a number manually and try again.
2015-06-23 22:01:37 -06:00
Matt Holt 640cd059ce Merge pull request #165 from Karthic-Hackintosh/master
Test for webSocketParse function in config/setup
2015-06-23 20:04:07 -06:00
Karthic Rao a78cea7d8a test for webSocketParse function in config/setup 2015-06-24 07:05:53 +05:30
Matt Holt 7044cbbd67 Merge pull request #161 from Karthic-Hackintosh/master
Modularizing config/setup/Websocket.go and test file for the same
2015-06-23 10:06:05 -06:00
Karthic Rao 292c15cd48 Modularizing config/setup/Websocket.go and for the same 2015-06-23 14:40:27 +05:30
Matt Holt 06a7f1d3da Merge pull request #160 from guilhermebr/master
errors: change missing errors file from error to a warning during parsing
2015-06-22 20:38:23 -06:00
Guilherme Rezende efbf01b49d change from error to a warning in errors setup 2015-06-22 23:19:48 -03:00
Matthew Holt 4b349805db browse: better-looking sort order arrows 2015-06-22 15:04:22 -06:00
Matt Holt 47096e112a Merge pull request #156 from pyed/sort
browse: Ability to sort
2015-06-22 12:40:58 -06:00
pyed 68add78230 Implement sorting functionality for "Browse" 2015-06-21 18:04:47 +03:00
Matt Holt ebae65b6af Merge pull request #157 from abiosoft/master
setup: export functions and variables for external packages.
2015-06-20 10:14:15 -06:00
Abiola Ibrahim 460c0c8a42 setup: export functions and variables for external packages. 2015-06-20 14:59:33 +01:00
Matt Holt 528d1b03f1 Merge pull request #155 from Karthic-Hackintosh/master
Tests for config/setup/internal.go
2015-06-19 12:27:43 -06:00
Karthic Rao 9d33d9d6b0 typo corrected 2015-06-19 23:54:34 +05:30
Karthic Rao d9729b4a2e test for config/setup/internal.go 2015-06-19 23:34:54 +05:30
Matt Holt 1db6c244bb Merge pull request #153 from Karthic-Hackintosh/master
Tests for config/setup/log.go
2015-06-19 11:35:01 -06:00
Karthic Rao 13c5d25a2e Typo corrected 2015-06-19 22:30:48 +05:30
Karthic Rao c166261513 more test cases for the test struct 2015-06-19 19:37:48 +05:30
Karthic Rao 84998a4d19 Tests for config/setup/log.go 2015-06-19 19:15:57 +05:30
Matt Holt 6b27d4ce11 Merge pull request #131 from slav123/master
fastcgi: fix #127
2015-06-17 17:26:52 -06:00
Slawomir Jasinski f11e136068 Update fcgiclient.go 2015-06-18 09:12:35 +10:00
Matt Holt 707ea554ac Merge pull request #149 from Karthic-Hackintosh/master
ext: test for function extParse
2015-06-17 07:01:26 -06:00
Karthic Rao 65f7190030 more cases added to test struct in extParse test 2015-06-17 14:07:26 +05:30
Karthic Rao a5a5c06716 test for function extParse written for config/setup/ext_go 2015-06-17 12:48:52 +05:30
Karthic Rao 9c832893af Merge branch 'master' of https://github.com/mholt/caddy 2015-06-17 12:46:34 +05:30
Karthic Rao 4e15901df1 solving merge conflicts 2015-06-17 12:46:12 +05:30
Karthic Rao 9a32d08e9f test for function exParse under config/setup/ext.go 2015-06-17 11:59:24 +05:30
Matthew Holt c811d416a7 log: Customizable default error function 2015-06-15 10:17:09 -06:00
Matt Holt 92391bfdf9 Merge pull request #135 from simonjefford/ensure_correct_log_status
log: ensure the correct status is always logged
2015-06-15 09:25:29 -06:00
Simon Jefford 6c1f2af53a log: ensure the correct status is always logged
in the case of error (>=400) then no response may have been sent
2015-06-14 21:00:27 +01:00
Matt Holt 7875f98b71 Merge pull request #138 from Karthic-Hackintosh/master
Preliminary test case for setup/config/ext.go
2015-06-13 09:01:47 -06:00
karthic rao 076fc4d72c Update
Update with missing assertion for the Next middleware being properly set , this makes sure that Ext middleware carries the requests to the further middlewares .
2015-06-13 20:25:23 +05:30
karthic rao d7db1b9576 Added Assertions
Added assertions to check for the extensions in the order specified
2015-06-13 15:56:29 +05:30
karthic rao 2a166f088d Update ext_test.go 2015-06-13 10:05:42 +05:30
Karthic Rao 2175c68319 Preliminary test case for setup/config/ext.go 2015-06-13 09:54:54 +05:30
Matthew Holt 3418770fe1 Latest change list 2015-06-12 08:30:53 -06:00
Matt Holt d7051e986f Merge pull request #134 from tw4452852/hijack
middleware: let middleware.responseRecorder be a http.Hijacker
2015-06-12 08:26:49 -06:00
Matthew Holt f36d9bfa2a Add dist/release build script and licenses 2015-06-11 23:17:11 -06:00
Tw e79a88856a let middleware.responseRecorder be a http.Hijacker
Signed-off-by: Tw <tw19881113@gmail.com>
2015-06-12 11:42:28 +08:00
Slawomir Jasinski db2368cd0b Removed parentheses #127 2015-06-12 11:33:55 +10:00
Matt Holt 0f9d26829c Merge pull request #130 from abiosoft/master
Gzip: Accept MIME types.
2015-06-11 19:27:02 -06:00
slav123 29404e34d9 code cleanup 2015-06-11 13:17:56 +10:00
Slawomir Jasinski 14b64fef43 fix #127
fixes issue with Status header coming from php-fpm 5.5 different then regular "HTTP/1.1 200 OK".
If server returns  Status code - "200" will be handled properly instead "throwing runtime error: index out of range"
2015-06-11 09:59:30 +10:00
Abiola Ibrahim e0f10c2b03 Gzip: Accept MIME types. 2015-06-10 22:02:08 +01:00
Matt Holt 01aca02edc Merge pull request #129 from tw4452852/rename_internal
internal: rename middleware's package name from `internal` to `inner`
2015-06-10 08:04:27 -06:00
Matt Holt 5cdfa0aaaf Merge pull request #128 from coolaj86/meta-redirects
redirect: add ability to do meta redirects
2015-06-10 08:03:59 -06:00
Tw 90921a9deb rename middleware's name from internal to inner
The internal package has the special meaning in go
(see https://golang.org/s/go14internal).
So rename it to `inner`.

Signed-off-by: Tw <tw19881113@gmail.com>
2015-06-10 15:48:41 +08:00
AJ ONeal d6a7dfc1a5 add ability to do meta redirects
Proper Location redirects are disadvantageous in some situations.
For example, you may want a developer to know that a resource is available
via https, but you don't want an insecure call to the API to succeed.
2015-06-10 05:48:40 +00:00
Matthew Holt 00093a2052 core: Fix to allow empty (wildcard) host 2015-06-09 23:07:32 -06:00
Matt Holt 3a795de828 Merge pull request #117 from zmb3/errorfmt
Update error strings (start with lowercase letters)
2015-06-09 08:24:08 -06:00
Matt Holt cde60ed6b2 Merge pull request #121 from tw4452852/test_errors
errors: Add unit test for errors middleware
2015-06-09 08:06:09 -06:00
Tw b717e6f2d8 Add unit test for errors middleware
Signed-off-by: Tw <tw19881113@gmail.com>
2015-06-09 15:24:53 +08:00
Zac Bergquist 3aff1677cc Fix failing test that's looking for a specific error message. 2015-06-08 20:29:48 -04:00
Zac Bergquist 9e97d79c81 Ensure that proper names are capitalized in error strings. 2015-06-08 17:35:16 -04:00
Matt Holt 47415937dd Merge pull request #118 from mholt/jumanjiman-docker
Add link to Docker container jumanjiman/caddy
2015-06-08 08:24:20 -06:00
Matthew Holt aed649475e Add link to Docker container jumanjiman/caddy 2015-06-07 20:22:16 -06:00
Zac Bergquist 41e1f1ffa5 Update error strings (start with lowercase letters) 2015-06-07 20:49:17 -04:00
Matt Holt 20b6e971c0 Merge pull request #116 from abiosoft/master
Git: fix for data races.
2015-06-07 14:32:20 -06:00
Abiola Ibrahim c42e60a3d2 Git: fix for data races. 2015-06-07 20:39:24 +01:00
Matt Holt 995a2ea618 Merge pull request #112 from abiosoft/master
Gzip: Added compression level, extension and path filters.
2015-06-06 18:37:59 -06:00
Abiola Ibrahim 13db60d382 rename gzip test function to TestGzipHandler 2015-06-07 01:27:36 +01:00
Abiola Ibrahim c9233d7446 Gzip: Added compression level, extension and path filters. 2015-06-07 01:21:54 +01:00
Matt Holt 6080c4fab1 Merge pull request #105 from abiosoft/master
FastCGI: allow "unix:" prefix for unix sockets.
2015-06-04 16:09:08 -06:00
Abiola Ibrahim 820b2af43a FastCGI: allow "unix:" prefix for unix sockets. 2015-06-04 23:03:58 +01:00
Matt Holt 1cb0053720 Merge pull request #102 from brk0v/proxy-without
proxy: Add "without" option
2015-06-04 08:24:48 -06:00
Viacheslav Biriukov 822a615c6c rollback tests 2015-06-04 14:02:52 +00:00
Viacheslav Biriukov 593557659c fix tests and change naming 2015-06-04 13:57:39 +00:00
Matt Holt c6e2b9ccc0 Merge pull request #99 from xenolf/github-deploy
git: Webhook middleware for GitHub events
2015-06-03 19:30:20 -06:00
xenolf b4780a41d3 Added webhook functionality to the git middleware.
The webhook providers reside behind a small interface which determines if
a provider should run. If a provider should run it delegates
responsibility of the request to the provider.
ghdeploy initial commit

Added webhook functionality to the git middleware.
The webhook providers reside behind a small interface which determines if a provider should run. If a provider should run it delegates responsibility of the request to the provider.

Add tests

Remove old implementation

Fix inconsistency with git interval pulling.

Remove '\n' from logging statements and put the initial pull into a startup function
2015-06-04 03:24:16 +02:00
Viacheslav Biriukov 4790dacbf7 add without to proxy middleware 2015-06-03 18:06:24 +00:00
Matthew Holt 4852f0580b Bump to 0.7.1 2015-06-02 12:19:08 -06:00
Matt Holt 9d7639a9d3 Merge pull request #98 from mholt/clientauth
tls: Client authentication
2015-06-02 12:05:05 -06:00
Matt Holt 6c52368124 tls: Fix format string 2015-06-02 07:42:38 -06:00
Matthew Holt c78eb50eb8 tls: Client authentication 2015-06-01 23:22:11 -06:00
Matthew Holt 9ce0e8e17c proxy: Added tests for reverse websocket proxy 2015-06-01 22:39:53 -06:00
Matthew Holt 32825e8a79 basicauth: Patch timing vulnerability 2015-06-01 20:33:07 -06:00
Matthew Holt cb8691a381 Add abiosoft's docker container to the list 2015-06-01 20:33:07 -06:00
Matt Holt 5ddcd60332 Merge pull request #96 from acmacalister/feature/ws-proxy
add supported for ws in reverse proxy
2015-06-01 20:31:41 -06:00
Austin 68cd4bdeab check server response instead of client 2015-06-01 19:29:32 -07:00
Austin ccd3e55b32 changes as noted in PR 2015-06-01 10:23:57 -07:00
Austin 56ec7b9887 websocket directive, upgrade comparison 2015-05-30 11:34:54 -07:00
Austin 2d6ff40649 add supported for ws in reverse proxy 2015-05-29 19:21:50 -07:00
Matt Holt 9bdd9bdecc Merge pull request #94 from acmacalister/feature/custom-policies
added custom policy support
2015-05-29 12:33:08 -06:00
Austin dd946f8ab5 moved init to policy.go 2015-05-28 18:16:23 -07:00
Austin 593aec9ab1 changes per comment 2015-05-28 16:53:54 -07:00
Austin 6b173b5170 added custom policy support 2015-05-28 15:56:11 -07:00
Matt Holt 130301e32e Merge pull request #92 from abiosoft/master
Git: code refactor. replace Sleep with Ticker
2015-05-28 08:37:13 -06:00
Abiola Ibrahim 2013838bfd Git: mock time functions in tests. 2015-05-28 10:20:26 +01:00
Abiola Ibrahim 879558b9ee Git: code refactor. replace Sleep with Ticker 2015-05-26 20:20:57 +01:00
Matt Holt ee059c0910 Merge pull request #90 from abiosoft/master
Git: More tests. Code refactor.
2015-05-25 22:15:56 -06:00
Abiola Ibrahim 6c6e0e3f73 Git: More tests. Code refactor. 2015-05-26 04:44:47 +01:00
Matthew Holt b1c8b48e6e core: Version 0.7.0 2015-05-25 15:48:25 -06:00
Matthew Holt 6f05794bb8 git: Fixed unusual but potent race condition 2015-05-25 15:39:04 -06:00
Matt Holt 346135fed3 Merge pull request #89 from guilhermebr/master
removed tls cache option
2015-05-25 15:00:36 -06:00
Guilherme Rezende 69939108e1 removed tls cache option 2015-05-25 14:42:09 -03:00
Matthew Holt 674f454e70 t.Fatal -> t.Fatalf 2015-05-25 08:27:54 -06:00
Abiola Ibrahim a881838836 Merge pull request #88 from zmb3/lintwarnings
Fix lint warnings
2015-05-25 06:28:25 +01:00
Zac Bergquist e4b50aa814 Fix more lint warnings 2015-05-24 22:52:34 -04:00
Zac Bergquist fd8490c689 Fix lint warnings for middleware/websockets 2015-05-24 21:04:03 -04:00
Zac Bergquist d0a51048d7 Fix lint warnings in middleware/rewrite 2015-05-24 21:00:54 -04:00
Zac Bergquist 506f131428 Fix lint warnings for middleware/proxy 2015-05-24 20:58:17 -04:00
Matthew Holt e42c6bf0bb Updated readme 2015-05-23 14:29:59 -06:00
Abiola Ibrahim 535f956682 Merge pull request #86 from abiosoft/master
FastCGI: support for unix sockets.
2015-05-23 03:46:47 +01:00
Abiola Ibrahim 4e94b85ec2 FastCGI: support for unix sockets. 2015-05-23 03:39:23 +01:00
Abiola Ibrahim 6b3b04ffb7 Merge pull request #85 from jpoehls/master
Tweaked ulimit warning message.
2015-05-23 02:37:45 +01:00
Joshua Poehls 36bc3a453f Tweaked ulimit warning message. 2015-05-22 20:24:48 -05:00
Matt Holt 99c069df15 Merge pull request #84 from jpoehls/master
Updated ulimit warning message to include the recommended min value.
2015-05-22 18:50:12 -06:00
Joshua Poehls 04fd7ce9e1 Updated ulimit warning message to include the recommended min value. 2015-05-22 19:34:00 -05:00
Matt Holt cc958947e5 Merge pull request #83 from abiosoft/master
Git: Minor fixes. Refactor. Added tests.
2015-05-22 17:13:29 -06:00
Abiola Ibrahim f44cd5d740 Git: Minor fixes. Refactor. Added tests. 2015-05-22 20:50:04 +01:00
Matthew Holt bba2d63de8 Updated contribution guidelines 2015-05-22 11:08:24 -06:00
Matthew Holt 3420bd6e06 Another test case for parser 2015-05-21 23:50:37 -06:00
Matthew Holt b10d846019 More parser tests 2015-05-21 23:36:17 -06:00
Matthew Holt 11ddb5c6ca Tests for the parser 2015-05-21 18:46:42 -06:00
Matthew Holt b37fed4cc8 Beginning some tests for the parser 2015-05-21 16:31:01 -06:00
Matthew Holt 5397eef234 Added tests for allTokens 2015-05-21 15:42:49 -06:00
Matthew Holt d311345aa5 Fix for running ulimit check 2015-05-21 11:21:08 -06:00
Matthew Holt d6df615588 tls: Mainstream compatibility improvements, better security rating 2015-05-21 10:37:39 -06:00
Matthew Holt ce6e30c09e Lil' bit of godoc 2015-05-21 00:40:58 -06:00
Matthew Holt ee754b4a47 Bug fixes 2015-05-21 00:40:05 -06:00
Matthew Holt 5f72b7438a Created app package, and better TLS compatibility with HTTP/2 2015-05-21 00:06:53 -06:00
Matthew Holt ea9607302a Whoops 2015-05-20 20:50:19 -06:00
Matthew Holt 26bb17337e Warn if ulimit is too low when serving production sites 2015-05-20 20:46:27 -06:00
Matthew Holt 5e8491cf7f Allow IPv6 address without port (fixes #80) 2015-05-20 20:15:39 -06:00
Matthew Holt e42c6ab520 Notice displayed if non-localhost hosts resolve to loopback 2015-05-20 20:06:30 -06:00
Matt Holt 9c039474b3 Merge pull request #78 from guilhermebr/master
tls: Optional block for ciphers, protocols and cache options
2015-05-18 22:57:38 -06:00
Guilherme Rezende b378316103 replace c.ArgErr with c.Err in tls when is the case 2015-05-18 23:27:35 -03:00
Guilherme Rezende a94c7dd788 change tls ssl3 protocol key and tests 2015-05-18 18:15:41 -03:00
Guilherme Rezende 823a7eac03 Added tls option block including: ciphers, protocols and cache options
Signed-off-by: Guilherme Rezende <guilhermebr@gmail.com>
2015-05-18 16:38:21 -03:00
Matt Holt cf2808ae45 Merge pull request #75 from abiosoft/master
Rewrite: Support for Regular Expressions.
2015-05-16 18:44:20 -07:00
Abiola Ibrahim c382c885e4 use middleware.Path for base path comparison 2015-05-16 16:57:57 +01:00
Abiola Ibrahim 7ae9e3a262 Rewrite: Added new variables file, dir, fragment. 2015-05-16 16:30:15 +01:00
Abiola Ibrahim 74d162f377 Rewrite: Support for Regular Expressions. 2015-05-16 13:03:48 +01:00
Abiola Ibrahim ad7b453f03 Rewrite: modified syntax. 2015-05-15 18:47:26 +01:00
Abiola Ibrahim b2afc30d12 Rewrite: added regexp. awaiting documentation and tests. 2015-05-15 02:43:29 +01:00
Matt Holt 1076daa8c9 Merge pull request #73 from abiosoft/master
Fix for Issue 72: Markdown: 500 for YAML metadata
2015-05-12 18:04:03 -06:00
Abiola Ibrahim 8394d72f48 Fix for Issue 72: Markdown: 500 for YAML metadata 2015-05-13 00:44:35 +01:00
Abiola Ibrahim 018fd21741 Merge pull request #71 from abiosoft/master
browse: return forbidden (403) only when it is a permission error.
2015-05-10 17:59:55 +01:00
Abiola Ibrahim a1312465b5 browse: return forbidden (403) only when it is a permission error. 2015-05-10 17:58:44 +01:00
Matt Holt e2273ea676 Merge pull request #69 from peterhellberg/headers_test
headers: Initial test for Headers and change of Rule.Url to Rule.Path
2015-05-10 07:49:11 -06:00
Matt Holt c6eaf0db36 Merge pull request #62 from jordic/testing_basicauth
basicauth: Should write an unauthorized code to response. (Actually only returns it)
2015-05-10 07:47:16 -06:00
Michael Schöbel a0eca49795 Merge pull request #70 from mschoebel/internal_middleware_tests
Internal middleware: added tests
2015-05-10 14:57:16 +02:00
Michael Schoebel e0173ec4c7 internal middleware: added tests 2015-05-10 14:41:48 +02:00
jordi collell 99fa4581aa basicauth: patch for overlapping rules 2015-05-10 08:20:58 +02:00
Peter Hellberg 37b1a81fc7 headers: Corrected copy paste (BasicAuth->Headers) 2015-05-10 07:44:43 +02:00
Matthew Holt 4272536518 markdown: sitegen keyword and run generation at startup
Also fixed bug for markdown files that don't contain front matter
2015-05-09 21:12:52 -06:00
Peter Hellberg 0d5a8a7383 headers: Test for Headers and headersParse funcs 2015-05-09 23:10:18 +02:00
Peter Hellberg df6efe5d88 headers: Replaced usage of Url to Path in setup 2015-05-09 21:57:55 +02:00
Peter Hellberg d9dc9326f2 headers: Initial test for Headers 2015-05-09 21:47:02 +02:00
Peter Hellberg b5fff09b54 headers: Changed Rule.Url to Rule.Path
Updated ServeHTTP comment to indicate that it is 
setting headers and not adding them to existing values.
2015-05-09 21:45:28 +02:00
Matt Holt a96c4d707b Merge pull request #67 from peterhellberg/redirect_test
redirect: initial test for Redirect
2015-05-09 12:21:59 -06:00
Peter Hellberg 0f9df18dfb Check if the Next handler was called unexpectedly
Removed body check and replaced urlPrinter with a inlined Next handler
2015-05-09 20:07:29 +02:00
Matthew Holt 8ea98f8cce markdown: Fix panic: assignment to entry in nil map
Ensures metadata.Variables is made
2015-05-09 11:49:28 -06:00
Matt Holt f2f7e6825f Merge pull request #68 from abiosoft/master
Markdown: support for templates and metadata
2015-05-09 09:13:40 -06:00
Abiola Ibrahim 2f5e2f39cb markdown: remove identifier from Json metadata 2015-05-09 11:49:54 +01:00
jordi collell 4c11854927 added header match and a new failing test 2015-05-09 08:11:02 +02:00
Abiola Ibrahim 6ce83aad2b markdown: Refactor fixes 2015-05-09 00:54:39 +01:00
Matt Holt 2743f97892 Merge pull request #66 from stanislavromanov/master
Close #64 (Make it possible to remove headers)
2015-05-08 17:26:36 -06:00
Abiola Ibrahim 978aef2ae7 Merge remote-tracking branch 'upstream/master' 2015-05-08 23:51:09 +01:00
Abiola Ibrahim 48a12c605a markdown: Added template support. 2015-05-08 23:45:31 +01:00
Peter Hellberg 95b4e61a07 redirect: initial test for Redirect 2015-05-09 00:36:58 +02:00
Matthew Holt 2501691ea4 lexer: Fixed backslashes in quoted strings (closes #65) 2015-05-08 10:32:57 -06:00
stanislavromanov a5a90fe6fc close #64 2015-05-08 17:47:37 +02:00
Abiola Ibrahim 0fccd3707d markdown: documentation done. awaiting test 2015-05-08 16:20:07 +01:00
jordi collell 253c069b26 if basic auth fails should write unauthorized to response 2015-05-08 09:41:17 +02:00
Abiola Ibrahim 2c7de8f328 Merge remote-tracking branch 'upstream/master' 2015-05-08 06:52:14 +01:00
jordi collell 64d203491c Some failing tests 2015-05-08 07:41:48 +02:00
Matthew Holt b2ee6638e4 Tests for rewrite middleware 2015-05-07 14:55:34 -06:00
Matt Holt 557410ffd7 Merge pull request #58 from mschoebel/internal_middleware
Adding "internal" middleware
2015-05-07 13:28:09 -06:00
Matthew Holt 40105094e7 Some tests and utilities for setup functions 2015-05-07 13:11:03 -06:00
Matthew Holt 0dba8d406b NextBlock() doesn't enter an empty block 2015-05-07 13:10:00 -06:00
Matthew Holt 2ce5102473 Add -version flag 2015-05-07 13:09:40 -06:00
Michael Schoebel e3d64169ed Adapted internal middleware
- Detect too many internal redirects - return 500 in this case
2015-05-07 20:48:29 +02:00
Michael Schoebel a5b565e193 Adapted internal middleware
- redirect internally regardless of proxy status code
- support multiple internal redirects
2015-05-07 20:20:45 +02:00
Abiola Ibrahim 7443fd0973 Merge remote-tracking branch 'upstream/master' 2015-05-07 14:02:40 +01:00
Abiola Ibrahim ba613a1567 markdown: template integration done. awaiting documentation and test 2015-05-07 13:45:27 +01:00
Abiola Ibrahim 0bfdb50ade markdown: working version of template integration. Awaiting static site generation and tests. 2015-05-07 00:19:02 +01:00
Matthew Holt a5f20829cb Added link to Slack channel 2015-05-06 16:39:58 -06:00
Matthew Holt abe3e5f597 Move flag.Parse() to main() in case other packages use flags 2015-05-06 14:57:32 -06:00
Matthew Holt e12428efb9 Add build status 2015-05-06 14:49:24 -06:00
Michael Schoebel 0650dd7171 New internal middleware 2015-05-06 22:44:37 +02:00
Matthew Holt 7c844909b9 fastcgi: Add support for OPTIONS requests 2015-05-06 11:14:02 -06:00
Matthew Holt 898896f9e0 Fix for stdin on Windows 2015-05-06 09:16:10 -06:00
Matthew Holt 63ccc626f9 Fix parsing bug for one-line Caddyfiles 2015-05-06 08:58:15 -06:00
Abiola Ibrahim 434ec7b6ea Merge remote-tracking branch 'upstream/master' 2015-05-06 09:06:14 +01:00
Matthew Holt b2549c317c Minor flag help text change; go fmt 2015-05-05 23:19:14 -06:00
Matthew Holt 20c01883c3 Adding Travis CI manifest 2015-05-05 22:54:13 -06:00
Matthew Holt 340a53fb80 Disabling fcgiclient tests until they can be rewritten 2015-05-05 22:53:14 -06:00
Abiola Ibrahim 25847a6192 markdown: added template codes. awaiting integration and tests 2015-05-06 03:37:29 +01:00
Abiola Ibrahim 69eb5cdd8e Merge pull request #56 from abiosoft/master
server: read -conf flag before reading stdin config
2015-05-06 03:34:25 +01:00
Abiola Ibrahim 70d6caf95b server: read -conf flag before reading stdin config file 2015-05-06 03:31:25 +01:00
Matthew Holt 47717fee88 Expanded index file support to other middlewares (fixes #27) 2015-05-05 15:50:42 -06:00
Matthew Holt a9064a7871 templates: Changed .RemoteAddr to .IP and stripped port 2015-05-05 15:49:22 -06:00
Matthew Holt 21c26f48d0 Ensure a default root is always set in the configs 2015-05-05 15:48:40 -06:00
Matthew Holt 857b4f90d9 errors: Log includes file and line number of panics 2015-05-05 15:48:10 -06:00
Matt Holt 97e702b963 Merge pull request #51 from ChannelMeter/tls/disable-http
tls: suggestion - disable tls if port is http?
2015-05-05 13:35:49 -06:00
Nimi Wariboko Jr accb3e616d Add warning message when tls is disabled when its used on http 2015-05-05 12:30:24 -07:00
Matthew Holt 33786408f0 Startup/shutdown commands now have stdin 2015-05-05 11:20:57 -06:00
Matthew Holt 9a78857b31 Startup/shutdown commands run in background with & 2015-05-05 11:08:45 -06:00
Matt Holt 1e730a74a0 Merge pull request #50 from ChannelMeter/core/bind_address
core: Add the option to specify what address to bind on in Caddyfile
2015-05-05 00:05:42 -06:00
Nimi Wariboko Jr 46f7930787 Rename bindaddr to just bind 2015-05-04 22:58:08 -07:00
Nimi Wariboko Jr 68793ffe13 Disable tls if the port is http 2015-05-04 22:26:28 -07:00
Matt Holt 4637f14b7f Merge pull request #48 from ChannelMeter/proxy/refactor
proxy: Refactor so that other upstream backends can be implemented
2015-05-04 23:20:44 -06:00
Matt Holt a3b853dd47 Merge pull request #49 from guilhermebr/master
adding crypto/tls sessioncache
2015-05-04 23:15:13 -06:00
Nimi Wariboko Jr d3aedbeb9a core: add bindaddr directive, allowing you to specify what address to listen on 2015-05-04 21:38:49 -07:00
Guilherme Rezende da6a097dcc adding crypto/tls sessioncache 2015-05-05 00:25:29 -03:00
Nimi Wariboko Jr 0ed5b364c6 Refactor proxy middleware so that 1.) From() is exposed 2.) Other upstreams can be implemented/plugged in 2015-05-04 19:58:18 -07:00
Matthew Holt 2dbd14b6dc Consistent app name/version info; pipe config data through stdin 2015-05-04 16:23:16 -06:00
Matthew Holt 085f6e9560 Keepin' the comments true 2015-05-04 13:42:39 -06:00
Matthew Holt 20118bdfd2 Clearing out the old stuff 2015-05-04 13:40:07 -06:00
Matthew Holt 088f41b334 Began adding tests 2015-05-04 12:04:14 -06:00
Matthew Holt e4fdf171c7 More refactoring - nearly complete 2015-05-04 11:49:49 -06:00
Matthew Holt 6029973bdc Major refactoring of middleware and parser in progress 2015-05-04 11:04:37 -06:00
Matthew Holt 995edf0566 Bringing in latest from master; refactoring under way 2015-05-04 11:02:46 -06:00
Matt Holt 5f32f9b1c8 Merge pull request #40 from ChannelMeter/proxy-middleware
Proxy Middleware: Add support for multiple backends, load balancing & healthchecks
2015-05-03 15:58:50 -06:00
Nimi Wariboko Jr 264e5b7911 Use the provided Replacer tools in order to proxy string interpolation. 2015-05-03 13:33:08 -07:00
Nimi Wariboko Jr a28d5585f5 Export Replacer type 2015-05-03 12:43:50 -07:00
Nimi Wariboko Jr 082ae70d1d Allow responseRecorder to be nil 2015-05-03 12:38:06 -07:00
Nimi Wariboko Jr 2aa958e058 Update {remote} replacer to use X-Forwarded-For if its provided 2015-05-03 12:37:00 -07:00
Matt Holt 290cf82936 Merge pull request #43 from dgryski/vet-fixes
config: format string fixes from vet
2015-05-03 11:47:47 -06:00
Damian Gryski a872ff2d77 config: format string fixes from vet 2015-05-03 19:43:04 +02:00
Matt Holt 4ed9387801 Merge pull request #41 from abiosoft/master
fastcgi: allow more request types.
2015-05-03 08:08:21 -06:00
Abiola Ibrahim 225d5977ff fastcgi: allow more request types. 2015-05-03 12:12:18 +01:00
Nimi Wariboko Jr 4a4b80450a Upgrade proxy middleware. Add support for: multiple backends, load balancing, health checks, and pluggable backends 2015-05-02 22:45:01 -07:00
Matthew Holt 747d59b895 Replace Open with Stat 2015-05-02 11:57:53 -06:00
Matthew Holt ca95b561dc gzip: Fix Content-Length header for proxies requests (closes #38) 2015-05-02 09:20:39 -06:00
Matt Holt 9df9ad975d Merge pull request #37 from abiosoft/master
git: post pull command. retries after pull failure.
2015-05-01 23:03:43 -06:00
Abiola Ibrahim 9cd1587cf7 git: post pull command. retries after pull failure. 2015-05-02 04:20:01 +01:00
Matthew Holt 782ba32457 Only a warning if site root doesn't exist 2015-05-01 16:23:28 -06:00
Matthew Holt d11819721d core: Error if root directory is not found 2015-05-01 13:35:57 -06:00
Matthew Holt 7ee3653342 core: Kill whole process if any server fail to start 2015-05-01 13:35:57 -06:00
Matt Holt 9e3852f21c Merge pull request #34 from abiosoft/master
fastcgi: user defined environment variables
2015-05-01 13:33:22 -06:00
Abiola Ibrahim 447d0ce0e2 fastcgi: user defined environment variables 2015-05-01 19:55:47 +01:00
Matthew Holt 49bb3f1387 git: Service routine, customizable logger, no more HTTP handler 2015-05-01 12:19:30 -06:00
Matt Holt 53a89c953a Merge pull request #33 from daviskoh/improvement/specify-go-version-dependency
Specify Go Version in README
2015-05-01 11:45:30 -06:00
Davis Koh 75d713cdea add go version to Running from Source section in README.md 2015-05-01 12:35:03 -04:00
Matt Holt 32c104c660 Merge pull request #32 from abiosoft/master
Implementation of Git middleware
2015-05-01 10:23:13 -06:00
Abiola Ibrahim 0d2ed0784f Modified repository path to be relative to root path. 2015-05-01 17:18:58 +01:00
Abiola Ibrahim 479c611420 Implementation of Git middleware
Defaults path to site root.
2015-05-01 16:41:34 +01:00
Matthew Holt d0556d6236 Add link to Docker container 2015-05-01 08:55:34 -06:00
Matthew Holt 37e3fe5f1f core: Fix dyslexic/backward handling of 403/404 errors 2015-04-30 11:58:38 -06:00
Matthew Holt 9dfbbbcda4 errors: Pointer to handler prevents nil pointer errors in handling (fixes #15)
If we do not use a pointer here, the startup function that opens the log file stores the log file in a copy of the handler, not the same instance of the handler, causing panics during requests, which is bad, especially when the response is gzipped: the next recover() is beyond the gzip handler, so the browser downloads a gz file instead.
2015-04-30 10:16:49 -06:00
Matthew Holt b1e1caba29 core: Graceful error handling during heavy load; proper error responses 2015-04-30 10:14:58 -06:00
Matthew Holt b51e8bc191 fastcgi: Fix for handling errors that come from responder 2015-04-30 07:50:07 -06:00
Matthew Holt 27722463a7 -host flag to set default host 2015-04-29 22:30:12 -06:00
Matthew Holt 3bc4e84ed3 Default host is now 0.0.0.0 (wildcard)
Doesn't break using localhost to access the site
2015-04-29 22:30:03 -06:00
Matt Holt 60beddf6c8 Merge pull request #18 from mholt/portflag
-port flag to allow overriding default port
2015-04-29 09:27:30 -06:00
Matthew Holt d00bb87f17 -port flag to override default port
Default port used if none is specified in config
2015-04-28 22:13:00 -06:00
Matt Holt 0f332bd9fb Merge pull request #14 from abiosoft/master
Fix for Issue 13: Trouble running in Docker containers (or binding to 0.0.0.0)
2015-04-28 20:25:15 -06:00
Abiola Ibrahim e04e06d6e2 Fix for Issue 13: Trouble running in Docker containers (or binding to 0.0.0.0) 2015-04-28 22:31:42 +01:00
Thomas Hansen 17fa5a9334 adding support for fastcgi index files in subdirectories 2015-04-28 13:15:14 -06:00
Matthew Holt 9b74901b40 errors: Fix file paths for error pages & empty log filenames 2015-04-28 13:05:01 -06:00
Matthew Holt 78e6d7db95 Clarified "no dependencies"; info about 3rd party libraries 2015-04-28 12:07:37 -06:00
Matthew Holt 2cf06bc3ee Ignore automated build script and stuff 2015-04-27 23:20:58 -06:00
Matthew Holt 264820e3e8 Fixed config file leak, but new todo item 2015-04-27 22:27:34 -06:00
Matthew Holt 979041a072 More canonical godoc shield, I suppose 2015-04-27 17:19:54 -06:00
Matthew Holt ff344535ba Added some godoc 2015-04-27 10:56:57 -06:00
Matthew Holt 1df35eb687 Fixed link/email in instructions 2015-04-27 10:45:47 -06:00
Matthew Holt cba96c9b35 Merge branch 'master' of github.com:mholt/caddy 2015-04-27 10:42:17 -06:00
Matthew Holt d7f0133f5f Add notes for contributors 2015-04-27 10:41:55 -06:00
Matt Holt e5d064d513 Create LICENSE.md 2015-04-27 10:41:32 -06:00
Matt Holt c33a49fc5e Merge pull request #2 from thomas4019/master
gzip middleware now strips encoding header
2015-04-27 09:54:54 -06:00
Matthew Holt a837bb6681 The README sorely needed an update 2015-04-27 09:53:08 -06:00
Thomas Hansen dbef6c73bc Merge branch 'master' of https://github.com/mholt/caddy 2015-04-27 09:39:40 -06:00
Matthew Holt fa2403c1d3 websockets: quick version fix 2015-04-27 07:35:39 -06:00
Matthew Holt c1916c0fb5 Server header in response
Version number purposefully excluded (for now?)
2015-04-26 23:09:26 -06:00
Matthew Holt dba4dcb4a5 gzip strips Accept-Encoding header after using it 2015-04-26 22:53:47 -06:00
Thomas Hansen 9d26a9268b added comment about encoding header 2015-04-26 22:15:43 -06:00
Thomas Hansen 1b17072a89 gzip middleware now strips encoding header 2015-04-26 22:01:20 -06:00
Matthew Holt 7d46108c12 With just a destination, default redir code is now 301 2015-04-26 20:20:32 -06:00
Matt Holt cd53ec9bcc Merge pull request #1 from thomas4019/master
adding support for php including clean urls and wordpress permalinks
2015-04-25 23:41:57 -06:00
Thomas Hansen 1ac32a5256 generalizing fastcgi parameters, and improving headers passed. 2015-04-25 21:56:14 -06:00
Thomas Hansen 9e12c45d82 Merge branch 'master' of https://github.com/mholt/caddy 2015-04-25 19:06:39 -06:00
Matthew Holt 24d9d23743 Default port is 2015 2015-04-25 14:28:56 -06:00
Matthew Holt ce74333348 Markdown requires a base path (for now) 2015-04-25 12:26:04 -06:00
Matthew Holt 46f5325c15 More accurate initialization output 2015-04-24 20:09:31 -06:00
Matthew Holt aa89b95075 Replaced cpu directive with command line flag 2015-04-24 20:08:14 -06:00
Matthew Holt 27fc1672d4 Basic auth middleware 2015-04-23 14:57:07 -06:00
Matthew Holt e6c5482b7c Slightly more helpful parse error message 2015-04-23 14:39:21 -06:00
Matthew Holt 95dce5cdfc Latency now available with recorder and replacer 2015-04-23 13:35:56 -06:00
Matthew Holt 51139a5f56 log: Fix so user can specify custom log format 2015-04-23 13:35:21 -06:00
Matthew Holt dd3ff0fcb5 Shows site addresses at start; new -quiet flag 2015-04-23 13:28:05 -06:00
Matthew Holt d088194585 Default port is now 80 2015-04-22 13:22:03 -06:00
Matthew Holt 5f187738e6 Better parse support for files with only an address line 2015-04-22 13:21:51 -06:00
Matthew Holt c10d2e0d45 Make the thing compile 2015-04-21 22:30:47 -06:00
Matthew Holt 1a8f753303 Meh. 2015-04-21 21:41:58 -06:00
Matthew Holt 23f7f5ebba Minor UI tweaks to directory listings 2015-04-21 21:36:43 -06:00
Matthew Holt bdd145b0de Better error handling when executing templates 2015-04-21 21:36:30 -06:00
Matthew Holt 0cbaed2443 A few helpful comments 2015-04-21 16:00:16 -06:00
Matthew Holt 99c0cbdf29 Fixed a typo 2015-04-21 12:12:58 -06:00
Thomas Hansen 96985fb3fd adding support for php including clean urls and wordpress permalinks 2015-04-20 17:40:54 -06:00
Matthew Holt 981ca72ee6 Enforce canonical URLs 2015-04-18 13:24:54 -06:00
Matthew Holt 6a32de4b47 Use text/template because html shouldn't be escaped for this 2015-04-18 12:28:22 -06:00
Matthew Holt f5d0ed5b1c More template love 2015-04-18 11:31:59 -06:00
Matthew Holt 55801b48ec More template functions 2015-04-18 11:08:41 -06:00
Matthew Holt 3ec870cb56 Templates middleware with "include" functionality 2015-04-18 09:57:51 -06:00
Matthew Holt cd0421ceb8 Package extension -> extensions 2015-04-18 09:55:02 -06:00
Matthew Holt 9a27beb79c Quick bug fix for empty Caddyfile 2015-04-18 09:53:43 -06:00
Matthew Holt e6532b6d85 Multiple addresses may be specified per server block 2015-04-15 23:24:39 -06:00
Matthew Holt 7d96cfa424 Turn off log timestamp for parse errors (easier to read) 2015-04-15 23:17:56 -06:00
Matthew Holt c7af6725ca Removed Host() and Port() functions from Controller
I don't think they'll be necessary; can get same info from request Host header
2015-04-15 23:17:28 -06:00
Matthew Holt feec7c5b40 Virtual hosts and SNI support 2015-04-15 14:11:32 -06:00
Matthew Holt 07964a6112 Fixed bug in parser; implicit server block with middleware directives 2015-04-14 13:26:35 -06:00
Matthew Holt a93db40138 Improvements to the redirect middleware 2015-04-12 18:13:58 -06:00
Matthew Holt b7c8afab2f Respond with 404 if requesting server's config file 2015-04-12 17:44:02 -06:00
Matthew Holt 6ca475def8 Redirect now does exact path matching like rewrite middleware 2015-04-12 17:40:59 -06:00
Matthew Holt d8e7adcdb4 Refactored proxy middleware 2015-04-11 17:24:47 -06:00
Matthew Holt 113b175db7 Refactored fastcgi middleware 2015-04-11 17:15:17 -06:00
Matthew Holt 40bf7c5285 Refactored redirect middleware 2015-04-11 17:06:09 -06:00
Matthew Holt abeb337f45 Refactored rewrite middleware 2015-04-11 16:58:34 -06:00
Matthew Holt d0a0216602 Added flag to disable http/2 support (still enabled by default) 2015-04-09 10:08:22 -06:00
Matthew Holt 2a0cfb608d Bug fix for default error handling with gzip 2015-04-08 23:24:59 -06:00
Matthew Holt d33256f1dc Refactor: Middleware chain uses Handler instead of HandlerFunc 2015-04-02 23:30:54 -06:00
Matthew Holt db2cd9e941 Renamed extensionless to extension, Extensionless to Ext 2015-04-02 21:59:45 -06:00
Matthew Holt 3e6f5de92f Renamed redirect -> redir 2015-03-31 23:57:09 -06:00
Matthew Holt 9f793dad28 Proxy destination may include scheme 2015-03-31 23:53:39 -06:00
Matthew Holt f2f5d4984d Markdown defaults to .md 2015-03-31 23:41:16 -06:00
Matthew Holt 29fec4742e Detailed godoc; better error handling convention 2015-03-29 22:01:42 -06:00
Matthew Holt 0a9a19305c Made catch-all redirects possible 2015-03-29 21:48:53 -06:00
Matthew Holt 4e9c432c14 Controller/Dispenser refactoring, typo fixes 2015-03-29 19:56:19 -06:00
Matthew Holt 6bf36d922c Refactored proxy middleware to return errors 2015-03-28 16:56:56 -06:00
Matthew Holt 076d4e0ec5 Refactored web socket middleware to return errors 2015-03-28 16:56:33 -06:00
Matthew Holt b87e6ccb76 Refactored markdown middleware to return errors 2015-03-28 16:55:40 -06:00
Matthew Holt 22707edcbf Refactored fastcgi middleware to return errors 2015-03-28 16:52:43 -06:00
Matthew Holt 7578298b3f Rewrote access log middleware 2015-03-28 16:50:42 -06:00
Matthew Holt d2892fc799 New error handler middleware 2015-03-28 16:50:06 -06:00
Matthew Holt 21b2e5a059 Refactored file server to return errors 2015-03-28 16:49:42 -06:00
Matthew Holt 878ae7ea89 Refactored rewrite middleware to return errors 2015-03-28 16:49:18 -06:00
Matthew Holt c657948824 Refactored redir middleware to return errors 2015-03-28 16:49:06 -06:00
Matthew Holt a39e71ca26 Refactored headers middleware to return errors 2015-03-28 16:48:43 -06:00
Matthew Holt 8f4e7f7fdc Refactored gzip middleware to return errors 2015-03-28 16:47:41 -06:00
Matthew Holt a674450198 Refactored ext middleware to return errors 2015-03-28 16:47:28 -06:00
Matthew Holt 843f6e83a9 Refactored browse middleware to return errors 2015-03-28 16:46:54 -06:00
Matthew Holt 058ff94828 Better middleware godoc, fixed ordering too 2015-03-28 16:45:12 -06:00
Matthew Holt 9378f38371 Major refactoring for better error handling 2015-03-28 16:37:37 -06:00
Matthew Holt 2dc39feabd Tweak to parser and main's error handling 2015-03-28 16:24:00 -06:00
Matthew Holt 09aad777f4 Proper host/port splitting; also log file perms 2015-03-26 23:39:36 -06:00
Matthew Holt da72a5fbcd Controller can register functions to run at shutdown 2015-03-26 23:22:48 -06:00
Matthew Holt 1146a9b90b Recover from panic during requests 2015-03-26 22:52:27 -06:00
Matthew Holt 2fbfafc408 New startup and shutdown directives 2015-03-26 09:52:03 -06:00
Matthew Holt b5dc1dde8b Updated README 2015-03-25 22:48:28 -06:00
Matthew Holt 63b39c78ee Better default template; other fixes 2015-03-24 23:05:42 -06:00
Matthew Holt 13d9bcc0c7 Clean URL middleware handles URLs ending with / 2015-03-24 21:56:22 -06:00
Matthew Holt ba0d63d722 Adapted std lib file server and gutted it 2015-03-24 21:55:51 -06:00
Matthew Holt 9672850d11 Bug fixes and improvements for browse middleware 2015-03-24 21:54:33 -06:00
Matthew Holt 00e43197fd Started browse middleware to list directory contents 2015-03-24 20:12:48 -06:00
Matthew Holt 284ab11c7f Little bit of cleanup 2015-03-21 15:18:50 -06:00
Matthew Holt e62b222372 Couple more controller tests 2015-03-21 15:11:31 -06:00
Matthew Holt a0e93009f0 Controller tests 2015-03-21 14:50:28 -06:00
Matthew Holt 5d4726446d Finished dispenser tests 2015-03-21 14:36:32 -06:00
Matthew Holt 010ac23e8a More tests! 2015-03-21 11:18:37 -06:00
Matthew Holt cdfc67db01 Some godoc 2015-03-21 11:04:08 -06:00
Matthew Holt 6d869ef55b Support multiple occurrences of markdown directive 2015-03-21 10:59:29 -06:00
Matthew Holt 2fa6129c3a Started dispenser tests 2015-03-20 18:22:22 -06:00
Matthew Holt bb6a921d1e Tests for location context parsing 2015-03-20 18:13:13 -06:00
Matthew Holt 9aaf81328f There's a std lib function for that 2015-03-20 18:12:53 -06:00
Matthew Holt 35225fe2d3 Docs and comments, la la 2015-03-20 18:11:54 -06:00
Matthew Holt 01266ece6b Minor style nit-pick 2015-03-20 00:03:41 -06:00
Matthew Holt 1b7415a81b Markdown handles titles a little better 2015-03-20 00:01:39 -06:00
Matthew Holt abdadf1ee1 Improvements to websocket middleware 2015-03-19 23:52:56 -06:00
Matthew Holt d7ae9fb4a2 Added markdown middleware 2015-03-16 11:45:51 -06:00
Matthew Holt fb78592425 Experimental HTTP/2 support 2015-03-16 11:44:54 -06:00
Matthew Holt af56c5033c New method to get remaining arguments on a line 2015-03-16 11:23:17 -06:00
Matthew Holt 3858e31942 A couple pesky env variables to deal with in websockets 2015-03-03 18:39:38 -07:00
Matthew Holt 37f0a37ed2 Filled out more web socket stuff 2015-03-03 17:36:18 -07:00
Matthew Holt 411f3256cc Minor changes to readme 2015-03-03 11:14:36 -07:00
Matthew Holt 811c6a986f Added WebSocket middleware 2015-03-03 09:49:45 -07:00
Matthew Holt 974acbf38c Partial support for location contexts in config files 2015-03-03 09:49:01 -07:00
Matthew Holt 634b8b707f Slight refactoring/renaming 2015-02-07 22:17:15 -07:00
Matthew Holt 0e43271cc9 Basic proxy feature works 2015-02-02 23:41:35 -07:00
Matthew Holt 5ae1790e52 Moved controller into its own file; other minor cleanups 2015-01-31 10:15:17 -07:00
Matthew Holt 16997d85eb Made 'extensionless' middleware more modular/useful 2015-01-30 11:09:36 -07:00
Matthew Holt 62d7d61381 Refactored the dispenser/controller 2015-01-30 10:00:41 -07:00
Matthew Holt ae2a2d5b00 Godoc for middleware packages and server package 2015-01-29 23:52:18 -07:00
Matthew Holt bcdf04d00e Inlined a fixed version of the fastcgi_client dependency 2015-01-29 23:48:35 -07:00
Matthew Holt ba88be0fe9 Allow nil middleware to be returned
In case a middleware actually just wants some code to execute at startup... will expand on that idea later.
2015-01-29 23:09:14 -07:00
Matthew Holt 8471c2d9d8 Updated docs; renamed a couple utility files 2015-01-29 22:52:21 -07:00
Matthew Holt dcc67863dc Experimenting to make middleware more independent 2015-01-29 22:46:09 -07:00
Matthew Holt ac7f50b4cd Updated doc comment 2015-01-29 22:14:31 -07:00
Matthew Holt 612d77eaab Moved Path type around 2015-01-29 22:08:40 -07:00
Matthew Holt 04996b2850 Exported NewReplacer and NewRecorder 2015-01-29 22:06:53 -07:00
Matthew Holt 261beb046e Moved rewrite middleware into its own package 2015-01-29 22:06:19 -07:00
Matthew Holt b8c43e55db Moved redirect middleware into its own package 2015-01-29 22:05:54 -07:00
Matthew Holt 13cf980879 Moved proxy middleware into its own package 2015-01-29 22:05:36 -07:00
Matthew Holt e6063fb26b Moved logging middleware into its own package 2015-01-29 22:05:21 -07:00
Matthew Holt 1e4baa53f0 Moved headers middleware into its own package
Further trying out spreading out the code outside of the nested functions
2015-01-29 22:05:05 -07:00
Matthew Holt 80ef5d761c Moved gzip middleware into its own package
Trying a different format where the middleware is a type that satisfies http.Handler
2015-01-29 22:04:18 -07:00
Matthew Holt affd470820 Moved fastcgi middleware into its own package 2015-01-29 22:03:14 -07:00
Matthew Holt 89783ac0c2 Moved extensionless middleware into its own package 2015-01-29 22:02:58 -07:00
Matthew Holt fe62afd3d9 Beginning to move middleware into their own packages 2015-01-29 22:02:17 -07:00
Matthew Holt dca59d0eda Stubbed out really basic proxy middleware 2015-01-29 17:18:14 -07:00
Matt Holt ec5f94adc8 Added feature list 2015-01-21 19:16:31 -07:00
Matthew Holt a38a2a0e4f Created basic fastcgi middleware layer 2015-01-21 17:51:47 -07:00
Matthew Holt fe1978c6f5 New 'cpu' directive; now uses all cores by default (if needed) 2015-01-21 14:10:52 -07:00
Matthew Holt 509db0b08f Wrote basic tests for parser 2015-01-21 13:19:55 -07:00
Matthew Holt eae024027f Parser fixes, and now using base filename 2015-01-21 13:19:25 -07:00
Matthew Holt decfda2705 Made parsing easier in middleware 2015-01-21 12:09:49 -07:00
Matthew Holt 318781512b Wrote lexer tests 2015-01-21 12:09:01 -07:00
Matthew Holt 286d558c54 Moved most docs to wiki 2015-01-19 17:13:00 -07:00
Matthew Holt 822c231f1c Renamed {time} placeholder to {when} 2015-01-19 17:12:38 -07:00
Matthew Holt 24fc2ae59e Major refactoring; more modular middleware 2015-01-18 23:11:21 -07:00
Matthew Holt 7b3d005662 Started adding tests 2015-01-13 17:25:55 -07:00
Matthew Holt 04162aaa79 Added flag to specify config file location 2015-01-13 16:22:16 -07:00
Matt Holt db1adcac97 Filled out README a little more 2015-01-13 15:45:08 -07:00
Matthew Holt 1e78262fc5 Created basic README 2015-01-13 13:24:43 -07:00
Matthew Holt 4497a16fb0 Early prototype; initial commit 2015-01-13 12:43:45 -07:00
1592 changed files with 443675 additions and 92128 deletions
-5
View File
@@ -1,5 +0,0 @@
[*]
end_of_line = lf
[caddytest/integration/caddyfile_adapt/*.caddyfiletest]
indent_style = tab
+14 -1
View File
@@ -1 +1,14 @@
*.go text eol=lf
# 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
*.yml text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2
.git* text eol=auto core.whitespace whitespace=trailing-space
+36 -73
View File
@@ -1,14 +1,14 @@
Contributing to Caddy
=====================
Welcome! Thank you for choosing to be a part of our community. Caddy wouldn't be nearly as excellent without your involvement!
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 Caddy module](#writing-a-caddy-module)
- [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)
@@ -17,73 +17,61 @@ For starters, we invite you to join [the Caddy forum](https://caddy.community) w
Other menu items:
- [Values](#values)
- [Coordinated Disclosure](#coordinated-disclosure)
- [Responsible Disclosure](#responsible-disclosure)
- [Thank You](#thank-you)
### Contributing code
You can have a huge impact on the project by helping with its code. To contribute code to Caddy, first submit or comment in an issue to discuss your contribution, then open a [pull request](https://github.com/caddyserver/caddy/pulls) (PR). If you're new to our community, that's okay: **we gladly welcome pull requests from anyone, regardless of your native language or coding experience.** You can get familiar with Caddy's code base by using [code search at Sourcegraph](https://sourcegraph.com/github.com/caddyserver/caddy).
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. :blue_heart: If your change is on the right track, we can guide you to make it mergeable.
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:
- **Open an issue to propose your change first.** This way we can avoid confusion, coordinate what everyone is working on, and ensure that any changes are in-line with the project's goals and the best interests of its users. We can also discuss the best possible implementation. If there's already an issue about it, comment on the existing issue to claim it. A lot of valuable time can be saved by discussing a proposal first.
- 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 want to avoid.](https://twitter.com/iamdevloper/status/397664295875805184)
- **Keep pull requests small.** Smaller PRs are more likely to be merged because they are easier to review! We might ask you to break up large PRs into smaller ones. [An example of what we 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.** Good, automated tests are very valuable! Written properly, they ensure your change works, and that other changes in the future won't break your change. CI checks should pass.
- **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 and profiling.
- **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`.
- **Be responsible for and maintain your contributions.** Caddy is a growing project, and it's much better when individual contributors help maintain their change after it is merged.
- **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.
- **Pull requests may still get closed.** The longer a PR stays open and idle, the more likely it is to be closed. If we haven't reviewed it in a while, it probably means the change is not a priority. Please don't take this personally, we're trying to balance a lot of tasks! If nobody else has commented or reacted to the PR, it likely means your change is useful only to you. The reality is this happens quite a lot. We don't tend to accept PRs that aren't generally helpful. For these reasons or others, the PR may get closed even after a review. We are not obligated to accept all proposed changes, even if the best justification we can give is something vague like, "It doesn't sit right." Sometimes PRs are just the wrong thing or the wrong time. Because it is open source, you can always build your own modified version of Caddy with a change you need, even if we reject it in the official repo. Plus, because Caddy is extensible, it's possible your feature could make a great plugin instead!
- **You certify that you wrote and comprehend the code you submit.** The Caddy project welcomes original contributions that comply with [our CLA](https://cla-assistant.io/caddyserver/caddy), meaning that authors must be able to certify that they created or have rights to the code they are contributing. In addition, we require that code is not simply copy-pasted from Q/A sites or AI language models without full comprehension and rigorous testing. In other words: contributors are allowed to refer to communities for assistance and use AI tools such as language models for inspiration, but code which originates from or is assisted by these resources MUST be:
- Licensed for you to freely share
- Fully comprehended by you (be able to explain every line of code)
- Verified by automated tests when feasible, or thorough manual tests otherwise
We have found that current language models (LLMs, like ChatGPT) may understand code syntax and even problem spaces to an extent, but often fail in subtle ways to convey true knowledge and produce correct algorithms. Integrated tools such as GitHub Copilot and Sourcegraph Cody may be used for inspiration, but code generated by these tools still needs to meet our criteria for licensing, human comprehension, and testing. These tools may be used to help write code comments and tests as long as you can certify they are accurate and correct. Note that it is often more trouble than it's worth to certify that Copilot (for example) is not giving you code that is possibly plagiarised, unlicensed, or licensed with incompatible terms -- as the Caddy project cannot accept such contributions. If that's too difficult for you (or impossible), then we recommend using these resources only for inspiration and write your own code. Ultimately, you (the contributor) are responsible for the code you're submitting.
As a courtesy to reviewers, we kindly ask that you disclose when contributing code that was generated by an AI tool or copied from another website so we can be aware of what to look for in code review.
We often grant [collaborator status](#collaborator-instructions) to contributors who author one or more significant, high-quality PRs that are merged into the code base.
We often grant [collaborator status](#collaborator-instructions) to contributors who author one or more significant, high-quality PRs that are merged into the code base!
#### HOW TO MAKE A PULL REQUEST TO CADDY
Contributing to Go projects on GitHub is fun and easy. After you have proposed your change in an issue, we recommend the following workflow:
Contributing to Go projects on GitHub is fun and easy. We recommend the following workflow:
1. [Fork this repo](https://github.com/caddyserver/caddy). This makes a copy of the code you can write to.
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 (caddyserver/caddy.git) repo on your computer, clone it down: `git clone https://github.com/caddyserver/caddy.git`
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 caddyserver/caddy.git repo to your fork by adding a remote: `git remote add myfork https://github.com/<your-username>/caddy.git`
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 caddyserver/caddy.git repo on your computer.
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/caddyserver/caddy/pull/new/master) to merge your changes into caddyserver/caddy @ master. (Click "compare across forks" and change the head fork.)
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 Caddy module
### Writing a plugin
Caddy can do more with modules! Anyone can write one. Caddy modules are Go libraries that get compiled into Caddy, extending its feature set. They can add directives to the Caddyfile, add new configuration adapters, and even implement new server types (e.g. HTTP, DNS).
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 a module here](https://caddyserver.com/docs/extending-caddy). You should also share and discuss your module idea [on the forums](https://caddy.community) to have people test it out. We don't use the Caddy issue tracker for third-party modules.
[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
@@ -95,61 +83,35 @@ Many people on the forums could benefit from your experience and expertise, too.
### Reporting bugs
Like every software, Caddy has its flaws. If you find one, [search the issues](https://github.com/caddyserver/caddy/issues) to see if it has already been reported. If not, [open a new issue](https://github.com/caddyserver/caddy/issues/new) and describe the bug, and somebody will look into it! (This repository is only for Caddy and its standard modules.)
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 us fix bugs!** Speed up the patch by identifying the bug in the code. This can sometimes be done by adding `fmt.Println()` statements (or similar) in relevant code paths to narrow down where the problem may be. It's a good way to [introduce yourself to the Go language](https://tour.golang.org), too.
**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.
We may reply with an issue template. Please follow the 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. If you don't, we might close your report. The burden is on you to make it easily reproducible and to convince us that it is actually a bug in Caddy. This is easiest to do when you write clear, concise instructions so we can reproduce the behavior (even if it seems obvious). The more detailed and specific you are, the faster we will be able to help you!
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!
#### Bug reporting expectations
Maintainers---or more generally, developers---need three things to act on bugs:
1. To agree or be convinced that it's a bug (reporter's responsibility).
- A bug is unintentional, undesired, or surprising behavior which violates documentation or relevant spec. It might be either a mistake in the documentation or a bug in the code.
- This project usually does not work around bugs in other software, systems, and dependencies; instead, we recommend that those bugs are fixed at their source. This sometimes means we close issues or reject PRs that attempt to fix, workaround, or hide bugs in other projects.
2. To be able to understand what is happening (mostly reporter's responsibility).
- If the reporter can provide satisfactory instructions such that a developer can reproduce the bug, the developer will likely be able to understand the bug, write a test case, and implement a fix. This is the least amount of work for everyone and path to the fastest resolution.
- Otherwise, the burden is on the reporter to test possible solutions. This is less preferable because it loosens the feedback loop, slows down debugging efforts, obscures the true nature of the problem from the developers, and is unlikely to result in new test cases.
3. A solution, or ideas toward a solution (mostly maintainer's responsibility).
- Sometimes the best solution is a documentation change.
- Usually the developers have the best domain knowledge for inventing a solution, but reporters may have ideas or preferences for how they would like the software to work.
- Security, correctness, and project goals/vision all take priority over a user's preferences.
- It's simply good business to yield a solution that satisfies the users, and it's even better business to leave them impressed.
Thus, at the very least, the reporter is expected to:
1. Convince the reader that it's a bug in Caddy (if it's not obvious).
2. Reduce the problem down to the minimum specific steps required to reproduce it.
The maintainer is usually able to do the rest; but of course the reporter may invest additional effort to speed up the process.
### Suggesting features
First, [search to see if your feature has already been requested](https://github.com/caddyserver/caddy/issues). If it has, you can add a :+1: reaction to vote for it. If your feature idea is new, open an issue to request the feature. Please describe your idea thoroughly so that we know how to implement it! Really vague requests may not be helpful or actionable and, without clarification, will have to be closed.
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 modules](#writing-a-caddy-module), which can be made by anyone! But if a feature is not in the best interest of the Caddy project or its users in general, we may politely decline to implement it into Caddy core. Additionally, some features are bad ideas altogether (for either obvious or non-obvious reasons) which may be rejected. We'll try to explain why we reject a feature, but sometimes the best we can do is, "It's not a good fit for the project."
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) and its source is in the [website repo](https://github.com/caddyserver/website). If you would like to make a fix to the docs, please submit an issue there describing the change to make.
Caddy's documentation is available at [https://caddyserver.com/docs](https://caddyserver.com/docs). If you would like to make a fix to the docs, feel free to contribute at the [caddyserver/website](https://github.com/caddyserver/website) repository!
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.
Note that third-party module documentation is not hosted by the Caddy website, other than basic usage examples. They are managed by the individual module authors, and you will have to contact them to change their documentation.
Our documentation is scoped to the Caddy project only: it is not for describing how other software or systems work, even if they relate to Caddy or web servers. That kind of content [can be found in our community wiki](https://caddy.community/c/wiki/13), however.
## Collaborator Instructions
Collaborators have push rights to the repository. We grant this permission after one or more successful, high-quality PRs are merged! We thank them for their help. The expectations we have of collaborators are:
Collabators have push rights to the repository. We grant this permission after one or more successful, high-quality PRs are merged! We thank them for their help.The expectations we have of collaborators are:
- **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?
@@ -166,9 +128,9 @@ Collaborators have push rights to the repository. We grant this permission after
- **Prefer squashed commits over a messy merge.** If there are many little commits, please [squash the commits](https://stackoverflow.com/a/11732910/1048862) so we don't clutter the commit history.
- **Don't accept new dependencies lightly.** Dependencies can make the world crash and burn, but they are sometimes necessary. Choose carefully. Extremely small dependencies (a few lines of code) can be inlined. The rest may not be needed. For those that are, Caddy uses [go modules](https://github.com/golang/go/wiki/Modules). All external dependencies must be installed as modules, and _Caddy must not export any types defined by those dependencies_. Check this diligently!
- **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 `caddyhttp` and `caddytls` packages especially.
- **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.
@@ -180,18 +142,19 @@ Collaborators have push rights to the repository. We grant this permission after
## Values (WIP)
## 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.
## Security Policy
## Responsible Disclosure
If you think you've found a security vulnerability, please refer to our [Security Policy](https://github.com/caddyserver/caddy/security/policy) document.
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.
Thanks for your help! Caddy would not be what it is today without your
contributions.
-12
View File
@@ -1,12 +0,0 @@
# These are supported funding model platforms
github: [mholt] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
+32
View File
@@ -0,0 +1,32 @@
<!--
Are you asking for help with using Caddy? Please use our forum instead: https://caddy.community. If you are filing a bug report, please take a few minutes to carefully answer the following questions. If your issue is not a bug report, you do not need to use this template. Thanks!)
-->
### 1. What version of Caddy are you using (`caddy -version`)?
### 2. What are you trying to do?
### 3. What is your entire Caddyfile?
```text
(paste Caddyfile here)
```
### 4. How did you run Caddy (give the full command and describe the execution environment)?
### 5. Please paste any relevant HTTP request(s) here.
<!-- Paste curl command, or full HTTP request including headers and body, here. -->
### 6. What did you expect to see?
### 7. What did you see instead (give full error messages and/or log)?
### 8. How can someone who is starting from scratch reproduce the bug as minimally as possible?
<!-- Please strip away any extra infrastructure such as containers, reverse proxies, upstream apps, caches, dependencies, etc, to prove this is a bug in Caddy and not an external misconfiguration. Your chances of getting this bug fixed go way up the easier it is to replicate. Thank you! -->
+19
View File
@@ -0,0 +1,19 @@
<!--
Thank you for contributing to Caddy! Please fill this out to help us make the most of your pull request.
-->
### 1. What does this change do, exactly?
### 2. Please link to the relevant issues.
### 3. Which documentation changes (if any) need to be made because of this PR?
### 4. Checklist
- [ ] I have written tests and verified that they fail without my change
- [ ] I have squashed any insignificant commits
- [ ] This change has comments for package types, values, functions, and non-obvious lines of code
- [ ] I am willing to help maintain this change if there are issues with it later
-59
View File
@@ -1,59 +0,0 @@
# Security Policy
The Caddy project would like to make sure that it stays on top of all practically-exploitable vulnerabilities.
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| 2.x | ✔️ |
| 1.x | :x: |
| < 1.x | :x: |
## Acceptable Scope
A security report must demonstrate a security bug in the source code from this repository.
Some security problems are the result of interplay between different components of the Web, rather than a vulnerability in the web server itself. Please only report vulnerabilities in the web server itself, as we cannot coerce the rest of the Web to be fixed (for example, we do not consider IP spoofing, BGP hijacks, or missing/misconfigured HTTP headers a vulnerability in the Caddy web server).
Vulnerabilities caused by misconfigurations are out of scope. Yes, it is entirely possible to craft and use a configuration that is unsafe, just like with every other web server; we recommend against doing that.
We do not accept reports if the steps imply or require a compromised system or third-party software, as we cannot control those. We expect that users secure their own systems and keep all their software patched. For example, if untrusted users are able to upload/write/host arbitrary files in the web root directory, it is NOT a security bug in Caddy if those files get served to clients; however, it _would_ be a valid report if a bug in Caddy's source code unintentionally gave unauthorized users the ability to upload unsafe files or delete files without relying on an unpatched system or piece of software.
Client-side exploits are out of scope. In other words, it is not a bug in Caddy if the web browser does something unsafe, even if the downloaded content was served by Caddy. (Those kinds of exploits can generally be mitigated by proper configuration of HTTP headers.) As a general rule, the content served by Caddy is not considered in scope because content is configurable by the site owner or the associated web application.
Security bugs in code dependencies (including Go's standard library) are out of scope. Instead, if a dependency has patched a relevant security bug, please feel free to open a public issue or pull request to update that dependency in our code.
## Reporting a Vulnerability
We get a lot of difficult reports that turn out to be invalid. Clear, obvious reports tend to be the most credible (but are also rare).
First please ensure your report falls within the accepted scope of security bugs (above).
We'll need enough information to verify the bug and make a patch. To speed things up, please include:
- Most minimal possible config (without redactions!)
- Command(s)
- Precise HTTP requests (`curl -v` and its output please)
- Full log output (please enable debug mode)
- Specific minimal steps to reproduce the issue from scratch
- A working patch
Please DO NOT use containers, VMs, cloud instances or services, or any other complex infrastructure in your steps. Always prefer `curl -v` instead of web browsers.
We consider publicly-registered domain names to be public information. This necessary in order to maintain the integrity of certificate transparency, public DNS, and other public trust systems. Do not redact domain names from your reports. The actual content of your domain name affects Caddy's behavior, so we need the exact domain name(s) to reproduce with, or your report will be ignored.
It will speed things up if you suggest a working patch, such as a code diff, and explain why and how it works. Reports that are not actionable, do not contain enough information, are too pushy/demanding, or are not able to convince us that it is a viable and practical attack on the web server itself may be deferred to a later time or possibly ignored, depending on available resources. Priority will be given to credible, responsible reports that are constructive, specific, and actionable. (We get a lot of invalid reports.) Thank you for understanding.
When you are ready, please email Matt Holt (the author) directly: matt at dyanim dot com.
Please don't encrypt the email body. It only makes the process more complicated.
Please also understand that due to our nature as an open source project, we do not have a budget to award security bounties. We can only thank you.
If your report is valid and a patch is released, we will not reveal your identity by default. If you wish to be credited, please give us the name to use and/or your GitHub username. If you don't provide this we can't credit you.
Thanks for responsibly helping Caddy&mdash;and thousands of websites&mdash;be more secure!
-7
View File
@@ -1,7 +0,0 @@
---
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
-181
View File
@@ -1,181 +0,0 @@
# Used as inspiration: https://github.com/mvdan/github-actions-golang
name: Tests
on:
push:
branches:
- master
- 2.*
pull_request:
branches:
- master
- 2.*
jobs:
test:
strategy:
# Default is true, cancels jobs for other platforms in the matrix if one fails
fail-fast: false
matrix:
os:
- linux
- mac
- windows
go:
- '1.21'
- '1.22'
include:
# Set the minimum Go patch version for the given Go minor
# Usable via ${{ matrix.GO_SEMVER }}
- go: '1.21'
GO_SEMVER: '~1.21.0'
- go: '1.22'
GO_SEMVER: '~1.22.3'
# Set some variables per OS, usable via ${{ matrix.VAR }}
# OS_LABEL: the VM label from GitHub Actions (see https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-public-repositories)
# CADDY_BIN_PATH: the path to the compiled Caddy binary, for artifact publishing
# SUCCESS: the typical value for $? per OS (Windows/pwsh returns 'True')
- os: linux
OS_LABEL: ubuntu-latest
CADDY_BIN_PATH: ./cmd/caddy/caddy
SUCCESS: 0
- os: mac
OS_LABEL: macos-14
CADDY_BIN_PATH: ./cmd/caddy/caddy
SUCCESS: 0
- os: windows
OS_LABEL: windows-latest
CADDY_BIN_PATH: ./cmd/caddy/caddy.exe
SUCCESS: 'True'
runs-on: ${{ matrix.OS_LABEL }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.GO_SEMVER }}
check-latest: true
# These tools would be useful if we later decide to reinvestigate
# publishing test/coverage reports to some tool for easier consumption
# - name: Install test and coverage analysis tools
# run: |
# go get github.com/axw/gocov/gocov
# go get github.com/AlekSi/gocov-xml
# go get -u github.com/jstemmer/go-junit-report
# echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
- name: Print Go version and environment
id: vars
shell: bash
run: |
printf "Using go at: $(which go)\n"
printf "Go version: $(go version)\n"
printf "\n\nGo environment:\n\n"
go env
printf "\n\nSystem environment:\n\n"
env
printf "Git version: $(git version)\n\n"
# Calculate the short SHA1 hash of the git commit
echo "short_sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
- name: Get dependencies
run: |
go get -v -t -d ./...
# mkdir test-results
- name: Build Caddy
working-directory: ./cmd/caddy
env:
CGO_ENABLED: 0
run: |
go build -tags nobdger -trimpath -ldflags="-w -s" -v
- name: Smoke test Caddy
working-directory: ./cmd/caddy
run: |
./caddy start
./caddy stop
- name: Publish Build Artifact
uses: actions/upload-artifact@v4
with:
name: caddy_${{ runner.os }}_go${{ matrix.go }}_${{ steps.vars.outputs.short_sha }}
path: ${{ matrix.CADDY_BIN_PATH }}
compression-level: 0
# Commented bits below were useful to allow the job to continue
# even if the tests fail, so we can publish the report separately
# For info about set-output, see https://stackoverflow.com/questions/57850553/github-actions-check-steps-status
- name: Run tests
# id: step_test
# continue-on-error: true
run: |
# (go test -v -coverprofile=cover-profile.out -race ./... 2>&1) > test-results/test-result.out
go test -tags nobadger -v -coverprofile="cover-profile.out" -short -race ./...
# echo "status=$?" >> $GITHUB_OUTPUT
# Relevant step if we reinvestigate publishing test/coverage reports
# - name: Prepare coverage reports
# run: |
# mkdir coverage
# gocov convert cover-profile.out > coverage/coverage.json
# # Because Windows doesn't work with input redirection like *nix, but output redirection works.
# (cat ./coverage/coverage.json | gocov-xml) > coverage/coverage.xml
# To return the correct result even though we set 'continue-on-error: true'
# - name: Coerce correct build result
# if: matrix.os != 'windows' && steps.step_test.outputs.status != ${{ matrix.SUCCESS }}
# run: |
# echo "step_test ${{ steps.step_test.outputs.status }}\n"
# exit 1
s390x-test:
name: test (s390x on IBM Z)
runs-on: ubuntu-latest
if: github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]'
continue-on-error: true # August 2020: s390x VM is down due to weather and power issues
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run Tests
run: |
mkdir -p ~/.ssh && echo -e "${SSH_KEY//_/\\n}" > ~/.ssh/id_ecdsa && chmod og-rwx ~/.ssh/id_ecdsa
# short sha is enough?
short_sha=$(git rev-parse --short HEAD)
# The environment is fresh, so there's no point in keeping accepting and adding the key.
rsync -arz -e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" --progress --delete --exclude '.git' . "$CI_USER"@ci-s390x.caddyserver.com:/var/tmp/"$short_sha"
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -t "$CI_USER"@ci-s390x.caddyserver.com "cd /var/tmp/$short_sha; go version; go env; printf "\n\n";CGO_ENABLED=0 go test -tags nobadger -v ./..."
test_result=$?
# There's no need leaving the files around
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null "$CI_USER"@ci-s390x.caddyserver.com "rm -rf /var/tmp/'$short_sha'"
echo "Test exit code: $test_result"
exit $test_result
env:
SSH_KEY: ${{ secrets.S390X_SSH_KEY }}
CI_USER: ${{ secrets.CI_USER }}
goreleaser-check:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- uses: goreleaser/goreleaser-action@v6
with:
version: latest
args: check
-69
View File
@@ -1,69 +0,0 @@
name: Cross-Build
on:
push:
branches:
- master
- 2.*
pull_request:
branches:
- master
- 2.*
jobs:
build:
strategy:
fail-fast: false
matrix:
goos:
- 'aix'
- 'linux'
- 'solaris'
- 'illumos'
- 'dragonfly'
- 'freebsd'
- 'openbsd'
- 'windows'
- 'darwin'
- 'netbsd'
go:
- '1.22'
include:
# Set the minimum Go patch version for the given Go minor
# Usable via ${{ matrix.GO_SEMVER }}
- go: '1.22'
GO_SEMVER: '~1.22.3'
runs-on: ubuntu-latest
continue-on-error: true
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.GO_SEMVER }}
check-latest: true
- name: Print Go version and environment
id: vars
run: |
printf "Using go at: $(which go)\n"
printf "Go version: $(go version)\n"
printf "\n\nGo environment:\n\n"
go env
printf "\n\nSystem environment:\n\n"
env
- name: Run Build
env:
CGO_ENABLED: 0
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goos == 'aix' && 'ppc64' || 'amd64' }}
shell: bash
continue-on-error: true
working-directory: ./cmd/caddy
run: |
GOOS=$GOOS GOARCH=$GOARCH go build -tags nobadger -trimpath -o caddy-"$GOOS"-$GOARCH 2> /dev/null
-67
View File
@@ -1,67 +0,0 @@
name: Lint
on:
push:
branches:
- master
- 2.*
pull_request:
branches:
- master
- 2.*
permissions:
contents: read
jobs:
# From https://github.com/golangci/golangci-lint-action
golangci:
permissions:
contents: read # for actions/checkout to fetch code
pull-requests: read # for golangci/golangci-lint-action to fetch pull requests
name: lint
strategy:
matrix:
os:
- linux
- mac
- windows
include:
- os: linux
OS_LABEL: ubuntu-latest
- os: mac
OS_LABEL: macos-14
- os: windows
OS_LABEL: windows-latest
runs-on: ${{ matrix.OS_LABEL }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '~1.22.3'
check-latest: true
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
with:
version: v1.55
# Windows times out frequently after about 5m50s if we don't set a longer timeout.
args: --timeout 10m
# Optional: show only new issues if it's a pull request. The default value is `false`.
# only-new-issues: true
govulncheck:
runs-on: ubuntu-latest
steps:
- name: govulncheck
uses: golang/govulncheck-action@v1
with:
go-version-input: '~1.22.3'
check-latest: true
-174
View File
@@ -1,174 +0,0 @@
name: Release
on:
push:
tags:
- 'v*.*.*'
jobs:
release:
name: Release
strategy:
matrix:
os:
- ubuntu-latest
go:
- '1.22'
include:
# Set the minimum Go patch version for the given Go minor
# Usable via ${{ matrix.GO_SEMVER }}
- go: '1.22'
GO_SEMVER: '~1.22.3'
runs-on: ${{ matrix.os }}
# https://github.com/sigstore/cosign/issues/1258#issuecomment-1002251233
# https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#adding-permissions-settings
permissions:
id-token: write
# https://docs.github.com/en/rest/overview/permissions-required-for-github-apps#permission-on-contents
# "Releases" is part of `contents`, so it needs the `write`
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.GO_SEMVER }}
check-latest: true
# Force fetch upstream tags -- because 65 minutes
# tl;dr: actions/checkout@v4 runs this line:
# git -c protocol.version=2 fetch --no-tags --prune --progress --no-recurse-submodules --depth=1 origin +ebc278ec98bb24f2852b61fde2a9bf2e3d83818b:refs/tags/
# which makes its own local lightweight tag, losing all the annotations in the process. Our earlier script ran:
# git fetch --prune --unshallow
# which doesn't overwrite that tag because that would be destructive.
# Credit to @francislavoie for the investigation.
# https://github.com/actions/checkout/issues/290#issuecomment-680260080
- name: Force fetch upstream tags
run: git fetch --tags --force
# https://github.community/t5/GitHub-Actions/How-to-get-just-the-tag-name/m-p/32167/highlight/true#M1027
- name: Print Go version and environment
id: vars
run: |
printf "Using go at: $(which go)\n"
printf "Go version: $(go version)\n"
printf "\n\nGo environment:\n\n"
go env
printf "\n\nSystem environment:\n\n"
env
echo "version_tag=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_OUTPUT
echo "short_sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
# Add "pip install" CLI tools to PATH
echo ~/.local/bin >> $GITHUB_PATH
# Parse semver
TAG=${GITHUB_REF/refs\/tags\//}
SEMVER_RE='[^0-9]*\([0-9]*\)[.]\([0-9]*\)[.]\([0-9]*\)\([0-9A-Za-z\.-]*\)'
TAG_MAJOR=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\1#"`
TAG_MINOR=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\2#"`
TAG_PATCH=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\3#"`
TAG_SPECIAL=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\4#"`
echo "tag_major=${TAG_MAJOR}" >> $GITHUB_OUTPUT
echo "tag_minor=${TAG_MINOR}" >> $GITHUB_OUTPUT
echo "tag_patch=${TAG_PATCH}" >> $GITHUB_OUTPUT
echo "tag_special=${TAG_SPECIAL}" >> $GITHUB_OUTPUT
# Cloudsmith CLI tooling for pushing releases
# See https://help.cloudsmith.io/docs/cli
- name: Install Cloudsmith CLI
run: pip install --upgrade cloudsmith-cli
- name: Validate commits and tag signatures
run: |
# Import Matt Holt's key
curl 'https://github.com/mholt.gpg' | gpg --import
echo "Verifying the tag: ${{ steps.vars.outputs.version_tag }}"
# tags are only accepted if signed by Matt's key
git verify-tag "${{ steps.vars.outputs.version_tag }}" || exit 1
- name: Install Cosign
uses: sigstore/cosign-installer@main
- name: Cosign version
run: cosign version
- name: Install Syft
uses: anchore/sbom-action/download-syft@main
- name: Syft version
run: syft version
# GoReleaser will take care of publishing those artifacts into the release
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
with:
version: latest
args: release --clean --timeout 60m
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ steps.vars.outputs.version_tag }}
COSIGN_EXPERIMENTAL: 1
# Only publish on non-special tags (e.g. non-beta)
# We will continue to push to Gemfury for the foreseeable future, although
# Cloudsmith is probably better, to not break things for existing users of Gemfury.
# See https://gemfury.com/caddy/deb:caddy
- name: Publish .deb to Gemfury
if: ${{ steps.vars.outputs.tag_special == '' }}
env:
GEMFURY_PUSH_TOKEN: ${{ secrets.GEMFURY_PUSH_TOKEN }}
run: |
for filename in dist/*.deb; do
# armv6 and armv7 are both "armhf" so we can skip the duplicate
if [[ "$filename" == *"armv6"* ]]; then
echo "Skipping $filename"
continue
fi
curl -F package=@"$filename" https://${GEMFURY_PUSH_TOKEN}:@push.fury.io/caddy/
done
# Publish only special tags (unstable/beta/rc) to the "testing" repo
# See https://cloudsmith.io/~caddy/repos/testing/
- name: Publish .deb to Cloudsmith (special tags)
if: ${{ steps.vars.outputs.tag_special != '' }}
env:
CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }}
run: |
for filename in dist/*.deb; do
# armv6 and armv7 are both "armhf" so we can skip the duplicate
if [[ "$filename" == *"armv6"* ]]; then
echo "Skipping $filename"
continue
fi
echo "Pushing $filename to 'testing'"
cloudsmith push deb caddy/testing/any-distro/any-version $filename
done
# Publish stable tags to Cloudsmith to both repos, "stable" and "testing"
# See https://cloudsmith.io/~caddy/repos/stable/
- name: Publish .deb to Cloudsmith (stable tags)
if: ${{ steps.vars.outputs.tag_special == '' }}
env:
CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }}
run: |
for filename in dist/*.deb; do
# armv6 and armv7 are both "armhf" so we can skip the duplicate
if [[ "$filename" == *"armv6"* ]]; then
echo "Skipping $filename"
continue
fi
echo "Pushing $filename to 'stable'"
cloudsmith push deb caddy/stable/any-distro/any-version $filename
echo "Pushing $filename to 'testing'"
cloudsmith push deb caddy/testing/any-distro/any-version $filename
done
-35
View File
@@ -1,35 +0,0 @@
name: Release Published
# Event payload: https://developer.github.com/webhooks/event-payloads/#release
on:
release:
types: [published]
jobs:
release:
name: Release Published
strategy:
matrix:
os:
- ubuntu-latest
runs-on: ${{ matrix.os }}
steps:
# See https://github.com/peter-evans/repository-dispatch
- name: Trigger event on caddyserver/dist
uses: peter-evans/repository-dispatch@v3
with:
token: ${{ secrets.REPO_DISPATCH_TOKEN }}
repository: caddyserver/dist
event-type: release-tagged
client-payload: '{"tag": "${{ github.event.release.tag_name }}"}'
- name: Trigger event on caddyserver/caddy-docker
uses: peter-evans/repository-dispatch@v3
with:
token: ${{ secrets.REPO_DISPATCH_TOKEN }}
repository: caddyserver/caddy-docker
event-type: release-tagged
client-payload: '{"tag": "${{ github.event.release.tag_name }}"}'
+15 -27
View File
@@ -1,31 +1,19 @@
_gitignore/
*.log
Caddyfile
Caddyfile.*
!caddyfile/
!caddyfile.go
# artifacts from pprof tooling
*.prof
*.test
# build artifacts and helpers
cmd/caddy/caddy
cmd/caddy/caddy.exe
cmd/caddy/tmp/*.exe
cmd/caddy/.env
# mac specific
.DS_Store
Thumbs.db
_gitignore/
Vagrantfile
.vagrant/
/.idea
# go modules
vendor
dist/builds/
dist/release/
# goreleaser artifacts
dist
caddy-build
caddy-dist
error.log
access.log
# IDE files
.idea/
.vscode/
/*.conf
Caddyfile
og_static/
.vscode/
-168
View File
@@ -1,168 +0,0 @@
linters-settings:
errcheck:
ignore: fmt:.*,go.uber.org/zap/zapcore:^Add.*
ignoretests: true
gci:
sections:
- standard # Standard section: captures all standard packages.
- default # Default section: contains all imports that could not be matched to another section type.
- prefix(github.com/caddyserver/caddy/v2/cmd) # ensure that this is always at the top and always has a line break.
- prefix(github.com/caddyserver/caddy) # Custom section: groups all imports with the specified Prefix.
# Skip generated files.
# Default: true
skip-generated: true
# Enable custom order of sections.
# If `true`, make the section order the same as the order of `sections`.
# Default: false
custom-order: true
exhaustive:
ignore-enum-types: reflect.Kind|svc.Cmd
linters:
disable-all: true
enable:
- asasalint
- asciicheck
- bidichk
- bodyclose
- decorder
- dogsled
- dupl
- dupword
- durationcheck
- errcheck
- errname
- exhaustive
- exportloopref
- gci
- gofmt
- goimports
- gofumpt
- gosec
- gosimple
- govet
- ineffassign
- importas
- misspell
- prealloc
- promlinter
- sloglint
- sqlclosecheck
- staticcheck
- tenv
- testableexamples
- testifylint
- tparallel
- typecheck
- unconvert
- unused
- wastedassign
- whitespace
- zerologlint
# these are implicitly disabled:
# - containedctx
# - contextcheck
# - cyclop
# - depguard
# - errchkjson
# - errorlint
# - exhaustruct
# - execinquery
# - exhaustruct
# - forbidigo
# - forcetypeassert
# - funlen
# - ginkgolinter
# - gocheckcompilerdirectives
# - gochecknoglobals
# - gochecknoinits
# - gochecksumtype
# - gocognit
# - goconst
# - gocritic
# - gocyclo
# - godot
# - godox
# - goerr113
# - goheader
# - gomnd
# - gomoddirectives
# - gomodguard
# - goprintffuncname
# - gosmopolitan
# - grouper
# - inamedparam
# - interfacebloat
# - ireturn
# - lll
# - loggercheck
# - maintidx
# - makezero
# - mirror
# - musttag
# - nakedret
# - nestif
# - nilerr
# - nilnil
# - nlreturn
# - noctx
# - nolintlint
# - nonamedreturns
# - nosprintfhostport
# - paralleltest
# - perfsprint
# - predeclared
# - protogetter
# - reassign
# - revive
# - rowserrcheck
# - stylecheck
# - tagalign
# - tagliatelle
# - testpackage
# - thelper
# - unparam
# - usestdlibvars
# - varnamelen
# - wrapcheck
# - wsl
run:
# default concurrency is a available CPU number.
# concurrency: 4 # explicitly omit this value to fully utilize available resources.
deadline: 5m
issues-exit-code: 1
tests: false
# output configuration options
output:
format: 'colored-line-number'
print-issued-lines: true
print-linter-name: true
issues:
exclude-rules:
# we aren't calling unknown URL
- text: 'G107' # G107: Url provided to HTTP request as taint input
linters:
- gosec
# as a web server that's expected to handle any template, this is totally in the hands of the user.
- text: 'G203' # G203: Use of unescaped data in HTML templates
linters:
- gosec
# we're shelling out to known commands, not relying on user-defined input.
- text: 'G204' # G204: Audit use of command execution
linters:
- gosec
# the choice of weakrand is deliberate, hence the named import "weakrand"
- path: modules/caddyhttp/reverseproxy/selectionpolicies.go
text: 'G404' # G404: Insecure random number source (rand)
linters:
- gosec
- path: modules/caddyhttp/reverseproxy/streaming.go
text: 'G404' # G404: Insecure random number source (rand)
linters:
- gosec
- path: modules/logging/filters.go
linters:
- dupl
-207
View File
@@ -1,207 +0,0 @@
version: 2
before:
hooks:
# The build is done in this particular way to build Caddy in a designated directory named in .gitignore.
# This is so we can run goreleaser on tag without Git complaining of being dirty. The main.go in cmd/caddy directory
# cannot be built within that directory due to changes necessary for the build causing Git to be dirty, which
# subsequently causes gorleaser to refuse running.
- rm -rf caddy-build caddy-dist vendor
# vendor Caddy deps
- go mod vendor
- mkdir -p caddy-build
- cp cmd/caddy/main.go caddy-build/main.go
- /bin/sh -c 'cd ./caddy-build && go mod init caddy'
# GoReleaser doesn't seem to offer {{.Tag}} at this stage, so we have to embed it into the env
# so we run: TAG=$(git describe --abbrev=0) goreleaser release --rm-dist --skip-publish --skip-validate
- go mod edit -require=github.com/caddyserver/caddy/v2@{{.Env.TAG}} ./caddy-build/go.mod
# as of Go 1.16, `go` commands no longer automatically change go.{mod,sum}. We now have to explicitly
# run `go mod tidy`. The `/bin/sh -c '...'` is because goreleaser can't find cd in PATH without shell invocation.
- /bin/sh -c 'cd ./caddy-build && go mod tidy'
# vendor the deps of the prepared to-build module
- /bin/sh -c 'cd ./caddy-build && go mod vendor'
- git clone --depth 1 https://github.com/caddyserver/dist caddy-dist
- mkdir -p caddy-dist/man
- go mod download
- go run cmd/caddy/main.go manpage --directory ./caddy-dist/man
- gzip -r ./caddy-dist/man/
- /bin/sh -c 'go run cmd/caddy/main.go completion bash > ./caddy-dist/scripts/bash-completion'
builds:
- env:
- CGO_ENABLED=0
- GO111MODULE=on
main: main.go
dir: ./caddy-build
binary: caddy
goos:
- darwin
- linux
- windows
- freebsd
goarch:
- amd64
- arm
- arm64
- s390x
- ppc64le
- riscv64
goarm:
- "5"
- "6"
- "7"
ignore:
- goos: darwin
goarch: arm
- goos: darwin
goarch: ppc64le
- goos: darwin
goarch: s390x
- goos: darwin
goarch: riscv64
- goos: windows
goarch: ppc64le
- goos: windows
goarch: s390x
- goos: windows
goarch: riscv64
- goos: freebsd
goarch: ppc64le
- goos: freebsd
goarch: s390x
- goos: freebsd
goarch: riscv64
- goos: freebsd
goarch: arm
goarm: "5"
flags:
- -trimpath
- -mod=readonly
ldflags:
- -s -w
tags:
- nobadger
signs:
- cmd: cosign
signature: "${artifact}.sig"
certificate: '{{ trimsuffix (trimsuffix .Env.artifact ".zip") ".tar.gz" }}.pem'
args: ["sign-blob", "--yes", "--output-signature=${signature}", "--output-certificate", "${certificate}", "${artifact}"]
artifacts: all
sboms:
- artifacts: binary
documents:
- >-
{{ .ProjectName }}_
{{- .Version }}_
{{- if eq .Os "darwin" }}mac{{ else }}{{ .Os }}{{ end }}_
{{- .Arch }}
{{- with .Arm }}v{{ . }}{{ end }}
{{- with .Mips }}_{{ . }}{{ end }}
{{- if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}.sbom
cmd: syft
args: ["$artifact", "--file", "${document}", "--output", "cyclonedx-json"]
archives:
- id: default
format_overrides:
- goos: windows
format: zip
name_template: >-
{{ .ProjectName }}_
{{- .Version }}_
{{- if eq .Os "darwin" }}mac{{ else }}{{ .Os }}{{ end }}_
{{- .Arch }}
{{- with .Arm }}v{{ . }}{{ end }}
{{- with .Mips }}_{{ . }}{{ end }}
{{- if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}
# package the 'caddy-build' directory into a tarball,
# allowing users to build the exact same set of files as ours.
- id: source
meta: true
name_template: "{{ .ProjectName }}_{{ .Version }}_buildable-artifact"
files:
- src: LICENSE
dst: ./LICENSE
- src: README.md
dst: ./README.md
- src: AUTHORS
dst: ./AUTHORS
- src: ./caddy-build
dst: ./
source:
enabled: true
name_template: '{{ .ProjectName }}_{{ .Version }}_src'
format: 'tar.gz'
# Additional files/template/globs you want to add to the source archive.
#
# Default: empty.
files:
- vendor
checksum:
algorithm: sha512
nfpms:
- id: default
package_name: caddy
vendor: Dyanim
homepage: https://caddyserver.com
maintainer: Matthew Holt <mholt@users.noreply.github.com>
description: |
Caddy - Powerful, enterprise-ready, open source web server with automatic HTTPS written in Go
license: Apache 2.0
formats:
- deb
# - rpm
bindir: /usr/bin
contents:
- src: ./caddy-dist/init/caddy.service
dst: /lib/systemd/system/caddy.service
- src: ./caddy-dist/init/caddy-api.service
dst: /lib/systemd/system/caddy-api.service
- src: ./caddy-dist/welcome/index.html
dst: /usr/share/caddy/index.html
- src: ./caddy-dist/scripts/bash-completion
dst: /etc/bash_completion.d/caddy
- src: ./caddy-dist/config/Caddyfile
dst: /etc/caddy/Caddyfile
type: config
- src: ./caddy-dist/man/*
dst: /usr/share/man/man8/
scripts:
postinstall: ./caddy-dist/scripts/postinstall.sh
preremove: ./caddy-dist/scripts/preremove.sh
postremove: ./caddy-dist/scripts/postremove.sh
release:
github:
owner: caddyserver
name: caddy
draft: true
prerelease: auto
changelog:
sort: asc
filters:
exclude:
- '^chore:'
- '^ci:'
- '^docs?:'
- '^readme:'
- '^tests?:'
- '^\w+\s+' # a hack to remove commit messages without colons thus don't correspond to a package
+39
View File
@@ -0,0 +1,39 @@
language: go
go:
- 1.8.3
- tip
matrix:
allow_failures:
- go: tip
fast_finish: true
before_install:
# Decrypts a script that installs an authenticated cookie
# for git to use when cloning from googlesource.com.
# Bypasses "bandwidth limit exceeded" errors.
# See github.com/golang/go/issues/12933
- if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then openssl aes-256-cbc -K $encrypted_3df18f9af81d_key -iv $encrypted_3df18f9af81d_iv -in dist/gitcookie.sh.enc -out dist/gitcookie.sh -d; fi
install:
- if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then bash dist/gitcookie.sh; fi
- go get -t ./...
- go get github.com/golang/lint/golint
- go get github.com/FiloSottile/vendorcheck
# Install gometalinter and certain linters
- go get github.com/alecthomas/gometalinter
- go get github.com/client9/misspell/cmd/misspell
- go get github.com/gordonklaus/ineffassign
- go get golang.org/x/tools/cmd/goimports
- go get github.com/tsenart/deadcode
script:
- gometalinter --disable-all -E vet -E gofmt -E misspell -E ineffassign -E goimports -E deadcode --tests --vendor ./...
- vendorcheck ./...
# TODO: When Go 1.9 is released, replace $(go list) subcommand with ./... because vendor folder should be ignored
- go test -race $(go list ./... | grep -v vendor)
after_script:
# TODO: When Go 1.9 is released, replace $(go list) subcommand with ./... because vendor folder should be ignored
- golint $(go list ./... | grep -v vendor)
-10
View File
@@ -1,10 +0,0 @@
# This is the official list of Caddy Authors for copyright purposes.
# Authors may be either individual people or legal entities.
#
# Not all individual contributors are authors. For the full list of
# contributors, refer to the project's page on GitHub or the repo's
# commit history.
Matthew Holt <Matthew.Holt@gmail.com>
Light Code Labs <sales@lightcodelabs.com>
Ardan Labs <info@ardanlabs.com>
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+107 -149
View File
@@ -1,200 +1,158 @@
<p align="center">
<a href="https://caddyserver.com">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/1128849/210187358-e2c39003-9a5e-4dd5-a783-6deb6483ee72.svg">
<source media="(prefers-color-scheme: light)" srcset="https://user-images.githubusercontent.com/1128849/210187356-dfb7f1c5-ac2e-43aa-bb23-fc014280ae1f.svg">
<img src="https://user-images.githubusercontent.com/1128849/210187356-dfb7f1c5-ac2e-43aa-bb23-fc014280ae1f.svg" alt="Caddy" width="550">
</picture>
</a>
<br>
<h3 align="center">a <a href="https://zerossl.com"><img src="https://user-images.githubusercontent.com/55066419/208327323-2770dc16-ec09-43a0-9035-c5b872c2ad7f.svg" height="28" style="vertical-align: -7.7px" valign="middle"></a> project</h3>
<a href="https://caddyserver.com"><img src="https://cloud.githubusercontent.com/assets/1128849/25305033/12916fce-2731-11e7-86ec-580d4d31cb16.png" alt="Caddy" width="400"></a>
</p>
<hr>
<h3 align="center">Every site on HTTPS</h3>
<p align="center">Caddy is an extensible server platform that uses TLS by default.</p>
<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://github.com/caddyserver/caddy/actions/workflows/ci.yml"><img src="https://github.com/caddyserver/caddy/actions/workflows/ci.yml/badge.svg"></a>
<a href="https://pkg.go.dev/github.com/caddyserver/caddy/v2"><img src="https://img.shields.io/badge/godoc-reference-%23007d9c.svg"></a>
<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>
<br>
<a href="https://sourcegraph.com/github.com/caddyserver/caddy?badge" title="Caddy on Sourcegraph"><img src="https://sourcegraph.com/github.com/caddyserver/caddy/-/badge.svg" alt="Caddy on Sourcegraph"></a>
<a href="https://cloudsmith.io/~caddy/repos/"><img src="https://img.shields.io/badge/OSS%20hosting%20by-cloudsmith-blue?logo=cloudsmith" alt="Cloudsmith"></a>
<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://github.com/caddyserver/caddy/releases">Releases</a> ·
<a href="https://caddyserver.com/docs/">Documentation</a> ·
<a href="https://caddy.community">Get Help</a>
<a href="https://caddyserver.com/download">Download</a> ·
<a href="https://caddyserver.com/docs">Documentation</a> ·
<a href="https://caddy.community">Community</a>
</p>
---
Caddy is fast, easy to use, and makes you more productive.
### Menu
Available for Windows, Mac, Linux, BSD, Solaris, and [Android](https://github.com/mholt/caddy/wiki/Running-Caddy-on-Android).
## Menu
- [Features](#features)
- [Install](#install)
- [Build from source](#build-from-source)
- [For development](#for-development)
- [With version information and/or plugins](#with-version-information-andor-plugins)
- [Quick start](#quick-start)
- [Overview](#overview)
- [Full documentation](#full-documentation)
- [Getting help](#getting-help)
- [About](#about)
- [Quick Start](#quick-start)
- [Running in Production](#running-in-production)
- [Contributing](#contributing)
- [Donors](#donors)
- [About the Project](#about-the-project)
<p align="center">
<b>Powered by</b>
<br>
<a href="https://github.com/caddyserver/certmagic">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/55066419/206946718-740b6371-3df3-4d72-a822-47e4c48af999.png">
<source media="(prefers-color-scheme: light)" srcset="https://user-images.githubusercontent.com/1128849/49704830-49d37200-fbd5-11e8-8385-767e0cd033c3.png">
<img src="https://user-images.githubusercontent.com/1128849/49704830-49d37200-fbd5-11e8-8385-767e0cd033c3.png" alt="CertMagic" width="250">
</picture>
</a>
</p>
## Features
## [Features](https://caddyserver.com/features)
- **Easy configuration** with the [Caddyfile](https://caddyserver.com/docs/caddyfile)
- **Powerful configuration** with its [native JSON config](https://caddyserver.com/docs/json/)
- **Dynamic configuration** with the [JSON API](https://caddyserver.com/docs/api)
- [**Config adapters**](https://caddyserver.com/docs/config-adapters) if you don't like JSON
- **Automatic HTTPS** by default
- [ZeroSSL](https://zerossl.com) and [Let's Encrypt](https://letsencrypt.org) for public names
- Fully-managed local CA for internal names & IPs
- Can coordinate with other Caddy instances in a cluster
- Multi-issuer fallback
- **Stays up when other servers go down** due to TLS/OCSP/certificate-related issues
- **Production-ready** after serving trillions of requests and managing millions of TLS certificates
- **Scales to hundreds of thousands of sites** as proven in production
- **HTTP/1.1, HTTP/2, and HTTP/3** all supported by default
- **Highly extensible** [modular architecture](https://caddyserver.com/docs/architecture) lets Caddy do anything without bloat
- **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 those that like speed
- 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)
- Written in Go, a language with higher **memory safety guarantees** than other servers
- Actually **fun to use**
- So much more to [discover](https://caddyserver.com/features)
There's way more, too! [See all 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).
## Install
The simplest, cross-platform way to get started is to download Caddy from [GitHub Releases](https://github.com/caddyserver/caddy/releases) and place the executable file in your PATH.
Caddy binaries have no dependencies and are available for every platform. Get Caddy any one of these ways:
See [our online documentation](https://caddyserver.com/docs/install) for other install instructions.
- **[Download page](https://caddyserver.com/download)** allows you to
customize your build in the browser
- **[Latest release](https://github.com/mholt/caddy/releases/latest)** for
pre-built, vanilla binaries
- **go get** to build from source: `go get github.com/mholt/caddy/caddy` (requires Go 1.8 or newer)
## Build from source
Then make sure the `caddy` binary is in your PATH.
Requirements:
- [Go 1.21 or newer](https://golang.org/dl/)
## Quick Start
### For development
_**Note:** These steps [will not embed proper version information](https://github.com/golang/go/issues/29228). For that, please follow the instructions in the next section._
```bash
$ git clone "https://github.com/caddyserver/caddy.git"
$ cd caddy/cmd/caddy/
$ go build
```
When you run Caddy, it may try to bind to low ports unless otherwise specified in your config. If your OS requires elevated privileges for this, you will need to give your new binary permission to do so. On Linux, this can be done easily with: `sudo setcap cap_net_bind_service=+ep ./caddy`
If you prefer to use `go run` which only creates temporary binaries, you can still do this with the included `setcap.sh` like so:
```bash
$ go run -exec ./setcap.sh main.go
```
If you don't want to type your password for `setcap`, use `sudo visudo` to edit your sudoers file and allow your user account to run that command without a password, for example:
To serve static files from the current working directory, run:
```
username ALL=(ALL:ALL) NOPASSWD: /usr/sbin/setcap
caddy
```
replacing `username` with your actual username. Please be careful and only do this if you know what you are doing! We are only qualified to document how to use Caddy, not Go tooling or your computer, and we are providing these instructions for convenience only; please learn how to use your own computer at your own risk and make any needful adjustments.
Caddy's default port is 2015, so open your browser to [http://localhost:2015](http://localhost:2015).
### With version information and/or plugins
### Go from 0 to HTTPS in 5 seconds
Using [our builder tool, `xcaddy`](https://github.com/caddyserver/xcaddy)...
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:
```
$ xcaddy build
caddy -host example.com
```
...the following steps are automated:
This command serves static files from the current directory over HTTPS. Certificates are automatically obtained and renewed for you!
1. Create a new folder: `mkdir caddy`
2. Change into it: `cd caddy`
3. Copy [Caddy's main.go](https://github.com/caddyserver/caddy/blob/master/cmd/caddy/main.go) into the empty folder. Add imports for any custom plugins you want to add.
4. Initialize a Go module: `go mod init caddy`
5. (Optional) Pin Caddy version: `go get github.com/caddyserver/caddy/v2@version` replacing `version` with a git tag, commit, or branch name.
6. (Optional) Add plugins by adding their import: `_ "import/path/here"`
7. Compile: `go build`
### 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
push
browse
websocket /echo cat
ext .html
log /var/log/access.log
proxy /api 127.0.0.1:7005
header /api Access-Control-Allow-Origin *
```
When you run `caddy` in that directory, it will automatically find and use that Caddyfile.
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
To host multiple sites and do more with the Caddyfile, please see the [Caddyfile tutorial](https://caddyserver.com/tutorial/caddyfile).
Sites with qualifying hostnames are served over [HTTPS by default](https://caddyserver.com/docs/automatic-https).
Caddy has a 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 as root:** We advise against this. You can still listen on ports < 1024 on Linux using setcap like so: `sudo setcap cap_net_bind_service=+ep ./caddy`
The Caddy project does not officially maintain any system-specific integrations nor suggest how to administer your own system. But your download file includes [unofficial resources](https://github.com/mholt/caddy/tree/master/dist/init) contributed by the community that you may find helpful for running Caddy in production.
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.
## Quick start
## Contributing
The [Caddy website](https://caddyserver.com/docs/) has documentation that includes tutorials, quick-start guides, reference, and more.
**[Join our forum](https://caddy.community) where you can chat with other Caddy users and developers!** To get familiar with the code base, try [Caddy code search on Sourcegraph](https://sourcegraph.com/github.com/mholt/caddy/-/search)!
**We recommend that all users -- regardless of experience level -- do our [Getting Started](https://caddyserver.com/docs/getting-started) guide to become familiar with using Caddy.**
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).
If you've only got a minute, [the website has several quick-start tutorials](https://caddyserver.com/docs/quick-starts) to choose from! However, after finishing a quick-start tutorial, please read more documentation to understand how the software works. 🙂
We use GitHub issues and pull requests only for discussing bug reports and the development of specific changes. We welcome all other topics on the [forum](https://caddy.community)!
If you want to contribute to the documentation, please submit pull requests to [caddyserver/website](https://github.com/caddyserver/website).
Thanks for making Caddy -- and the Web -- better!
## 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)!**
## Overview
## About the Project
Caddy is most often used as an HTTPS server, but it is suitable for any long-running Go program. First and foremost, it is a platform to run Go applications. Caddy "apps" are just Go programs that are implemented as Caddy modules. Two apps -- `tls` and `http` -- ship standard with Caddy.
Caddy 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.
Caddy apps instantly benefit from [automated documentation](https://caddyserver.com/docs/json/), graceful on-line [config changes via API](https://caddyserver.com/docs/api), and unification with other Caddy apps.
**The name "Caddy":** 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).
Although [JSON](https://caddyserver.com/docs/json/) is Caddy's native config language, Caddy can accept input from [config adapters](https://caddyserver.com/docs/config-adapters) which can essentially convert any config format of your choice into JSON: Caddyfile, JSON 5, YAML, TOML, NGINX config, and more.
The primary way to configure Caddy is through [its API](https://caddyserver.com/docs/api), but if you prefer config files, the [command-line interface](https://caddyserver.com/docs/command-line) supports those too.
Caddy exposes an unprecedented level of control compared to any web server in existence. In Caddy, you are usually setting the actual values of the initialized types in memory that power everything from your HTTP handlers and TLS handshakes to your storage medium. Caddy is also ridiculously extensible, with a powerful plugin system that makes vast improvements over other web servers.
To wield the power of this design, you need to know how the config document is structured. Please see [our documentation site](https://caddyserver.com/docs/) for details about [Caddy's config structure](https://caddyserver.com/docs/json/).
Nearly all of Caddy's configuration is contained in a single config document, rather than being scattered across CLI flags and env variables and a configuration file as with other web servers. This makes managing your server config more straightforward and reduces hidden variables/factors.
## Full documentation
Our website has complete documentation:
**https://caddyserver.com/docs/**
The docs are also open source. You can contribute to them here: https://github.com/caddyserver/website
## Getting help
- We advise companies using Caddy to secure a support contract through [Ardan Labs](https://www.ardanlabs.com/my/contact-us?dd=caddy) before help is needed.
- A [sponsorship](https://github.com/sponsors/mholt) goes a long way! We can offer private help to sponsors. If Caddy is benefitting your company, please consider a sponsorship. This not only helps fund full-time work to ensure the longevity of the project, it provides your company the resources, support, and discounts you need; along with being a great look for your company to your customers and potential customers!
- Individuals can exchange help for free on our community forum at https://caddy.community. Remember that people give help out of their spare time and good will. The best way to get help is to give it first!
Please use our [issue tracker](https://github.com/caddyserver/caddy/issues) only for bug reports and feature requests, i.e. actionable development items (support questions will usually be referred to the forums).
## About
Matthew Holt began developing Caddy in 2014 while studying computer science at Brigham Young University. (The name "Caddy" was chosen because this software helps with the tedious, mundane tasks of serving the Web, and is also a single place for multiple things to be organized together.) It soon became the first web server to use HTTPS automatically and by default, and now has hundreds of contributors and has served trillions of HTTPS requests.
**The name "Caddy" is trademarked.** The name of the software is "Caddy", not "Caddy Server" or "CaddyServer". Please call it "Caddy" or, if you wish to clarify, "the Caddy web server". Caddy is a registered trademark of Stack Holdings GmbH.
- _Project on Twitter: [@caddyserver](https://twitter.com/caddyserver)_
- _Author on Twitter: [@mholt6](https://twitter.com/mholt6)_
Caddy is a project of [ZeroSSL](https://zerossl.com), a Stack Holdings company.
Debian package repository hosting is graciously provided by [Cloudsmith](https://cloudsmith.com). Cloudsmith is the only fully hosted, cloud-native, universal package management solution, that enables your organization to create, store and share packages in any format, to any place, with total confidence.
*Author on Twitter: [@mholt6](https://twitter.com/mholt6)*
-1415
View File
File diff suppressed because it is too large Load Diff
-205
View File
@@ -1,205 +0,0 @@
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package caddy
import (
"encoding/json"
"fmt"
"net/http"
"reflect"
"sync"
"testing"
)
var testCfg = []byte(`{
"apps": {
"http": {
"servers": {
"myserver": {
"listen": ["tcp/localhost:8080-8084"],
"read_timeout": "30s"
},
"yourserver": {
"listen": ["127.0.0.1:5000"],
"read_header_timeout": "15s"
}
}
}
}
}
`)
func TestUnsyncedConfigAccess(t *testing.T) {
// each test is performed in sequence, so
// each change builds on the previous ones;
// the config is not reset between tests
for i, tc := range []struct {
method string
path string // rawConfigKey will be prepended
payload string
expect string // JSON representation of what the whole config is expected to be after the request
shouldErr bool
}{
{
method: "POST",
path: "",
payload: `{"foo": "bar", "list": ["a", "b", "c"]}`, // starting value
expect: `{"foo": "bar", "list": ["a", "b", "c"]}`,
},
{
method: "POST",
path: "/foo",
payload: `"jet"`,
expect: `{"foo": "jet", "list": ["a", "b", "c"]}`,
},
{
method: "POST",
path: "/bar",
payload: `{"aa": "bb", "qq": "zz"}`,
expect: `{"foo": "jet", "bar": {"aa": "bb", "qq": "zz"}, "list": ["a", "b", "c"]}`,
},
{
method: "DELETE",
path: "/bar/qq",
expect: `{"foo": "jet", "bar": {"aa": "bb"}, "list": ["a", "b", "c"]}`,
},
{
method: "DELETE",
path: "/bar/qq",
expect: `{"foo": "jet", "bar": {"aa": "bb"}, "list": ["a", "b", "c"]}`,
shouldErr: true,
},
{
method: "POST",
path: "/list",
payload: `"e"`,
expect: `{"foo": "jet", "bar": {"aa": "bb"}, "list": ["a", "b", "c", "e"]}`,
},
{
method: "PUT",
path: "/list/3",
payload: `"d"`,
expect: `{"foo": "jet", "bar": {"aa": "bb"}, "list": ["a", "b", "c", "d", "e"]}`,
},
{
method: "DELETE",
path: "/list/3",
expect: `{"foo": "jet", "bar": {"aa": "bb"}, "list": ["a", "b", "c", "e"]}`,
},
{
method: "PATCH",
path: "/list/3",
payload: `"d"`,
expect: `{"foo": "jet", "bar": {"aa": "bb"}, "list": ["a", "b", "c", "d"]}`,
},
{
method: "POST",
path: "/list/...",
payload: `["e", "f", "g"]`,
expect: `{"foo": "jet", "bar": {"aa": "bb"}, "list": ["a", "b", "c", "d", "e", "f", "g"]}`,
},
} {
err := unsyncedConfigAccess(tc.method, rawConfigKey+tc.path, []byte(tc.payload), nil)
if tc.shouldErr && err == nil {
t.Fatalf("Test %d: Expected error return value, but got: %v", i, err)
}
if !tc.shouldErr && err != nil {
t.Fatalf("Test %d: Should not have had error return value, but got: %v", i, err)
}
// decode the expected config so we can do a convenient DeepEqual
var expectedDecoded any
err = json.Unmarshal([]byte(tc.expect), &expectedDecoded)
if err != nil {
t.Fatalf("Test %d: Unmarshaling expected config: %v", i, err)
}
// make sure the resulting config is as we expect it
if !reflect.DeepEqual(rawCfg[rawConfigKey], expectedDecoded) {
t.Fatalf("Test %d:\nExpected:\n\t%#v\nActual:\n\t%#v",
i, expectedDecoded, rawCfg[rawConfigKey])
}
}
}
// TestLoadConcurrent exercises Load under concurrent conditions
// and is most useful under test with `-race` enabled.
func TestLoadConcurrent(t *testing.T) {
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
_ = Load(testCfg, true)
wg.Done()
}()
}
wg.Wait()
}
type fooModule struct {
IntField int
StrField string
}
func (fooModule) CaddyModule() ModuleInfo {
return ModuleInfo{
ID: "foo",
New: func() Module { return new(fooModule) },
}
}
func (fooModule) Start() error { return nil }
func (fooModule) Stop() error { return nil }
func TestETags(t *testing.T) {
RegisterModule(fooModule{})
if err := Load([]byte(`{"admin": {"listen": "localhost:2999"}, "apps": {"foo": {"strField": "abc", "intField": 0}}}`), true); err != nil {
t.Fatalf("loading: %s", err)
}
const key = "/" + rawConfigKey + "/apps/foo"
// try update the config with the wrong etag
err := changeConfig(http.MethodPost, key, []byte(`{"strField": "abc", "intField": 1}}`), fmt.Sprintf(`"/%s not_an_etag"`, rawConfigKey), false)
if apiErr, ok := err.(APIError); !ok || apiErr.HTTPStatus != http.StatusPreconditionFailed {
t.Fatalf("expected precondition failed; got %v", err)
}
// get the etag
hash := etagHasher()
if err := readConfig(key, hash); err != nil {
t.Fatalf("reading: %s", err)
}
// do the same update with the correct key
err = changeConfig(http.MethodPost, key, []byte(`{"strField": "abc", "intField": 1}`), makeEtag(key, hash), false)
if err != nil {
t.Fatalf("expected update to work; got %v", err)
}
// now try another update. The hash should no longer match and we should get precondition failed
err = changeConfig(http.MethodPost, key, []byte(`{"strField": "abc", "intField": 2}`), makeEtag(key, hash), false)
if apiErr, ok := err.(APIError); !ok || apiErr.HTTPStatus != http.StatusPreconditionFailed {
t.Fatalf("expected precondition failed; got %v", err)
}
}
func BenchmarkLoad(b *testing.B) {
for i := 0; i < b.N; i++ {
Load(testCfg, true)
}
}
+39
View File
@@ -0,0 +1,39 @@
version: "{build}"
os: Windows Server 2012 R2
clone_folder: c:\gopath\src\github.com\mholt\caddy
environment:
GOPATH: c:\gopath
install:
- rmdir c:\go /s /q
- appveyor DownloadFile https://storage.googleapis.com/golang/go1.8.3.windows-amd64.zip
- 7z x go1.8.3.windows-amd64.zip -y -oC:\ > NUL
- set PATH=%GOPATH%\bin;%PATH%
- go version
- go env
- go get -t ./...
- go get github.com/golang/lint/golint
- go get github.com/FiloSottile/vendorcheck
# Install gometalinter and certain linters
- go get github.com/alecthomas/gometalinter
- go get github.com/client9/misspell/cmd/misspell
- go get github.com/gordonklaus/ineffassign
- go get golang.org/x/tools/cmd/goimports
- go get github.com/tsenart/deadcode
build: off
test_script:
- gometalinter --disable-all -E vet -E gofmt -E misspell -E ineffassign -E goimports -E deadcode --tests --vendor ./...
- vendorcheck ./...
# TODO: When Go 1.9 comes out, replace this whole line with `go test -race ./...` b/c vendor folder should be ignored
- for /f "" %%G in ('go list ./... ^| find /i /v "/vendor/"') do (go test -race %%G & IF ERRORLEVEL == 1 EXIT 1)
after_test:
# TODO: When Go 1.9 comes out, replace this whole line with `golint ./...` b/c vendor folder should be ignored
- for /f "" %%G in ('go list ./... ^| find /i /v "/vendor/"') do (golint %%G & IF ERRORLEVEL == 1 EXIT 1)
deploy: off
+34
View File
@@ -0,0 +1,34 @@
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")
}
+19
View File
@@ -0,0 +1,19 @@
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)
}
os.Setenv("CADDYPATH", "testpath")
if actual, expected := AssetsPath(), "testpath"; actual != expected {
t.Errorf("Expected path to be %v, got: %v", expected, actual)
}
os.Setenv("CADDYPATH", "")
}
+868 -1050
View File
File diff suppressed because it is too large Load Diff
+56
View File
@@ -0,0 +1,56 @@
#!/usr/bin/env bash
#
# Caddy build script. Automates proper versioning.
#
# Usage:
#
# $ ./build.bash [output_filename] [git_repo]
#
# Outputs compiled program in current directory.
# Default git repo is current directory.
# Builds always take place from current directory.
set -euo pipefail
: ${output_filename:="${1:-}"}
: ${output_filename:=""}
: ${git_repo:="${2:-}"}
: ${git_repo:="."}
pkg=github.com/mholt/caddy/caddy/caddymain
ldflags=()
# Timestamp of build
name="${pkg}.buildDate"
value=$(date -u +"%a %b %d %H:%M:%S %Z %Y")
ldflags+=("-X" "\"${name}=${value}\"")
# Current tag, if HEAD is on a tag
name="${pkg}.gitTag"
set +e
value="$(git -C "${git_repo}" describe --exact-match HEAD 2>/dev/null)"
set -e
ldflags+=("-X" "\"${name}=${value}\"")
# Nearest tag on branch
name="${pkg}.gitNearestTag"
value="$(git -C "${git_repo}" describe --abbrev=0 --tags HEAD)"
ldflags+=("-X" "\"${name}=${value}\"")
# Commit SHA
name="${pkg}.gitCommit"
value="$(git -C "${git_repo}" rev-parse --short HEAD)"
ldflags+=("-X" "\"${name}=${value}\"")
# Summary of uncommitted changes
name="${pkg}.gitShortStat"
value="$(git -C "${git_repo}" diff-index --shortstat HEAD)"
ldflags+=("-X" "\"${name}=${value}\"")
# List of modified files
name="${pkg}.gitFilesModified"
value="$(git -C "${git_repo}" diff-index --name-only HEAD)"
ldflags+=("-X" "\"${name}=${value}\"")
go build -ldflags "${ldflags[*]}" -o "${output_filename}"
+260
View File
@@ -0,0 +1,260 @@
package caddymain
import (
"errors"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"runtime"
"strconv"
"strings"
"gopkg.in/natefinch/lumberjack.v2"
"github.com/xenolf/lego/acme"
"github.com/mholt/caddy"
// plug in the HTTP server type
_ "github.com/mholt/caddy/caddyhttp"
"github.com/mholt/caddy/caddytls"
// This is where other plugins get plugged in (imported)
)
func init() {
caddy.TrapSignals()
setVersion()
flag.BoolVar(&caddytls.Agreed, "agree", false, "Agree to the CA's Subscriber Agreement")
flag.StringVar(&caddytls.DefaultCAUrl, "ca", "https://acme-v01.api.letsencrypt.org/directory", "URL to certificate authority's ACME server directory")
flag.BoolVar(&caddytls.DisableHTTPChallenge, "disable-http-challenge", caddytls.DisableHTTPChallenge, "Disable the ACME HTTP challenge")
flag.BoolVar(&caddytls.DisableTLSSNIChallenge, "disable-tls-sni-challenge", caddytls.DisableTLSSNIChallenge, "Disable the ACME TLS-SNI challenge")
flag.StringVar(&conf, "conf", "", "Caddyfile to load (default \""+caddy.DefaultConfigFile+"\")")
flag.StringVar(&cpu, "cpu", "100%", "CPU cap")
flag.BoolVar(&plugins, "plugins", false, "List installed plugins")
flag.StringVar(&caddytls.DefaultEmail, "email", "", "Default ACME CA account email address")
flag.DurationVar(&acme.HTTPClient.Timeout, "catimeout", acme.HTTPClient.Timeout, "Default ACME CA HTTP timeout")
flag.StringVar(&logfile, "log", "", "Process log file")
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(&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
acme.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:
log.SetOutput(&lumberjack.Logger{
Filename: logfile,
MaxSize: 100,
MaxAge: 14,
MaxBackups: 10,
})
}
// 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\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)
}
// 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)
}
// 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)
}
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 != ""
// 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")
}
}
}
// 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%).
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)
} 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
}
const appName = "Caddy"
// Flags that control program flow or startup
var (
serverType string
conf string
cpu string
logfile string
revoke string
version bool
plugins bool
validate bool
)
// 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
)
+44
View File
@@ -0,0 +1,44 @@
package caddymain
import (
"runtime"
"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
} {
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)
}
}
+14
View File
@@ -0,0 +1,14 @@
// 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()
}
+17
View File
@@ -0,0 +1,17 @@
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")
}
}
+148 -63
View File
@@ -1,74 +1,159 @@
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package caddy
import (
"net"
"strconv"
"testing"
"time"
)
func TestParseDuration(t *testing.T) {
const day = 24 * time.Hour
for i, tc := range []struct {
input string
expect time.Duration
}{
{
input: "3h",
expect: 3 * time.Hour,
},
{
input: "1d",
expect: day,
},
{
input: "1d30m",
expect: day + 30*time.Minute,
},
{
input: "1m2d",
expect: time.Minute + day*2,
},
{
input: "1m2d30s",
expect: time.Minute + day*2 + 30*time.Second,
},
{
input: "1d2d",
expect: 3 * day,
},
{
input: "1.5d",
expect: time.Duration(1.5 * float64(day)),
},
{
input: "4m1.25d",
expect: 4*time.Minute + time.Duration(1.25*float64(day)),
},
{
input: "-1.25d12h",
expect: time.Duration(-1.25*float64(day)) - 12*time.Hour,
},
} {
actual, err := ParseDuration(tc.input)
/*
// 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.Errorf("Test %d ('%s'): Got error: %v", i, tc.input, err)
continue
t.Fatalf("Error starting, iteration %d: %v", i, err)
}
if actual != tc.expect {
t.Errorf("Test %d ('%s'): Expected=%s Actual=%s", i, tc.input, tc.expect, actual)
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)
}
}
}
*/
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)
}
}
}
func TestListenerAddrEqual(t *testing.T) {
ln1, err := net.Listen("tcp", "[::]:0")
if err != nil {
t.Fatal(err)
}
defer ln1.Close()
ln1port := strconv.Itoa(ln1.Addr().(*net.TCPAddr).Port)
ln2, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer ln2.Close()
ln2port := strconv.Itoa(ln2.Addr().(*net.TCPAddr).Port)
for i, test := range []struct {
ln net.Listener
addr string
expect bool
}{
{ln1, ":" + ln2port, false},
{ln1, "0.0.0.0:" + ln2port, false},
{ln1, "0.0.0.0", false},
{ln1, ":" + ln1port, true},
{ln1, "0.0.0.0:" + ln1port, true},
{ln2, ":" + ln2port, false},
{ln2, "127.0.0.1:" + ln1port, false},
{ln2, "127.0.0.1", false},
{ln2, "127.0.0.1:" + ln2port, true},
} {
if got, want := listenerAddrEqual(test.ln, test.addr), test.expect; got != want {
t.Errorf("Test %d (%s == %s): expected %v but was %v", i, test.addr, test.ln.Addr().String(), want, got)
}
}
}
-145
View File
@@ -1,145 +0,0 @@
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package caddyfile
import (
"bytes"
"encoding/json"
"fmt"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig"
)
// Adapter adapts Caddyfile to Caddy JSON.
type Adapter struct {
ServerType ServerType
}
// Adapt converts the Caddyfile config in body to Caddy JSON.
func (a Adapter) Adapt(body []byte, options map[string]any) ([]byte, []caddyconfig.Warning, error) {
if a.ServerType == nil {
return nil, nil, fmt.Errorf("no server type")
}
if options == nil {
options = make(map[string]any)
}
filename, _ := options["filename"].(string)
if filename == "" {
filename = "Caddyfile"
}
serverBlocks, err := Parse(filename, body)
if err != nil {
return nil, nil, err
}
cfg, warnings, err := a.ServerType.Setup(serverBlocks, options)
if err != nil {
return nil, warnings, err
}
// lint check: see if input was properly formatted; sometimes messy files parse
// successfully but result in logical errors (the Caddyfile is a bad format, I'm sorry)
if warning, different := FormattingDifference(filename, body); different {
warnings = append(warnings, warning)
}
result, err := json.Marshal(cfg)
return result, warnings, err
}
// FormattingDifference returns a warning and true if the formatted version
// is any different from the input; empty warning and false otherwise.
// TODO: also perform this check on imported files
func FormattingDifference(filename string, body []byte) (caddyconfig.Warning, bool) {
// replace windows-style newlines to normalize comparison
normalizedBody := bytes.Replace(body, []byte("\r\n"), []byte("\n"), -1)
formatted := Format(normalizedBody)
if bytes.Equal(formatted, normalizedBody) {
return caddyconfig.Warning{}, false
}
// find where the difference is
line := 1
for i, ch := range normalizedBody {
if i >= len(formatted) || ch != formatted[i] {
break
}
if ch == '\n' {
line++
}
}
return caddyconfig.Warning{
File: filename,
Line: line,
Message: "Caddyfile input is not formatted; run 'caddy fmt --overwrite' to fix inconsistencies",
}, true
}
// Unmarshaler is a type that can unmarshal Caddyfile tokens to
// set itself up for a JSON encoding. The goal of an unmarshaler
// is not to set itself up for actual use, but to set itself up for
// being marshaled into JSON. Caddyfile-unmarshaled values will not
// be used directly; they will be encoded as JSON and then used from
// that. Implementations _may_ be able to support multiple segments
// (instances of their directive or batch of tokens); typically this
// means wrapping parsing logic in a loop: `for d.Next() { ... }`.
// More commonly, only a single segment is supported, so a simple
// `d.Next()` at the start should be used to consume the module
// identifier token (directive name, etc).
type Unmarshaler interface {
UnmarshalCaddyfile(d *Dispenser) error
}
// ServerType is a type that can evaluate a Caddyfile and set up a caddy config.
type ServerType interface {
// Setup takes the server blocks which contain tokens,
// as well as options (e.g. CLI flags) and creates a
// Caddy config, along with any warnings or an error.
Setup([]ServerBlock, map[string]any) (*caddy.Config, []caddyconfig.Warning, error)
}
// UnmarshalModule instantiates a module with the given ID and invokes
// UnmarshalCaddyfile on the new value using the immediate next segment
// of d as input. In other words, d's next token should be the first
// token of the module's Caddyfile input.
//
// This function is used when the next segment of Caddyfile tokens
// belongs to another Caddy module. The returned value is often
// type-asserted to the module's associated type for practical use
// when setting up a config.
func UnmarshalModule(d *Dispenser, moduleID string) (Unmarshaler, error) {
mod, err := caddy.GetModule(moduleID)
if err != nil {
return nil, d.Errf("getting module named '%s': %v", moduleID, err)
}
inst := mod.New()
unm, ok := inst.(Unmarshaler)
if !ok {
return nil, d.Errf("module %s is not a Caddyfile unmarshaler; is %T", mod.ID, inst)
}
err = unm.UnmarshalCaddyfile(d.NewFromNextSegment())
if err != nil {
return nil, err
}
return unm, nil
}
// Interface guard
var _ caddyconfig.Adapter = (*Adapter)(nil)
-521
View File
@@ -1,521 +0,0 @@
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package caddyfile
import (
"errors"
"fmt"
"io"
"log"
"strconv"
"strings"
)
// Dispenser is a type that dispenses tokens, similarly to a lexer,
// except that it can do so with some notion of structure. An empty
// Dispenser is invalid; call NewDispenser to make a proper instance.
type Dispenser struct {
tokens []Token
cursor int
nesting int
// A map of arbitrary context data that can be used
// to pass through some information to unmarshalers.
context map[string]any
}
// NewDispenser returns a Dispenser filled with the given tokens.
func NewDispenser(tokens []Token) *Dispenser {
return &Dispenser{
tokens: tokens,
cursor: -1,
}
}
// NewTestDispenser parses input into tokens and creates a new
// Dispenser for test purposes only; any errors are fatal.
func NewTestDispenser(input string) *Dispenser {
tokens, err := allTokens("Testfile", []byte(input))
if err != nil && err != io.EOF {
log.Fatalf("getting all tokens from input: %v", err)
}
return NewDispenser(tokens)
}
// Next loads the next token. Returns true if a token
// was loaded; false otherwise. If false, all tokens
// have been consumed.
func (d *Dispenser) Next() bool {
if d.cursor < len(d.tokens)-1 {
d.cursor++
return true
}
return false
}
// Prev moves to the previous token. It does the inverse
// of Next(), except this function may decrement the cursor
// to -1 so that the next call to Next() points to the
// first token; this allows dispensing to "start over". This
// method returns true if the cursor ends up pointing to a
// valid token.
func (d *Dispenser) Prev() bool {
if d.cursor > -1 {
d.cursor--
return d.cursor > -1
}
return false
}
// NextArg loads the next token if it is on the same
// line and if it is not a block opening (open curly
// brace). Returns true if an argument token was
// loaded; false otherwise. If false, all tokens on
// the line have been consumed except for potentially
// a block opening. It handles imported tokens
// correctly.
func (d *Dispenser) NextArg() bool {
if !d.nextOnSameLine() {
return false
}
if d.Val() == "{" {
// roll back; a block opening is not an argument
d.cursor--
return false
}
return true
}
// nextOnSameLine advances the cursor if the next
// token is on the same line of the same file.
func (d *Dispenser) nextOnSameLine() bool {
if d.cursor < 0 {
d.cursor++
return true
}
if d.cursor >= len(d.tokens)-1 {
return false
}
curr := d.tokens[d.cursor]
next := d.tokens[d.cursor+1]
if !isNextOnNewLine(curr, next) {
d.cursor++
return true
}
return false
}
// NextLine loads the next token only if it is not on the same
// line as the current token, and returns true if a token was
// loaded; false otherwise. If false, there is not another token
// or it is on the same line. It handles imported tokens correctly.
func (d *Dispenser) NextLine() bool {
if d.cursor < 0 {
d.cursor++
return true
}
if d.cursor >= len(d.tokens)-1 {
return false
}
curr := d.tokens[d.cursor]
next := d.tokens[d.cursor+1]
if isNextOnNewLine(curr, next) {
d.cursor++
return true
}
return false
}
// NextBlock can be used as the condition of a for loop
// to load the next token as long as it opens a block or
// is already in a block nested more than initialNestingLevel.
// In other words, a loop over NextBlock() will iterate
// all tokens in the block assuming the next token is an
// open curly brace, until the matching closing brace.
// The open and closing brace tokens for the outer-most
// block will be consumed internally and omitted from
// the iteration.
//
// Proper use of this method looks like this:
//
// for nesting := d.Nesting(); d.NextBlock(nesting); {
// }
//
// However, in simple cases where it is known that the
// Dispenser is new and has not already traversed state
// by a loop over NextBlock(), this will do:
//
// for d.NextBlock(0) {
// }
//
// As with other token parsing logic, a loop over
// NextBlock() should be contained within a loop over
// Next(), as it is usually prudent to skip the initial
// token.
func (d *Dispenser) NextBlock(initialNestingLevel int) bool {
if d.nesting > initialNestingLevel {
if !d.Next() {
return false // should be EOF error
}
if d.Val() == "}" && !d.nextOnSameLine() {
d.nesting--
} else if d.Val() == "{" && !d.nextOnSameLine() {
d.nesting++
}
return d.nesting > initialNestingLevel
}
if !d.nextOnSameLine() { // block must open on same line
return false
}
if d.Val() != "{" {
d.cursor-- // roll back if not opening brace
return false
}
d.Next() // consume open curly brace
if d.Val() == "}" {
return false // open and then closed right away
}
d.nesting++
return true
}
// Nesting returns the current nesting level. Necessary
// if using NextBlock()
func (d *Dispenser) Nesting() int {
return d.nesting
}
// 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
}
// ValRaw gets the raw text of the current token (including quotes).
// If the token was a heredoc, then the delimiter is not included,
// because that is not relevant to any unmarshaling logic at this time.
// If there is no token loaded, it returns empty string.
func (d *Dispenser) ValRaw() string {
if d.cursor < 0 || d.cursor >= len(d.tokens) {
return ""
}
quote := d.tokens[d.cursor].wasQuoted
if quote > 0 && quote != '<' {
// string literal
return string(quote) + d.tokens[d.cursor].Text + string(quote)
}
return d.tokens[d.cursor].Text
}
// ScalarVal gets value of the current token, converted to the closest
// scalar type. If there is no token loaded, it returns nil.
func (d *Dispenser) ScalarVal() any {
if d.cursor < 0 || d.cursor >= len(d.tokens) {
return nil
}
quote := d.tokens[d.cursor].wasQuoted
text := d.tokens[d.cursor].Text
if quote > 0 {
return text // string literal
}
if num, err := strconv.Atoi(text); err == nil {
return num
}
if num, err := strconv.ParseFloat(text, 64); err == nil {
return num
}
if bool, err := strconv.ParseBool(text); err == nil {
return bool
}
return text
}
// Line gets the line number of the current token.
// If there is no token loaded, it returns 0.
func (d *Dispenser) Line() int {
if d.cursor < 0 || d.cursor >= len(d.tokens) {
return 0
}
return d.tokens[d.cursor].Line
}
// File gets the filename where the current token originated.
func (d *Dispenser) File() string {
if d.cursor < 0 || d.cursor >= len(d.tokens) {
return ""
}
return d.tokens[d.cursor].File
}
// Args is a convenience function that loads the next arguments
// (tokens on the same line) into an arbitrary number of strings
// pointed to in targets. If there are not enough argument tokens
// available to fill targets, false is returned and the remaining
// targets are left unchanged. If all the targets are filled,
// then true is returned.
func (d *Dispenser) Args(targets ...*string) bool {
for i := 0; i < len(targets); i++ {
if !d.NextArg() {
return false
}
*targets[i] = d.Val()
}
return true
}
// AllArgs is like Args, but if there are more argument tokens
// available than there are targets, false is returned. The
// number of available argument tokens must match the number of
// targets exactly to return true.
func (d *Dispenser) AllArgs(targets ...*string) bool {
if !d.Args(targets...) {
return false
}
if d.NextArg() {
d.Prev()
return false
}
return true
}
// CountRemainingArgs counts the amount of remaining arguments
// (tokens on the same line) without consuming the tokens.
func (d *Dispenser) CountRemainingArgs() int {
count := 0
for d.NextArg() {
count++
}
for i := 0; i < count; i++ {
d.Prev()
}
return count
}
// RemainingArgs loads any more arguments (tokens on the same line)
// into a slice and returns them. Open curly brace tokens also indicate
// the end of arguments, and the curly brace is not included in
// the return value nor is it loaded.
func (d *Dispenser) RemainingArgs() []string {
var args []string
for d.NextArg() {
args = append(args, d.Val())
}
return args
}
// RemainingArgsRaw loads any more arguments (tokens on the same line,
// retaining quotes) into a slice and returns them. Open curly brace
// tokens also indicate the end of arguments, and the curly brace is
// not included in the return value nor is it loaded.
func (d *Dispenser) RemainingArgsRaw() []string {
var args []string
for d.NextArg() {
args = append(args, d.ValRaw())
}
return args
}
// NewFromNextSegment returns a new dispenser with a copy of
// the tokens from the current token until the end of the
// "directive" whether that be to the end of the line or
// the end of a block that starts at the end of the line;
// in other words, until the end of the segment.
func (d *Dispenser) NewFromNextSegment() *Dispenser {
return NewDispenser(d.NextSegment())
}
// NextSegment returns a copy of the tokens from the current
// token until the end of the line or block that starts at
// the end of the line.
func (d *Dispenser) NextSegment() Segment {
tkns := Segment{d.Token()}
for d.NextArg() {
tkns = append(tkns, d.Token())
}
var openedBlock bool
for nesting := d.Nesting(); d.NextBlock(nesting); {
if !openedBlock {
// because NextBlock() consumes the initial open
// curly brace, we rewind here to append it, since
// our case is special in that we want the new
// dispenser to have all the tokens including
// surrounding curly braces
d.Prev()
tkns = append(tkns, d.Token())
d.Next()
openedBlock = true
}
tkns = append(tkns, d.Token())
}
if openedBlock {
// include closing brace
tkns = append(tkns, d.Token())
// do not consume the closing curly brace; the
// next iteration of the enclosing loop will
// call Next() and consume it
}
return tkns
}
// Token returns the current token.
func (d *Dispenser) Token() Token {
if d.cursor < 0 || d.cursor >= len(d.tokens) {
return Token{}
}
return d.tokens[d.cursor]
}
// Reset sets d's cursor to the beginning, as
// if this was a new and unused dispenser.
func (d *Dispenser) Reset() {
d.cursor = -1
d.nesting = 0
}
// ArgErr returns an argument error, meaning that another
// argument was expected but not found. In other words,
// a line break or open curly brace was encountered instead of
// an argument.
func (d *Dispenser) ArgErr() error {
if d.Val() == "{" {
return d.Err("unexpected token '{', expecting argument")
}
return d.Errf("wrong argument count or unexpected line ending after '%s'", d.Val())
}
// SyntaxErr creates a generic syntax error which explains what was
// found and what was expected.
func (d *Dispenser) SyntaxErr(expected string) error {
msg := fmt.Sprintf("syntax error: unexpected token '%s', expecting '%s', at %s:%d import chain: ['%s']", d.Val(), expected, d.File(), d.Line(), strings.Join(d.Token().imports, "','"))
return errors.New(msg)
}
// EOFErr returns an error indicating that the dispenser reached
// the end of the input when searching for the next token.
func (d *Dispenser) EOFErr() error {
return d.Errf("unexpected EOF")
}
// Err generates a custom parse-time error with a message of msg.
func (d *Dispenser) Err(msg string) error {
return d.Errf(msg)
}
// Errf is like Err, but for formatted error messages
func (d *Dispenser) Errf(format string, args ...any) error {
return d.WrapErr(fmt.Errorf(format, args...))
}
// WrapErr takes an existing error and adds the Caddyfile file and line number.
func (d *Dispenser) WrapErr(err error) error {
if len(d.Token().imports) > 0 {
return fmt.Errorf("%w, at %s:%d import chain ['%s']", err, d.File(), d.Line(), strings.Join(d.Token().imports, "','"))
}
return fmt.Errorf("%w, at %s:%d", err, d.File(), d.Line())
}
// Delete deletes the current token and returns the updated slice
// of tokens. The cursor is not advanced to the next token.
// Because deletion modifies the underlying slice, this method
// should only be called if you have access to the original slice
// of tokens and/or are using the slice of tokens outside this
// Dispenser instance. If you do not re-assign the slice with the
// return value of this method, inconsistencies in the token
// array will become apparent (or worse, hide from you like they
// did me for 3 and a half freaking hours late one night).
func (d *Dispenser) Delete() []Token {
if d.cursor >= 0 && d.cursor <= len(d.tokens)-1 {
d.tokens = append(d.tokens[:d.cursor], d.tokens[d.cursor+1:]...)
d.cursor--
}
return d.tokens
}
// DeleteN is the same as Delete, but can delete many tokens at once.
// If there aren't N tokens available to delete, none are deleted.
func (d *Dispenser) DeleteN(amount int) []Token {
if amount > 0 && d.cursor >= (amount-1) && d.cursor <= len(d.tokens)-1 {
d.tokens = append(d.tokens[:d.cursor-(amount-1)], d.tokens[d.cursor+1:]...)
d.cursor -= amount
}
return d.tokens
}
// SetContext sets a key-value pair in the context map.
func (d *Dispenser) SetContext(key string, value any) {
if d.context == nil {
d.context = make(map[string]any)
}
d.context[key] = value
}
// GetContext gets the value of a key in the context map.
func (d *Dispenser) GetContext(key string) any {
if d.context == nil {
return nil
}
return d.context[key]
}
// GetContextString gets the value of a key in the context map
// as a string, or an empty string if the key does not exist.
func (d *Dispenser) GetContextString(key string) string {
if d.context == nil {
return ""
}
if val, ok := d.context[key].(string); ok {
return val
}
return ""
}
// isNewLine determines whether the current token is on a different
// line (higher line number) than the previous token. It handles imported
// tokens correctly. If there isn't a previous token, it returns true.
func (d *Dispenser) isNewLine() bool {
if d.cursor < 1 {
return true
}
if d.cursor > len(d.tokens)-1 {
return false
}
prev := d.tokens[d.cursor-1]
curr := d.tokens[d.cursor]
return isNextOnNewLine(prev, curr)
}
// isNextOnNewLine determines whether the current token is on a different
// line (higher line number) than the next token. It handles imported
// tokens correctly. If there isn't a next token, it returns true.
func (d *Dispenser) isNextOnNewLine() bool {
if d.cursor < 0 {
return false
}
if d.cursor >= len(d.tokens)-1 {
return true
}
curr := d.tokens[d.cursor]
next := d.tokens[d.cursor+1]
return isNextOnNewLine(curr, next)
}
const MatcherNameCtxKey = "matcher_name"
-298
View File
@@ -1,298 +0,0 @@
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package caddyfile
import (
"bytes"
"io"
"slices"
"unicode"
)
// Format formats the input Caddyfile to a standard, nice-looking
// appearance. It works by reading each rune of the input and taking
// control over all the bracing and whitespace that is written; otherwise,
// words, comments, placeholders, and escaped characters are all treated
// literally and written as they appear in the input.
func Format(input []byte) []byte {
input = bytes.TrimSpace(input)
out := new(bytes.Buffer)
rdr := bytes.NewReader(input)
type heredocState int
const (
heredocClosed heredocState = 0
heredocOpening heredocState = 1
heredocOpened heredocState = 2
)
var (
last rune // the last character that was written to the result
space = true // whether current/previous character was whitespace (beginning of input counts as space)
beginningOfLine = true // whether we are at beginning of line
openBrace bool // whether current word/token is or started with open curly brace
openBraceWritten bool // if openBrace, whether that brace was written or not
openBraceSpace bool // whether there was a non-newline space before open brace
newLines int // count of newlines consumed
comment bool // whether we're in a comment
quoted bool // whether we're in a quoted segment
escaped bool // whether current char is escaped
heredoc heredocState // whether we're in a heredoc
heredocEscaped bool // whether heredoc is escaped
heredocMarker []rune
heredocClosingMarker []rune
nesting int // indentation level
)
write := func(ch rune) {
out.WriteRune(ch)
last = ch
}
indent := func() {
for tabs := nesting; tabs > 0; tabs-- {
write('\t')
}
}
nextLine := func() {
write('\n')
beginningOfLine = true
}
for {
ch, _, err := rdr.ReadRune()
if err != nil {
if err == io.EOF {
break
}
panic(err)
}
// detect whether we have the start of a heredoc
if !quoted && !(heredoc != heredocClosed || heredocEscaped) &&
space && last == '<' && ch == '<' {
write(ch)
heredoc = heredocOpening
space = false
continue
}
if heredoc == heredocOpening {
if ch == '\n' {
if len(heredocMarker) > 0 && heredocMarkerRegexp.MatchString(string(heredocMarker)) {
heredoc = heredocOpened
} else {
heredocMarker = nil
heredoc = heredocClosed
nextLine()
continue
}
write(ch)
continue
}
if unicode.IsSpace(ch) {
// a space means it's just a regular token and not a heredoc
heredocMarker = nil
heredoc = heredocClosed
} else {
heredocMarker = append(heredocMarker, ch)
write(ch)
continue
}
}
// if we're in a heredoc, all characters are read&write as-is
if heredoc == heredocOpened {
heredocClosingMarker = append(heredocClosingMarker, ch)
if len(heredocClosingMarker) > len(heredocMarker)+1 { // We assert that the heredocClosingMarker is followed by a unicode.Space
heredocClosingMarker = heredocClosingMarker[1:]
}
// check if we're done
if unicode.IsSpace(ch) && slices.Equal(heredocClosingMarker[:len(heredocClosingMarker)-1], heredocMarker) {
heredocMarker = nil
heredocClosingMarker = nil
heredoc = heredocClosed
} else {
write(ch)
if ch == '\n' {
heredocClosingMarker = heredocClosingMarker[:0]
}
continue
}
}
if last == '<' && space {
space = false
}
if comment {
if ch == '\n' {
comment = false
space = true
nextLine()
continue
} else {
write(ch)
continue
}
}
if !escaped && ch == '\\' {
if space {
write(' ')
space = false
}
write(ch)
escaped = true
continue
}
if escaped {
if ch == '<' {
heredocEscaped = true
}
write(ch)
escaped = false
continue
}
if quoted {
if ch == '"' {
quoted = false
}
write(ch)
continue
}
if space && ch == '"' {
quoted = true
}
if unicode.IsSpace(ch) {
space = true
heredocEscaped = false
if ch == '\n' {
newLines++
}
continue
}
spacePrior := space
space = false
//////////////////////////////////////////////////////////
// I find it helpful to think of the formatting loop in two
// main sections; by the time we reach this point, we
// know we are in a "regular" part of the file: we know
// the character is not a space, not in a literal segment
// like a comment or quoted, it's not escaped, etc.
//////////////////////////////////////////////////////////
if ch == '#' {
comment = true
}
if openBrace && spacePrior && !openBraceWritten {
if nesting == 0 && last == '}' {
nextLine()
nextLine()
}
openBrace = false
if beginningOfLine {
indent()
} else if !openBraceSpace {
write(' ')
}
write('{')
openBraceWritten = true
nextLine()
newLines = 0
// prevent infinite nesting from ridiculous inputs (issue #4169)
if nesting < 10 {
nesting++
}
}
switch {
case ch == '{':
openBrace = true
openBraceWritten = false
openBraceSpace = spacePrior && !beginningOfLine
if openBraceSpace {
write(' ')
}
continue
case ch == '}' && (spacePrior || !openBrace):
if last != '\n' {
nextLine()
}
if nesting > 0 {
nesting--
}
indent()
write('}')
newLines = 0
continue
}
if newLines > 2 {
newLines = 2
}
for i := 0; i < newLines; i++ {
nextLine()
}
newLines = 0
if beginningOfLine {
indent()
}
if nesting == 0 && last == '}' && beginningOfLine {
nextLine()
nextLine()
}
if !beginningOfLine && spacePrior {
write(' ')
}
if openBrace && !openBraceWritten {
write('{')
openBraceWritten = true
}
if spacePrior && ch == '<' {
space = true
}
write(ch)
beginningOfLine = false
}
// the Caddyfile does not need any leading or trailing spaces, but...
trimmedResult := bytes.TrimSpace(out.Bytes())
// ...Caddyfiles should, however, end with a newline because
// newlines are significant to the syntax of the file
return append(trimmedResult, '\n')
}
-27
View File
@@ -1,27 +0,0 @@
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build gofuzz
package caddyfile
import "bytes"
func FuzzFormat(input []byte) int {
formatted := Format(input)
if bytes.Equal(formatted, Format(formatted)) {
return 1
}
return 0
}
-451
View File
@@ -1,451 +0,0 @@
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package caddyfile
import (
"strings"
"testing"
)
func TestFormatter(t *testing.T) {
for i, tc := range []struct {
description string
input string
expect string
}{
{
description: "very simple",
input: `abc def
g hi jkl
mn`,
expect: `abc def
g hi jkl
mn`,
},
{
description: "basic indentation, line breaks, and nesting",
input: ` a
b
c {
d
}
e { f
}
g {
h {
i
}
}
j { k {
l
}
}
m {
n { o
}
p { q r
s }
}
{
{ t
u
v
w
}
}`,
expect: `a
b
c {
d
}
e {
f
}
g {
h {
i
}
}
j {
k {
l
}
}
m {
n {
o
}
p {
q r
s
}
}
{
{
t
u
v
w
}
}`,
},
{
description: "block spacing",
input: `a{
b
}
c{ d
}`,
expect: `a {
b
}
c {
d
}`,
},
{
description: "advanced spacing",
input: `abc {
def
}ghi{
jkl mno
pqr}`,
expect: `abc {
def
}
ghi {
jkl mno
pqr
}`,
},
{
description: "env var placeholders",
input: `{$A}
b {
{$C}
}
d { {$E}
}
{ {$F}
}
`,
expect: `{$A}
b {
{$C}
}
d {
{$E}
}
{
{$F}
}`,
},
{
description: "env var placeholders with port",
input: `:{$PORT}`,
expect: `:{$PORT}`,
},
{
description: "comments",
input: `#a "\n"
#b {
c
}
d {
e#f
# g
}
h { # i
}`,
expect: `#a "\n"
#b {
c
}
d {
e#f
# g
}
h {
# i
}`,
},
{
description: "quotes and escaping",
input: `"a \"b\" "#c
d
e {
"f"
}
g { "h"
}
i {
"foo
bar"
}
j {
"\"k\" l m"
}`,
expect: `"a \"b\" "#c
d
e {
"f"
}
g {
"h"
}
i {
"foo
bar"
}
j {
"\"k\" l m"
}`,
},
{
description: "bad nesting (too many open)",
input: `a
{
{
}`,
expect: `a {
{
}
`,
},
{
description: "bad nesting (too many close)",
input: `a
{
{
}}}`,
expect: `a {
{
}
}
}
`,
},
{
description: "json",
input: `foo
bar "{\"key\":34}"
`,
expect: `foo
bar "{\"key\":34}"`,
},
{
description: "escaping after spaces",
input: `foo \"literal\"`,
expect: `foo \"literal\"`,
},
{
description: "simple placeholders as standalone tokens",
input: `foo {bar}`,
expect: `foo {bar}`,
},
{
description: "simple placeholders within tokens",
input: `foo{bar} foo{bar}baz`,
expect: `foo{bar} foo{bar}baz`,
},
{
description: "placeholders and malformed braces",
input: `foo{bar} foo{ bar}baz`,
expect: `foo{bar} foo {
bar
}
baz`,
},
{
description: "hash within string is not a comment",
input: `redir / /some/#/path`,
expect: `redir / /some/#/path`,
},
{
description: "brace does not fold into comment above",
input: `# comment
{
foo
}`,
expect: `# comment
{
foo
}`,
},
{
description: "matthewpi/vscode-caddyfile-support#13",
input: `{
email {$ACMEEMAIL}
#debug
}
block {
}
`,
expect: `{
email {$ACMEEMAIL}
#debug
}
block {
}
`,
},
{
description: "matthewpi/vscode-caddyfile-support#13 - bad formatting",
input: `{
email {$ACMEEMAIL}
#debug
}
block {
}
`,
expect: `{
email {$ACMEEMAIL}
#debug
}
block {
}
`,
},
{
description: "keep heredoc as-is",
input: `block {
heredoc <<HEREDOC
Here's more than one space Here's more than one space
HEREDOC
}
`,
expect: `block {
heredoc <<HEREDOC
Here's more than one space Here's more than one space
HEREDOC
}
`,
},
{
description: "Mixing heredoc with regular part",
input: `block {
heredoc <<HEREDOC
Here's more than one space Here's more than one space
HEREDOC
respond "More than one space will be eaten" 200
}
block2 {
heredoc <<HEREDOC
Here's more than one space Here's more than one space
HEREDOC
respond "More than one space will be eaten" 200
}
`,
expect: `block {
heredoc <<HEREDOC
Here's more than one space Here's more than one space
HEREDOC
respond "More than one space will be eaten" 200
}
block2 {
heredoc <<HEREDOC
Here's more than one space Here's more than one space
HEREDOC
respond "More than one space will be eaten" 200
}
`,
},
{
description: "Heredoc as regular token",
input: `block {
heredoc <<HEREDOC "More than one space will be eaten"
}
`,
expect: `block {
heredoc <<HEREDOC "More than one space will be eaten"
}
`,
},
{
description: "Escape heredoc",
input: `block {
heredoc \<<HEREDOC
respond "More than one space will be eaten" 200
}
`,
expect: `block {
heredoc \<<HEREDOC
respond "More than one space will be eaten" 200
}
`,
},
} {
// the formatter should output a trailing newline,
// even if the tests aren't written to expect that
if !strings.HasSuffix(tc.expect, "\n") {
tc.expect += "\n"
}
actual := Format([]byte(tc.input))
if string(actual) != tc.expect {
t.Errorf("\n[TEST %d: %s]\n====== EXPECTED ======\n%s\n====== ACTUAL ======\n%s^^^^^^^^^^^^^^^^^^^^^",
i, tc.description, string(tc.expect), string(actual))
}
}
}
-160
View File
@@ -1,160 +0,0 @@
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package caddyfile
import (
"regexp"
"strconv"
"strings"
"go.uber.org/zap"
"github.com/caddyserver/caddy/v2"
)
// parseVariadic determines if the token is a variadic placeholder,
// and if so, determines the index range (start/end) of args to use.
// Returns a boolean signaling whether a variadic placeholder was found,
// and the start and end indices.
func parseVariadic(token Token, argCount int) (bool, int, int) {
if !strings.HasPrefix(token.Text, "{args[") {
return false, 0, 0
}
if !strings.HasSuffix(token.Text, "]}") {
return false, 0, 0
}
argRange := strings.TrimSuffix(strings.TrimPrefix(token.Text, "{args["), "]}")
if argRange == "" {
caddy.Log().Named("caddyfile").Warn(
"Placeholder "+token.Text+" cannot have an empty index",
zap.String("file", token.File+":"+strconv.Itoa(token.Line)), zap.Strings("import_chain", token.imports))
return false, 0, 0
}
start, end, found := strings.Cut(argRange, ":")
// If no ":" delimiter is found, this is not a variadic.
// The replacer will pick this up.
if !found {
return false, 0, 0
}
// A valid token may contain several placeholders, and
// they may be separated by ":". It's not variadic.
// https://github.com/caddyserver/caddy/issues/5716
if strings.Contains(start, "}") || strings.Contains(end, "{") {
return false, 0, 0
}
var (
startIndex = 0
endIndex = argCount
err error
)
if start != "" {
startIndex, err = strconv.Atoi(start)
if err != nil {
caddy.Log().Named("caddyfile").Warn(
"Variadic placeholder "+token.Text+" has an invalid start index",
zap.String("file", token.File+":"+strconv.Itoa(token.Line)), zap.Strings("import_chain", token.imports))
return false, 0, 0
}
}
if end != "" {
endIndex, err = strconv.Atoi(end)
if err != nil {
caddy.Log().Named("caddyfile").Warn(
"Variadic placeholder "+token.Text+" has an invalid end index",
zap.String("file", token.File+":"+strconv.Itoa(token.Line)), zap.Strings("import_chain", token.imports))
return false, 0, 0
}
}
// bound check
if startIndex < 0 || startIndex > endIndex || endIndex > argCount {
caddy.Log().Named("caddyfile").Warn(
"Variadic placeholder "+token.Text+" indices are out of bounds, only "+strconv.Itoa(argCount)+" argument(s) exist",
zap.String("file", token.File+":"+strconv.Itoa(token.Line)), zap.Strings("import_chain", token.imports))
return false, 0, 0
}
return true, startIndex, endIndex
}
// makeArgsReplacer prepares a Replacer which can replace
// non-variadic args placeholders in imported tokens.
func makeArgsReplacer(args []string) *caddy.Replacer {
repl := caddy.NewEmptyReplacer()
repl.Map(func(key string) (any, bool) {
// TODO: Remove the deprecated {args.*} placeholder
// support at some point in the future
if matches := argsRegexpIndexDeprecated.FindStringSubmatch(key); len(matches) > 0 {
// What's matched may be a substring of the key
if matches[0] != key {
return nil, false
}
value, err := strconv.Atoi(matches[1])
if err != nil {
caddy.Log().Named("caddyfile").Warn(
"Placeholder {args." + matches[1] + "} has an invalid index")
return nil, false
}
if value >= len(args) {
caddy.Log().Named("caddyfile").Warn(
"Placeholder {args." + matches[1] + "} index is out of bounds, only " + strconv.Itoa(len(args)) + " argument(s) exist")
return nil, false
}
caddy.Log().Named("caddyfile").Warn(
"Placeholder {args." + matches[1] + "} deprecated, use {args[" + matches[1] + "]} instead")
return args[value], true
}
// Handle args[*] form
if matches := argsRegexpIndex.FindStringSubmatch(key); len(matches) > 0 {
// What's matched may be a substring of the key
if matches[0] != key {
return nil, false
}
if strings.Contains(matches[1], ":") {
caddy.Log().Named("caddyfile").Warn(
"Variadic placeholder {args[" + matches[1] + "]} must be a token on its own")
return nil, false
}
value, err := strconv.Atoi(matches[1])
if err != nil {
caddy.Log().Named("caddyfile").Warn(
"Placeholder {args[" + matches[1] + "]} has an invalid index")
return nil, false
}
if value >= len(args) {
caddy.Log().Named("caddyfile").Warn(
"Placeholder {args[" + matches[1] + "]} index is out of bounds, only " + strconv.Itoa(len(args)) + " argument(s) exist")
return nil, false
}
return args[value], true
}
// Not an args placeholder, ignore
return nil, false
})
return repl
}
var (
argsRegexpIndexDeprecated = regexp.MustCompile(`args\.(.+)`)
argsRegexpIndex = regexp.MustCompile(`args\[(.+)]`)
)
-130
View File
@@ -1,130 +0,0 @@
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package caddyfile
import (
"fmt"
)
type adjacency map[string][]string
type importGraph struct {
nodes map[string]struct{}
edges adjacency
}
func (i *importGraph) addNode(name string) {
if i.nodes == nil {
i.nodes = make(map[string]struct{})
}
if _, exists := i.nodes[name]; exists {
return
}
i.nodes[name] = struct{}{}
}
func (i *importGraph) addNodes(names []string) {
for _, name := range names {
i.addNode(name)
}
}
func (i *importGraph) removeNode(name string) {
delete(i.nodes, name)
}
func (i *importGraph) removeNodes(names []string) {
for _, name := range names {
i.removeNode(name)
}
}
func (i *importGraph) addEdge(from, to string) error {
if !i.exists(from) || !i.exists(to) {
return fmt.Errorf("one of the nodes does not exist")
}
if i.willCycle(to, from) {
return fmt.Errorf("a cycle of imports exists between %s and %s", from, to)
}
if i.areConnected(from, to) {
// if connected, there's nothing to do
return nil
}
if i.nodes == nil {
i.nodes = make(map[string]struct{})
}
if i.edges == nil {
i.edges = make(adjacency)
}
i.edges[from] = append(i.edges[from], to)
return nil
}
func (i *importGraph) addEdges(from string, tos []string) error {
for _, to := range tos {
err := i.addEdge(from, to)
if err != nil {
return err
}
}
return nil
}
func (i *importGraph) areConnected(from, to string) bool {
al, ok := i.edges[from]
if !ok {
return false
}
for _, v := range al {
if v == to {
return true
}
}
return false
}
func (i *importGraph) willCycle(from, to string) bool {
collector := make(map[string]bool)
var visit func(string)
visit = func(start string) {
if !collector[start] {
collector[start] = true
for _, v := range i.edges[start] {
visit(v)
}
}
}
for _, v := range i.edges[from] {
visit(v)
}
for k := range collector {
if to == k {
return true
}
}
return false
}
func (i *importGraph) exists(key string) bool {
_, exists := i.nodes[key]
return exists
}
-399
View File
@@ -1,399 +0,0 @@
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package caddyfile
import (
"bufio"
"bytes"
"fmt"
"io"
"regexp"
"strings"
"unicode"
)
type (
// lexer is a utility which can get values, token by
// token, from a Reader. A token is a word, and tokens
// are separated by whitespace. A word can be enclosed
// in quotes if it contains whitespace.
lexer struct {
reader *bufio.Reader
token Token
line int
skippedLines int
}
// Token represents a single parsable unit.
Token struct {
File string
imports []string
Line int
Text string
wasQuoted rune // enclosing quote character, if any
heredocMarker string
snippetName string
}
)
// Tokenize takes bytes as input and lexes it into
// a list of tokens that can be parsed as a Caddyfile.
// Also takes a filename to fill the token's File as
// the source of the tokens, which is important to
// determine relative paths for `import` directives.
func Tokenize(input []byte, filename string) ([]Token, error) {
l := lexer{}
if err := l.load(bytes.NewReader(input)); err != nil {
return nil, err
}
var tokens []Token
for {
found, err := l.next()
if err != nil {
return nil, err
}
if !found {
break
}
l.token.File = filename
tokens = append(tokens, l.token)
}
return tokens, nil
}
// 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
}
// next loads the next token into the lexer.
// A token is delimited by whitespace, unless
// the token starts with a quotes character (")
// in which case the token goes until the closing
// quotes (the enclosing quotes are not included).
// Inside quoted strings, quotes may be escaped
// with a preceding \ character. No other chars
// may be escaped. The rest of the line is skipped
// if a "#" character is read in. Returns true if
// a token was loaded; false otherwise.
func (l *lexer) next() (bool, error) {
var val []rune
var comment, quoted, btQuoted, inHeredoc, heredocEscaped, escaped bool
var heredocMarker string
makeToken := func(quoted rune) bool {
l.token.Text = string(val)
l.token.wasQuoted = quoted
l.token.heredocMarker = heredocMarker
return true
}
for {
// Read a character in; if err then if we had
// read some characters, make a token. If we
// reached EOF, then no more tokens to read.
// If no EOF, then we had a problem.
ch, _, err := l.reader.ReadRune()
if err != nil {
if len(val) > 0 {
if inHeredoc {
return false, fmt.Errorf("incomplete heredoc <<%s on line #%d, expected ending marker %s", heredocMarker, l.line+l.skippedLines, heredocMarker)
}
return makeToken(0), nil
}
if err == io.EOF {
return false, nil
}
return false, err
}
// detect whether we have the start of a heredoc
if !(quoted || btQuoted) && !(inHeredoc || heredocEscaped) &&
len(val) > 1 && string(val[:2]) == "<<" {
// a space means it's just a regular token and not a heredoc
if ch == ' ' {
return makeToken(0), nil
}
// skip CR, we only care about LF
if ch == '\r' {
continue
}
// after hitting a newline, we know that the heredoc marker
// is the characters after the two << and the newline.
// we reset the val because the heredoc is syntax we don't
// want to keep.
if ch == '\n' {
if len(val) == 2 {
return false, fmt.Errorf("missing opening heredoc marker on line #%d; must contain only alpha-numeric characters, dashes and underscores; got empty string", l.line)
}
// check if there's too many <
if string(val[:3]) == "<<<" {
return false, fmt.Errorf("too many '<' for heredoc on line #%d; only use two, for example <<END", l.line)
}
heredocMarker = string(val[2:])
if !heredocMarkerRegexp.Match([]byte(heredocMarker)) {
return false, fmt.Errorf("heredoc marker on line #%d must contain only alpha-numeric characters, dashes and underscores; got '%s'", l.line, heredocMarker)
}
inHeredoc = true
l.skippedLines++
val = nil
continue
}
val = append(val, ch)
continue
}
// if we're in a heredoc, all characters are read as-is
if inHeredoc {
val = append(val, ch)
if ch == '\n' {
l.skippedLines++
}
// check if we're done, i.e. that the last few characters are the marker
if len(val) >= len(heredocMarker) && heredocMarker == string(val[len(val)-len(heredocMarker):]) {
// set the final value
val, err = l.finalizeHeredoc(val, heredocMarker)
if err != nil {
return false, err
}
// set the line counter, and make the token
l.line += l.skippedLines
l.skippedLines = 0
return makeToken('<'), nil
}
// stay in the heredoc until we find the ending marker
continue
}
// track whether we found an escape '\' for the next
// iteration to be contextually aware
if !escaped && !btQuoted && ch == '\\' {
escaped = true
continue
}
if quoted || btQuoted {
if quoted && escaped {
// all is literal in quoted area,
// so only escape quotes
if ch != '"' {
val = append(val, '\\')
}
escaped = false
} else {
if (quoted && ch == '"') || (btQuoted && ch == '`') {
return makeToken(ch), nil
}
}
// allow quoted text to wrap continue on multiple lines
if ch == '\n' {
l.line += 1 + l.skippedLines
l.skippedLines = 0
}
// collect this character as part of the quoted token
val = append(val, ch)
continue
}
if unicode.IsSpace(ch) {
// ignore CR altogether, we only actually care about LF (\n)
if ch == '\r' {
continue
}
// end of the line
if ch == '\n' {
// newlines can be escaped to chain arguments
// onto multiple lines; else, increment the line count
if escaped {
l.skippedLines++
escaped = false
} else {
l.line += 1 + l.skippedLines
l.skippedLines = 0
}
// comments (#) are single-line only
comment = false
}
// any kind of space means we're at the end of this token
if len(val) > 0 {
return makeToken(0), nil
}
continue
}
// comments must be at the start of a token,
// in other words, preceded by space or newline
if ch == '#' && len(val) == 0 {
comment = true
}
if comment {
continue
}
if len(val) == 0 {
l.token = Token{Line: l.line}
if ch == '"' {
quoted = true
continue
}
if ch == '`' {
btQuoted = true
continue
}
}
if escaped {
// allow escaping the first < to skip the heredoc syntax
if ch == '<' {
heredocEscaped = true
} else {
val = append(val, '\\')
}
escaped = false
}
val = append(val, ch)
}
}
// finalizeHeredoc takes the runes read as the heredoc text and the marker,
// and processes the text to strip leading whitespace, returning the final
// value without the leading whitespace.
func (l *lexer) finalizeHeredoc(val []rune, marker string) ([]rune, error) {
stringVal := string(val)
// find the last newline of the heredoc, which is where the contents end
lastNewline := strings.LastIndex(stringVal, "\n")
// collapse the content, then split into separate lines
lines := strings.Split(stringVal[:lastNewline+1], "\n")
// figure out how much whitespace we need to strip from the front of every line
// by getting the string that precedes the marker, on the last line
paddingToStrip := stringVal[lastNewline+1 : len(stringVal)-len(marker)]
// iterate over each line and strip the whitespace from the front
var out string
for lineNum, lineText := range lines[:len(lines)-1] {
if lineText == "" || lineText == "\r" {
out += "\n"
continue
}
// find an exact match for the padding
index := strings.Index(lineText, paddingToStrip)
// if the padding doesn't match exactly at the start then we can't safely strip
if index != 0 {
return nil, fmt.Errorf("mismatched leading whitespace in heredoc <<%s on line #%d [%s], expected whitespace [%s] to match the closing marker", marker, l.line+lineNum+1, lineText, paddingToStrip)
}
// strip, then append the line, with the newline, to the output.
// also removes all "\r" because Windows.
out += strings.ReplaceAll(lineText[len(paddingToStrip):]+"\n", "\r", "")
}
// Remove the trailing newline from the loop
if len(out) > 0 && out[len(out)-1] == '\n' {
out = out[:len(out)-1]
}
// return the final value
return []rune(out), nil
}
// Quoted returns true if the token was enclosed in quotes
// (i.e. double quotes, backticks, or heredoc).
func (t Token) Quoted() bool {
return t.wasQuoted > 0
}
// NumLineBreaks counts how many line breaks are in the token text.
func (t Token) NumLineBreaks() int {
lineBreaks := strings.Count(t.Text, "\n")
if t.wasQuoted == '<' {
// heredocs have an extra linebreak because the opening
// delimiter is on its own line and is not included in the
// token Text itself, and the trailing newline is removed.
lineBreaks += 2
}
return lineBreaks
}
// Clone returns a deep copy of the token.
func (t Token) Clone() Token {
return Token{
File: t.File,
imports: append([]string{}, t.imports...),
Line: t.Line,
Text: t.Text,
wasQuoted: t.wasQuoted,
heredocMarker: t.heredocMarker,
snippetName: t.snippetName,
}
}
var heredocMarkerRegexp = regexp.MustCompile("^[A-Za-z0-9_-]+$")
// isNextOnNewLine tests whether t2 is on a different line from t1
func isNextOnNewLine(t1, t2 Token) bool {
// If the second token is from a different file,
// we can assume it's from a different line
if t1.File != t2.File {
return true
}
// If the second token is from a different import chain,
// we can assume it's from a different line
if len(t1.imports) != len(t2.imports) {
return true
}
for i, im := range t1.imports {
if im != t2.imports[i] {
return true
}
}
// If the first token (incl line breaks) ends
// on a line earlier than the next token,
// then the second token is on a new line
return t1.Line+t1.NumLineBreaks() < t2.Line
}
-28
View File
@@ -1,28 +0,0 @@
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build gofuzz
package caddyfile
func FuzzTokenize(input []byte) int {
tokens, err := Tokenize(input, "Caddyfile")
if err != nil {
return 0
}
if len(tokens) == 0 {
return -1
}
return 1
}
-541
View File
@@ -1,541 +0,0 @@
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package caddyfile
import (
"testing"
)
func TestLexer(t *testing.T) {
testCases := []struct {
input []byte
expected []Token
expectErr bool
errorMessage string
}{
{
input: []byte(`host:123`),
expected: []Token{
{Line: 1, Text: "host:123"},
},
},
{
input: []byte(`host:123
directive`),
expected: []Token{
{Line: 1, Text: "host:123"},
{Line: 3, Text: "directive"},
},
},
{
input: []byte(`host:123 {
directive
}`),
expected: []Token{
{Line: 1, Text: "host:123"},
{Line: 1, Text: "{"},
{Line: 2, Text: "directive"},
{Line: 3, Text: "}"},
},
},
{
input: []byte(`host:123 { directive }`),
expected: []Token{
{Line: 1, Text: "host:123"},
{Line: 1, Text: "{"},
{Line: 1, Text: "directive"},
{Line: 1, Text: "}"},
},
},
{
input: []byte(`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: []byte(`host:123 {
# hash inside string is not a comment
redir / /some/#/path
}`),
expected: []Token{
{Line: 1, Text: "host:123"},
{Line: 1, Text: "{"},
{Line: 3, Text: "redir"},
{Line: 3, Text: "/"},
{Line: 3, Text: "/some/#/path"},
{Line: 4, Text: "}"},
},
},
{
input: []byte("# comment at beginning of file\n# comment at beginning of line\nhost:123"),
expected: []Token{
{Line: 3, Text: "host:123"},
},
},
{
input: []byte(`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: []byte(`A "quoted \"value\" inside" B`),
expected: []Token{
{Line: 1, Text: "A"},
{Line: 1, Text: `quoted "value" inside`},
{Line: 1, Text: "B"},
},
},
{
input: []byte("An escaped \"newline\\\ninside\" quotes"),
expected: []Token{
{Line: 1, Text: "An"},
{Line: 1, Text: "escaped"},
{Line: 1, Text: "newline\\\ninside"},
{Line: 2, Text: "quotes"},
},
},
{
input: []byte("An escaped newline\\\noutside quotes"),
expected: []Token{
{Line: 1, Text: "An"},
{Line: 1, Text: "escaped"},
{Line: 1, Text: "newline"},
{Line: 1, Text: "outside"},
{Line: 1, Text: "quotes"},
},
},
{
input: []byte("line1\\\nescaped\nline2\nline3"),
expected: []Token{
{Line: 1, Text: "line1"},
{Line: 1, Text: "escaped"},
{Line: 3, Text: "line2"},
{Line: 4, Text: "line3"},
},
},
{
input: []byte("line1\\\nescaped1\\\nescaped2\nline4\nline5"),
expected: []Token{
{Line: 1, Text: "line1"},
{Line: 1, Text: "escaped1"},
{Line: 1, Text: "escaped2"},
{Line: 4, Text: "line4"},
{Line: 5, Text: "line5"},
},
},
{
input: []byte(`"unescapable\ in quotes"`),
expected: []Token{
{Line: 1, Text: `unescapable\ in quotes`},
},
},
{
input: []byte(`"don't\escape"`),
expected: []Token{
{Line: 1, Text: `don't\escape`},
},
},
{
input: []byte(`"don't\\escape"`),
expected: []Token{
{Line: 1, Text: `don't\\escape`},
},
},
{
input: []byte(`un\escapable`),
expected: []Token{
{Line: 1, Text: `un\escapable`},
},
},
{
input: []byte(`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: []byte(`"C:\php\php-cgi.exe"`),
expected: []Token{
{Line: 1, Text: `C:\php\php-cgi.exe`},
},
},
{
input: []byte(`empty "" string`),
expected: []Token{
{Line: 1, Text: `empty`},
{Line: 1, Text: ``},
{Line: 1, Text: `string`},
},
},
{
input: []byte("skip those\r\nCR characters"),
expected: []Token{
{Line: 1, Text: "skip"},
{Line: 1, Text: "those"},
{Line: 2, Text: "CR"},
{Line: 2, Text: "characters"},
},
},
{
input: []byte("\xEF\xBB\xBF:8080"), // test with leading byte order mark
expected: []Token{
{Line: 1, Text: ":8080"},
},
},
{
input: []byte("simple `backtick quoted` string"),
expected: []Token{
{Line: 1, Text: `simple`},
{Line: 1, Text: `backtick quoted`},
{Line: 1, Text: `string`},
},
},
{
input: []byte("multiline `backtick\nquoted\n` string"),
expected: []Token{
{Line: 1, Text: `multiline`},
{Line: 1, Text: "backtick\nquoted\n"},
{Line: 3, Text: `string`},
},
},
{
input: []byte("nested `\"quotes inside\" backticks` string"),
expected: []Token{
{Line: 1, Text: `nested`},
{Line: 1, Text: `"quotes inside" backticks`},
{Line: 1, Text: `string`},
},
},
{
input: []byte("reverse-nested \"`backticks` inside\" quotes"),
expected: []Token{
{Line: 1, Text: `reverse-nested`},
{Line: 1, Text: "`backticks` inside"},
{Line: 1, Text: `quotes`},
},
},
{
input: []byte(`heredoc <<EOF
content
EOF same-line-arg
`),
expected: []Token{
{Line: 1, Text: `heredoc`},
{Line: 1, Text: "content"},
{Line: 3, Text: `same-line-arg`},
},
},
{
input: []byte(`heredoc <<VERY-LONG-MARKER
content
VERY-LONG-MARKER same-line-arg
`),
expected: []Token{
{Line: 1, Text: `heredoc`},
{Line: 1, Text: "content"},
{Line: 3, Text: `same-line-arg`},
},
},
{
input: []byte(`heredoc <<EOF
extra-newline
EOF same-line-arg
`),
expected: []Token{
{Line: 1, Text: `heredoc`},
{Line: 1, Text: "extra-newline\n"},
{Line: 4, Text: `same-line-arg`},
},
},
{
input: []byte(`heredoc <<EOF
EOF
HERE same-line-arg
`),
expected: []Token{
{Line: 1, Text: `heredoc`},
{Line: 1, Text: ``},
{Line: 3, Text: `HERE`},
{Line: 3, Text: `same-line-arg`},
},
},
{
input: []byte(`heredoc <<EOF
EOF same-line-arg
`),
expected: []Token{
{Line: 1, Text: `heredoc`},
{Line: 1, Text: ""},
{Line: 2, Text: `same-line-arg`},
},
},
{
input: []byte(`heredoc <<EOF
content
EOF same-line-arg
`),
expected: []Token{
{Line: 1, Text: `heredoc`},
{Line: 1, Text: "content"},
{Line: 3, Text: `same-line-arg`},
},
},
{
input: []byte(`prev-line
heredoc <<EOF
multi
line
content
EOF same-line-arg
next-line
`),
expected: []Token{
{Line: 1, Text: `prev-line`},
{Line: 2, Text: `heredoc`},
{Line: 2, Text: "\tmulti\n\tline\n\tcontent"},
{Line: 6, Text: `same-line-arg`},
{Line: 7, Text: `next-line`},
},
},
{
input: []byte(`escaped-heredoc \<< >>`),
expected: []Token{
{Line: 1, Text: `escaped-heredoc`},
{Line: 1, Text: `<<`},
{Line: 1, Text: `>>`},
},
},
{
input: []byte(`not-a-heredoc <EOF
content
`),
expected: []Token{
{Line: 1, Text: `not-a-heredoc`},
{Line: 1, Text: `<EOF`},
{Line: 2, Text: `content`},
},
},
{
input: []byte(`not-a-heredoc <<<EOF content`),
expected: []Token{
{Line: 1, Text: `not-a-heredoc`},
{Line: 1, Text: `<<<EOF`},
{Line: 1, Text: `content`},
},
},
{
input: []byte(`not-a-heredoc "<<" ">>"`),
expected: []Token{
{Line: 1, Text: `not-a-heredoc`},
{Line: 1, Text: `<<`},
{Line: 1, Text: `>>`},
},
},
{
input: []byte(`not-a-heredoc << >>`),
expected: []Token{
{Line: 1, Text: `not-a-heredoc`},
{Line: 1, Text: `<<`},
{Line: 1, Text: `>>`},
},
},
{
input: []byte(`not-a-heredoc <<HERE SAME LINE
content
HERE same-line-arg
`),
expected: []Token{
{Line: 1, Text: `not-a-heredoc`},
{Line: 1, Text: `<<HERE`},
{Line: 1, Text: `SAME`},
{Line: 1, Text: `LINE`},
{Line: 2, Text: `content`},
{Line: 3, Text: `HERE`},
{Line: 3, Text: `same-line-arg`},
},
},
{
input: []byte(`heredoc <<s
s
`),
expected: []Token{
{Line: 1, Text: `heredoc`},
{Line: 1, Text: ""},
},
},
{
input: []byte("\u000Aheredoc \u003C\u003C\u0073\u0073\u000A\u00BF\u0057\u0001\u0000\u00FF\u00FF\u00FF\u00FF\u00FF\u00FF\u00FF\u003D\u001F\u000A\u0073\u0073\u000A\u00BF\u0057\u0001\u0000\u00FF\u00FF\u00FF\u00FF\u00FF\u00FF\u00FF\u003D\u001F\u000A\u00BF\u00BF\u0057\u0001\u0000\u00FF\u00FF\u00FF\u00FF\u00FF\u00FF\u00FF\u003D\u001F"),
expected: []Token{
{
Line: 2,
Text: "heredoc",
},
{
Line: 2,
Text: "\u00BF\u0057\u0001\u0000\u00FF\u00FF\u00FF\u00FF\u00FF\u00FF\u00FF\u003D\u001F",
},
{
Line: 5,
Text: "\u00BF\u0057\u0001\u0000\u00FF\u00FF\u00FF\u00FF\u00FF\u00FF\u00FF\u003D\u001F",
},
{
Line: 6,
Text: "\u00BF\u00BF\u0057\u0001\u0000\u00FF\u00FF\u00FF\u00FF\u00FF\u00FF\u00FF\u003D\u001F",
},
},
},
{
input: []byte("not-a-heredoc <<\n"),
expectErr: true,
errorMessage: "missing opening heredoc marker on line #1; must contain only alpha-numeric characters, dashes and underscores; got empty string",
},
{
input: []byte(`heredoc <<<EOF
content
EOF same-line-arg
`),
expectErr: true,
errorMessage: "too many '<' for heredoc on line #1; only use two, for example <<END",
},
{
input: []byte(`heredoc <<EOF
content
`),
expectErr: true,
errorMessage: "incomplete heredoc <<EOF on line #3, expected ending marker EOF",
},
{
input: []byte(`heredoc <<EOF
content
EOF
`),
expectErr: true,
errorMessage: "mismatched leading whitespace in heredoc <<EOF on line #2 [\tcontent], expected whitespace [\t\t] to match the closing marker",
},
{
input: []byte(`heredoc <<EOF
content
EOF
`),
expectErr: true,
errorMessage: "mismatched leading whitespace in heredoc <<EOF on line #2 [ content], expected whitespace [\t\t] to match the closing marker",
},
{
input: []byte(`heredoc <<EOF
The next line is a blank line
The previous line is a blank line
EOF`),
expected: []Token{
{Line: 1, Text: "heredoc"},
{Line: 1, Text: "The next line is a blank line\n\nThe previous line is a blank line"},
},
},
{
input: []byte(`heredoc <<EOF
One tab indented heredoc with blank next line
One tab indented heredoc with blank previous line
EOF`),
expected: []Token{
{Line: 1, Text: "heredoc"},
{Line: 1, Text: "One tab indented heredoc with blank next line\n\nOne tab indented heredoc with blank previous line"},
},
},
{
input: []byte(`heredoc <<EOF
The next line is a blank line with one tab
The previous line is a blank line with one tab
EOF`),
expected: []Token{
{Line: 1, Text: "heredoc"},
{Line: 1, Text: "The next line is a blank line with one tab\n\t\nThe previous line is a blank line with one tab"},
},
},
{
input: []byte(`heredoc <<EOF
The next line is a blank line with one tab less than the correct indentation
The previous line is a blank line with one tab less than the correct indentation
EOF`),
expectErr: true,
errorMessage: "mismatched leading whitespace in heredoc <<EOF on line #3 [\t], expected whitespace [\t\t] to match the closing marker",
},
}
for i, testCase := range testCases {
actual, err := Tokenize(testCase.input, "")
if testCase.expectErr {
if err == nil {
t.Fatalf("expected error, got actual: %v", actual)
continue
}
if err.Error() != testCase.errorMessage {
t.Fatalf("expected error '%v', got: %v", testCase.errorMessage, err)
}
continue
}
if err != nil {
t.Fatalf("%v", err)
}
lexerCompare(t, i, testCase.expected, actual)
}
}
func lexerCompare(t *testing.T, n int, expected, actual []Token) {
if len(expected) != len(actual) {
t.Fatalf("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.Fatalf("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.Fatalf("Test case %d token %d: expected text '%s' but was '%s'",
n, i, expected[i].Text, actual[i].Text)
break
}
}
}
-807
View File
@@ -1,807 +0,0 @@
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package caddyfile
import (
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"go.uber.org/zap"
"github.com/caddyserver/caddy/v2"
)
// 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.
//
// Environment variables in {$ENVIRONMENT_VARIABLE} notation
// will be replaced before parsing begins.
func Parse(filename string, input []byte) ([]ServerBlock, error) {
// unfortunately, we must copy the input because parsing must
// remain a read-only operation, but we have to expand environment
// variables before we parse, which changes the underlying array (#4422)
inputCopy := make([]byte, len(input))
copy(inputCopy, input)
tokens, err := allTokens(filename, inputCopy)
if err != nil {
return nil, err
}
p := parser{
Dispenser: NewDispenser(tokens),
importGraph: importGraph{
nodes: make(map[string]struct{}),
edges: make(adjacency),
},
}
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. It may mutate input as it expands env vars.
func allTokens(filename string, input []byte) ([]Token, error) {
return Tokenize(replaceEnvVars(input), filename)
}
// replaceEnvVars replaces all occurrences of environment variables.
// It mutates the underlying array and returns the updated slice.
func replaceEnvVars(input []byte) []byte {
var offset int
for {
begin := bytes.Index(input[offset:], spanOpen)
if begin < 0 {
break
}
begin += offset // make beginning relative to input, not offset
end := bytes.Index(input[begin+len(spanOpen):], spanClose)
if end < 0 {
break
}
end += begin + len(spanOpen) // make end relative to input, not begin
// get the name; if there is no name, skip it
envString := input[begin+len(spanOpen) : end]
if len(envString) == 0 {
offset = end + len(spanClose)
continue
}
// split the string into a key and an optional default
envParts := strings.SplitN(string(envString), envVarDefaultDelimiter, 2)
// do a lookup for the env var, replace with the default if not found
envVarValue, found := os.LookupEnv(envParts[0])
if !found && len(envParts) == 2 {
envVarValue = envParts[1]
}
// get the value of the environment variable
// note that this causes one-level deep chaining
envVarBytes := []byte(envVarValue)
// splice in the value
input = append(input[:begin],
append(envVarBytes, input[end+len(spanClose):]...)...)
// continue at the end of the replacement
offset = begin + len(envVarBytes)
}
return input
}
type parser struct {
*Dispenser
block ServerBlock // current server block being parsed
eof bool // if we encounter a valid EOF in a hard place
definedSnippets map[string][]Token
nesting int
importGraph importGraph
}
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 || len(p.block.Segments) > 0 {
blocks = append(blocks, p.block)
}
if p.nesting > 0 {
return blocks, p.EOFErr()
}
}
return blocks, nil
}
func (p *parser) parseOne() error {
p.block = ServerBlock{}
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.isNamedRoute(); ok {
// we just need a dummy leading token to ease parsing later
nameToken := p.Token()
nameToken.Text = name
// named routes only have one key, the route name
p.block.Keys = []Token{nameToken}
p.block.IsNamedRoute = true
// get all the tokens from the block, including the braces
tokens, err := p.blockTokens(true)
if err != nil {
return err
}
tokens = append([]Token{nameToken}, tokens...)
p.block.Segments = []Segment{tokens}
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.blockTokens(false)
if err != nil {
return err
}
// Just as we need to track which file the token comes from, we need to
// keep track of which snippet the token comes from. This is helpful
// in tracking import cycles across files/snippets by namespacing them.
// Without this, we end up with false-positives in cycle-detection.
for k, v := range tokens {
v.snippetName = name
tokens[k] = v
}
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 {
value := p.Val()
token := p.Token()
// Reject request matchers if trying to define them globally
if strings.HasPrefix(value, "@") {
return p.Errf("request matchers may not be defined globally, they must be in a site block; found %s", value)
}
// Special case: import directive replaces tokens during parse-time
if value == "import" && p.isNewLine() {
err := p.doImport(0)
if err != nil {
return err
}
continue
}
// Open brace definitely indicates end of addresses
if value == "{" {
if expectingAnother {
return p.Errf("Expected another address but had '%s' - check for extra comma", value)
}
// Mark this server block as being defined with braces.
// This is used to provide a better error message when
// the user may have tried to define two server blocks
// without having used braces, which are required in
// that case.
p.block.HasBraces = true
break
}
// Users commonly forget to place a space between the address and the '{'
if strings.HasSuffix(value, "{") {
return p.Errf("Site addresses cannot end with a curly brace: '%s' - put a space between the token and the brace", value)
}
if value != "" { // empty token possible if user typed ""
// Trailing comma indicates another address will follow, which
// may possibly be on the next line
if value[len(value)-1] == ',' {
value = value[:len(value)-1]
expectingAnother = true
} else {
expectingAnother = false // but we may still see another one on this line
}
// If there's a comma here, it's probably because they didn't use a space
// between their two domains, e.g. "foo.com,bar.com", which would not be
// parsed as two separate site addresses.
if strings.Contains(value, ",") {
return p.Errf("Site addresses cannot contain a comma ',': '%s' - put a space after the comma to separate site addresses", value)
}
token.Text = value
p.block.Keys = append(p.block.Keys, token)
}
// 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() == "}" {
// p.nesting has already been decremented
break
}
// special case: import directive replaces tokens during parse-time
if p.Val() == "import" {
err := p.doImport(1)
if err != nil {
return err
}
p.cursor-- // cursor is advanced when we continue, so roll back one more
continue
}
// normal case: parse a directive as a new segment
// (a "segment" is a line which starts with a directive
// and which ends at the end of the line or at the end of
// the block that is opened at the end of the 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(nesting int) error {
// syntax checks
if !p.NextArg() {
return p.ArgErr()
}
importPattern := p.Val()
if importPattern == "" {
return p.Err("Import requires a non-empty filepath")
}
// grab remaining args as placeholder replacements
args := p.RemainingArgs()
// set up a replacer for non-variadic args replacement
repl := makeArgsReplacer(args)
// grab all the tokens (if it exists) from within a block that follows the import
var blockTokens []Token
for currentNesting := p.Nesting(); p.NextBlock(currentNesting); {
blockTokens = append(blockTokens, p.Token())
}
// initialize with size 1
blockMapping := make(map[string][]Token, 1)
if len(blockTokens) > 0 {
// use such tokens to create a new dispenser, and then use it to parse each block
bd := NewDispenser(blockTokens)
for bd.Next() {
// see if we can grab a key
var currentMappingKey string
if bd.Val() == "{" {
return p.Err("anonymous blocks are not supported")
}
currentMappingKey = bd.Val()
currentMappingTokens := []Token{}
// read all args until end of line / {
if bd.NextArg() {
currentMappingTokens = append(currentMappingTokens, bd.Token())
for bd.NextArg() {
currentMappingTokens = append(currentMappingTokens, bd.Token())
}
// TODO(elee1766): we don't enter another mapping here because it's annoying to extract the { and } properly.
// maybe someone can do that in the future
} else {
// attempt to enter a block and add tokens to the currentMappingTokens
for mappingNesting := bd.Nesting(); bd.NextBlock(mappingNesting); {
currentMappingTokens = append(currentMappingTokens, bd.Token())
}
}
blockMapping[currentMappingKey] = currentMappingTokens
}
}
// splice out the import directive and its arguments
// (2 tokens, plus the length of args)
tokensBefore := p.tokens[:p.cursor-1-len(args)-len(blockTokens)]
tokensAfter := p.tokens[p.cursor+1:]
var importedTokens []Token
var nodes []string
// first check snippets. That is a simple, non-recursive replacement
if p.definedSnippets != nil && p.definedSnippets[importPattern] != nil {
importedTokens = p.definedSnippets[importPattern]
if len(importedTokens) > 0 {
// just grab the first one
nodes = append(nodes, fmt.Sprintf("%s:%s", importedTokens[0].File, importedTokens[0].snippetName))
}
} 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.File(), 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, "*?[]") {
caddy.Log().Warn("No files matching import glob pattern", zap.String("pattern", importPattern))
} else {
return p.Errf("File to import not found: %s", importPattern)
}
} else {
// See issue #5295 - should skip any files that start with a . when iterating over them.
sep := string(filepath.Separator)
segGlobPattern := strings.Split(globPattern, sep)
if strings.HasPrefix(segGlobPattern[len(segGlobPattern)-1], "*") {
var tmpMatches []string
for _, m := range matches {
seg := strings.Split(m, sep)
if !strings.HasPrefix(seg[len(seg)-1], ".") {
tmpMatches = append(tmpMatches, m)
}
}
matches = tmpMatches
}
}
// collect all the imported tokens
for _, importFile := range matches {
newTokens, err := p.doSingleImport(importFile)
if err != nil {
return err
}
importedTokens = append(importedTokens, newTokens...)
}
nodes = matches
}
nodeName := p.File()
if p.Token().snippetName != "" {
nodeName += fmt.Sprintf(":%s", p.Token().snippetName)
}
p.importGraph.addNode(nodeName)
p.importGraph.addNodes(nodes)
if err := p.importGraph.addEdges(nodeName, nodes); err != nil {
p.importGraph.removeNodes(nodes)
return err
}
// copy the tokens so we don't overwrite p.definedSnippets
tokensCopy := make([]Token, 0, len(importedTokens))
var (
maybeSnippet bool
maybeSnippetId bool
index int
)
// run the argument replacer on the tokens
// golang for range slice return a copy of value
// similarly, append also copy value
for i, token := range importedTokens {
// update the token's imports to refer to import directive filename, line number and snippet name if there is one
if token.snippetName != "" {
token.imports = append(token.imports, fmt.Sprintf("%s:%d (import %s)", p.File(), p.Line(), token.snippetName))
} else {
token.imports = append(token.imports, fmt.Sprintf("%s:%d (import)", p.File(), p.Line()))
}
// naive way of determine snippets, as snippets definition can only follow name + block
// format, won't check for nesting correctness or any other error, that's what parser does.
if !maybeSnippet && nesting == 0 {
// first of the line
if i == 0 || isNextOnNewLine(tokensCopy[i-1], token) {
index = 0
} else {
index++
}
if index == 0 && len(token.Text) >= 3 && strings.HasPrefix(token.Text, "(") && strings.HasSuffix(token.Text, ")") {
maybeSnippetId = true
}
}
switch token.Text {
case "{":
nesting++
if index == 1 && maybeSnippetId && nesting == 1 {
maybeSnippet = true
maybeSnippetId = false
}
case "}":
nesting--
if nesting == 0 && maybeSnippet {
maybeSnippet = false
}
}
// if it is {block}, we substitute with all tokens in the block
// if it is {blocks.*}, we substitute with the tokens in the mapping for the *
var skip bool
var tokensToAdd []Token
switch {
case token.Text == "{block}":
tokensToAdd = blockTokens
case strings.HasPrefix(token.Text, "{blocks.") && strings.HasSuffix(token.Text, "}"):
// {blocks.foo.bar} will be extracted to key `foo.bar`
blockKey := strings.TrimPrefix(strings.TrimSuffix(token.Text, "}"), "{blocks.")
val, ok := blockMapping[blockKey]
if ok {
tokensToAdd = val
}
default:
skip = true
}
if !skip {
if len(tokensToAdd) == 0 {
// if there is no content in the snippet block, don't do any replacement
// this allows snippets which contained {block}/{block.*} before this change to continue functioning as normal
tokensCopy = append(tokensCopy, token)
} else {
tokensCopy = append(tokensCopy, tokensToAdd...)
}
continue
}
if maybeSnippet {
tokensCopy = append(tokensCopy, token)
continue
}
foundVariadic, startIndex, endIndex := parseVariadic(token, len(args))
if foundVariadic {
for _, arg := range args[startIndex:endIndex] {
token.Text = arg
tokensCopy = append(tokensCopy, token)
}
} else {
token.Text = repl.ReplaceKnown(token.Text, "")
tokensCopy = append(tokensCopy, token)
}
}
// 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(tokensCopy, tokensAfter...)...)
p.cursor -= len(args) + len(blockTokens) + 1
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)
}
input, err := io.ReadAll(file)
if err != nil {
return nil, p.Errf("Could not read imported file %s: %v", importFile, err)
}
// only warning in case of empty files
if len(input) == 0 || len(strings.TrimSpace(string(input))) == 0 {
caddy.Log().Warn("Import file is empty", zap.String("file", importFile))
return []Token{}, nil
}
importedTokens, err := allTokens(importFile, input)
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", importFile, 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 {
// a segment is a list of tokens associated with this directive
var segment Segment
// the directive itself is appended as a relevant token
segment = append(segment, p.Token())
for p.Next() {
if p.Val() == "{" {
p.nesting++
if !p.isNextOnNewLine() && p.Token().wasQuoted == 0 {
return p.Err("Unexpected next token after '{' on same line")
}
if p.isNewLine() {
return p.Err("Unexpected '{' on a new line; did you mean to place the '{' on the previous line?")
}
} else if p.Val() == "{}" {
if p.isNextOnNewLine() && p.Token().wasQuoted == 0 {
return p.Err("Unexpected '{}' at end of line")
}
} else if p.isNewLine() && p.nesting == 0 {
p.cursor-- // read too far
break
} else if p.Val() == "}" && p.nesting > 0 {
p.nesting--
} else if p.Val() == "}" && p.nesting == 0 {
return p.Err("Unexpected '}' because no matching opening brace")
} else if p.Val() == "import" && p.isNewLine() {
if err := p.doImport(1); err != nil {
return err
}
p.cursor-- // cursor is advanced when we continue, so roll back one more
continue
}
segment = append(segment, p.Token())
}
p.block.Segments = append(p.block.Segments, segment)
if p.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
}
func (p *parser) isNamedRoute() (bool, string) {
keys := p.block.Keys
// A named route block is a single key with parens, prefixed with &.
if len(keys) == 1 && strings.HasPrefix(keys[0].Text, "&(") && strings.HasSuffix(keys[0].Text, ")") {
return true, strings.TrimSuffix(keys[0].Text[2:], ")")
}
return false, ""
}
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].Text, "(") && strings.HasSuffix(keys[0].Text, ")") {
return true, strings.TrimSuffix(keys[0].Text[1:], ")")
}
return false, ""
}
// read and store everything in a block for later replay.
func (p *parser) blockTokens(retainCurlies bool) ([]Token, error) {
// block must have curlies.
err := p.openCurlyBrace()
if err != nil {
return nil, err
}
nesting := 1 // count our own nesting
tokens := []Token{}
if retainCurlies {
tokens = append(tokens, p.Token())
}
for p.Next() {
if p.Val() == "}" {
nesting--
if nesting == 0 {
if retainCurlies {
tokens = append(tokens, p.Token())
}
break
}
}
if p.Val() == "{" {
nesting++
}
tokens = append(tokens, p.tokens[p.cursor])
}
// make sure we're matched up
if nesting != 0 {
return nil, p.SyntaxErr("}")
}
return tokens, nil
}
// ServerBlock associates any number of keys from the
// head of the server block with tokens, which are
// grouped by segments.
type ServerBlock struct {
HasBraces bool
Keys []Token
Segments []Segment
IsNamedRoute bool
}
func (sb ServerBlock) GetKeysText() []string {
res := []string{}
for _, k := range sb.Keys {
res = append(res, k.Text)
}
return res
}
// DispenseDirective returns a dispenser that contains
// all the tokens in the server block.
func (sb ServerBlock) DispenseDirective(dir string) *Dispenser {
var tokens []Token
for _, seg := range sb.Segments {
if len(seg) > 0 && seg[0].Text == dir {
tokens = append(tokens, seg...)
}
}
return NewDispenser(tokens)
}
// Segment is a list of tokens which begins with a directive
// and ends at the end of the directive (either at the end of
// the line, or at the end of a block it opens).
type Segment []Token
// Directive returns the directive name for the segment.
// The directive name is the text of the first token.
func (s Segment) Directive() string {
if len(s) > 0 {
return s[0].Text
}
return ""
}
// spanOpen and spanClose are used to bound spans that
// contain the name of an environment variable.
var (
spanOpen, spanClose = []byte{'{', '$'}, []byte{'}'}
envVarDefaultDelimiter = ":"
)
-885
View File
@@ -1,885 +0,0 @@
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package caddyfile
import (
"bytes"
"os"
"path/filepath"
"testing"
)
func TestParseVariadic(t *testing.T) {
args := make([]string, 10)
for i, tc := range []struct {
input string
result bool
}{
{
input: "",
result: false,
},
{
input: "{args[1",
result: false,
},
{
input: "1]}",
result: false,
},
{
input: "{args[:]}aaaaa",
result: false,
},
{
input: "aaaaa{args[:]}",
result: false,
},
{
input: "{args.}",
result: false,
},
{
input: "{args.1}",
result: false,
},
{
input: "{args[]}",
result: false,
},
{
input: "{args[:]}",
result: true,
},
{
input: "{args[:]}",
result: true,
},
{
input: "{args[0:]}",
result: true,
},
{
input: "{args[:0]}",
result: true,
},
{
input: "{args[-1:]}",
result: false,
},
{
input: "{args[:11]}",
result: false,
},
{
input: "{args[10:0]}",
result: false,
},
{
input: "{args[0:10]}",
result: true,
},
{
input: "{args[0]}:{args[1]}:{args[2]}",
result: false,
},
} {
token := Token{
File: "test",
Line: 1,
Text: tc.input,
}
if v, _, _ := parseVariadic(token, len(args)); v != tc.result {
t.Errorf("Test %d error expectation failed Expected: %t, got %t", i, tc.result, v)
}
}
}
func TestAllTokens(t *testing.T) {
input := []byte("a b c\nd e")
expected := []string{"a", "b", "c", "d", "e"}
tokens, err := allTokens("TestAllTokens", 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
numTokens []int // number of tokens to expect in each segment
}{
{`localhost`, false, []string{
"localhost",
}, []int{}},
{`localhost
dir1`, false, []string{
"localhost",
}, []int{1}},
{
`localhost:1234
dir1 foo bar`, false, []string{
"localhost:1234",
}, []int{3},
},
{`localhost {
dir1
}`, false, []string{
"localhost",
}, []int{1}},
{`localhost:1234 {
dir1 foo bar
dir2
}`, false, []string{
"localhost:1234",
}, []int{3, 1}},
{`http://localhost https://localhost
dir1 foo bar`, false, []string{
"http://localhost",
"https://localhost",
}, []int{3}},
{`http://localhost https://localhost {
dir1 foo bar
}`, false, []string{
"http://localhost",
"https://localhost",
}, []int{3}},
{`http://localhost, https://localhost {
dir1 foo bar
}`, false, []string{
"http://localhost",
"https://localhost",
}, []int{3}},
{`http://localhost, {
}`, true, []string{
"http://localhost",
}, []int{}},
{`host1:80, http://host2.com
dir1 foo bar
dir2 baz`, false, []string{
"host1:80",
"http://host2.com",
}, []int{3, 2}},
{`http://host1.com,
http://host2.com,
https://host3.com`, false, []string{
"http://host1.com",
"http://host2.com",
"https://host3.com",
}, []int{}},
{`http://host1.com:1234, https://host2.com
dir1 foo {
bar baz
}
dir2`, false, []string{
"http://host1.com:1234",
"https://host2.com",
}, []int{6, 1}},
{`127.0.0.1
dir1 {
bar baz
}
dir2 {
foo bar
}`, false, []string{
"127.0.0.1",
}, []int{5, 5}},
{`localhost
dir1 {
foo`, true, []string{
"localhost",
}, []int{3}},
{`localhost
dir1 {
}`, false, []string{
"localhost",
}, []int{3}},
{`localhost
dir1 {
} }`, true, []string{
"localhost",
}, []int{}},
{`localhost{
dir1
}`, true, []string{}, []int{}},
{`localhost
dir1 {
nested {
foo
}
}
dir2 foo bar`, false, []string{
"localhost",
}, []int{7, 3}},
{``, false, []string{}, []int{}},
{`localhost
dir1 arg1
import testdata/import_test1.txt`, false, []string{
"localhost",
}, []int{2, 3, 1}},
{`import testdata/import_test2.txt`, false, []string{
"host1",
}, []int{1, 2}},
{`import testdata/not_found.txt`, true, []string{}, []int{}},
// empty file should just log a warning, and result in no tokens
{`import testdata/empty.txt`, false, []string{}, []int{}},
{`import testdata/only_white_space.txt`, false, []string{}, []int{}},
// import path/to/dir/* should skip any files that start with a . when iterating over them.
{`localhost
dir1 arg1
import testdata/glob/*`, false, []string{
"localhost",
}, []int{2, 3, 1}},
// import path/to/dir/.* should continue to read all dotfiles in a dir.
{`import testdata/glob/.*`, false, []string{
"host1",
}, []int{1, 2}},
{`""`, false, []string{}, []int{}},
{``, false, []string{}, []int{}},
// Unexpected next token after '{' on same line
{`localhost
dir1 { a b }`, true, []string{"localhost"}, []int{}},
// Unexpected '{' on a new line
{`localhost
dir1
{
a b
}`, true, []string{"localhost"}, []int{}},
// Workaround with quotes
{`localhost
dir1 "{" a b "}"`, false, []string{"localhost"}, []int{5}},
// Unexpected '{}' at end of line
{`localhost
dir1 {}`, true, []string{"localhost"}, []int{}},
// Workaround with quotes
{`localhost
dir1 "{}"`, false, []string{"localhost"}, []int{2}},
// import with args
{`import testdata/import_args0.txt a`, false, []string{"a"}, []int{}},
{`import testdata/import_args1.txt a b`, false, []string{"a", "b"}, []int{}},
{`import testdata/import_args*.txt a b`, false, []string{"a"}, []int{2}},
// test cases found by fuzzing!
{`import }{$"`, true, []string{}, []int{}},
{`import /*/*.txt`, true, []string{}, []int{}},
{`import /???/?*?o`, true, []string{}, []int{}},
{`import /??`, true, []string{}, []int{}},
{`import /[a-z]`, true, []string{}, []int{}},
{`import {$}`, true, []string{}, []int{}},
{`import {%}`, true, []string{}, []int{}},
{`import {$$}`, true, []string{}, []int{}},
{`import {%%}`, true, []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)
}
// t.Logf("%+v\n", result)
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.GetKeysText() {
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.Segments) != len(test.numTokens) {
t.Errorf("Test %d: Expected %d segments, had %d",
i, len(test.numTokens), len(result.Segments))
continue
}
for j, seg := range result.Segments {
if len(seg) != test.numTokens[j] {
t.Errorf("Test %d, segment %d: Expected %d tokens, counted %d",
i, j, test.numTokens[j], len(seg))
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 {
textKeys := got.GetKeysText()
if len(textKeys) != 1 || textKeys[0] != "localhost" {
t.Errorf("got keys unexpected: expect localhost, got %v", textKeys)
return false
}
if len(got.Segments) != 2 {
t.Errorf("got wrong number of segments: expect 2, got %d", len(got.Segments))
return false
}
if len(got.Segments[0]) != 1 || len(got.Segments[1]) != 2 {
t.Errorf("got unexpected tokens: %v", got.Segments)
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 = os.WriteFile(recursiveFile1, []byte(
`localhost
dir1
import recursive_import_test2`), 0o644)
if err != nil {
t.Fatal(err)
}
defer os.Remove(recursiveFile1)
err = os.WriteFile(recursiveFile2, []byte("dir2 1"), 0o644)
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 = os.WriteFile(recursiveFile1, []byte(
`localhost
dir1
import `+recursiveFile2), 0o644)
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 {
textKeys := got.GetKeysText()
if len(textKeys) != 1 || textKeys[0] != "localhost" {
t.Errorf("got keys unexpected: expect localhost, got %v", textKeys)
return false
}
if len(got.Segments) != 2 {
t.Errorf("got wrong number of segments: expect 2, got %d", len(got.Segments))
return false
}
if len(got.Segments[0]) != 1 || len(got.Segments[1]) != 8 {
t.Errorf("got unexpected tokens: %v", got.Segments)
return false
}
return true
}
directiveFile, err := filepath.Abs("testdata/directive_import_test")
if err != nil {
t.Fatal(err)
}
err = os.WriteFile(directiveFile, []byte(`prop1 1
prop2 2`), 0o644)
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
// recursive self-import
{`import testdata/import_recursive0.txt`, true, [][]string{}},
{`import testdata/import_recursive3.txt
import testdata/import_recursive1.txt`, true, [][]string{}},
// cyclic imports
{`(A) {
import A
}
:80
import A
`, true, [][]string{}},
{`(A) {
import B
}
(B) {
import A
}
:80
import A
`, true, [][]string{}},
} {
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.GetKeysText() {
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("FOOBAR", "foobar")
os.Setenv("CHAINED", "$FOOBAR")
for i, test := range []struct {
input string
expect string
}{
{
input: "",
expect: "",
},
{
input: "foo",
expect: "foo",
},
{
input: "{$NOT_SET}",
expect: "",
},
{
input: "foo{$NOT_SET}bar",
expect: "foobar",
},
{
input: "{$FOOBAR}",
expect: "foobar",
},
{
input: "foo {$FOOBAR} bar",
expect: "foo foobar bar",
},
{
input: "foo{$FOOBAR}bar",
expect: "foofoobarbar",
},
{
input: "foo\n{$FOOBAR}\nbar",
expect: "foo\nfoobar\nbar",
},
{
input: "{$FOOBAR} {$FOOBAR}",
expect: "foobar foobar",
},
{
input: "{$FOOBAR}{$FOOBAR}",
expect: "foobarfoobar",
},
{
input: "{$CHAINED}",
expect: "$FOOBAR", // should not chain env expands
},
{
input: "{$FOO:default}",
expect: "default",
},
{
input: "foo{$BAR:bar}baz",
expect: "foobarbaz",
},
{
input: "foo{$BAR:$FOOBAR}baz",
expect: "foo$FOOBARbaz", // should not chain env expands
},
{
input: "{$FOOBAR",
expect: "{$FOOBAR",
},
{
input: "{$LONGER_NAME $FOOBAR}",
expect: "",
},
{
input: "{$}",
expect: "{$}",
},
{
input: "{$$}",
expect: "",
},
{
input: "{$",
expect: "{$",
},
{
input: "}{$",
expect: "}{$",
},
} {
actual := replaceEnvVars([]byte(test.input))
if !bytes.Equal(actual, []byte(test.expect)) {
t.Errorf("Test %d: Expected: '%s' but got '%s'", i, test.expect, actual)
}
}
}
func TestImportReplacementInJSONWithBrace(t *testing.T) {
for i, test := range []struct {
args []string
input string
expect string
}{
{
args: []string{"123"},
input: "{args[0]}",
expect: "123",
},
{
args: []string{"123"},
input: `{"key":"{args[0]}"}`,
expect: `{"key":"123"}`,
},
{
args: []string{"123", "123"},
input: `{"key":[{args[0]},{args[1]}]}`,
expect: `{"key":[123,123]}`,
},
} {
repl := makeArgsReplacer(test.args)
actual := repl.ReplaceKnown(test.input, "")
if actual != test.expect {
t.Errorf("Test %d: Expected: '%s' but got '%s'", i, test.expect, actual)
}
}
}
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)
}
if len(blocks) != 1 {
t.Fatalf("Expect exactly one server block. Got %d.", len(blocks))
}
if actual, expected := blocks[0].GetKeysText()[0], "http://example.com"; expected != actual {
t.Errorf("Expected server name to be '%s' but was '%s'", expected, actual)
}
if len(blocks[0].Segments) != 2 {
t.Fatalf("Server block should have tokens from import, got: %+v", blocks[0])
}
if actual, expected := blocks[0].Segments[0][0].Text, "gzip"; expected != actual {
t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
}
if actual, expected := blocks[0].Segments[1][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 := os.CreateTemp("", 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'
basic_auth / 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)
}
auth := blocks[0].Segments[0]
line := auth[0].Text + " " + auth[1].Text + " " + auth[2].Text + " " + auth[3].Text
if line != "basic_auth / import password" {
// Previously, it would be changed to:
// basic_auth / import /path/to/test/dir/password
// referencing a file that (probably) doesn't exist and changing the
// password!
t.Errorf("Expected basic_auth tokens to be 'basic_auth / 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)
}
if len(blocks) != 1 {
t.Fatalf("Expect exactly one server block. Got %d.", len(blocks))
}
if actual, expected := blocks[0].GetKeysText()[0], "http://example.com"; expected != actual {
t.Errorf("Expected server name to be '%s' but was '%s'", expected, actual)
}
if len(blocks[0].Segments) != 1 {
t.Fatalf("Server block should have tokens from import")
}
if actual, expected := blocks[0].Segments[0][0].Text, "gzip"; expected != actual {
t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
}
}
func TestRejectsGlobalMatcher(t *testing.T) {
p := testParser(`
@rejected path /foo
(common) {
gzip foo
errors stderr
}
http://example.com {
import common
}
`)
_, err := p.parseAll()
if err == nil {
t.Fatal("Expected an error, but got nil")
}
expected := "request matchers may not be defined globally, they must be in a site block; found @rejected, at Testfile:2"
if err.Error() != expected {
t.Errorf("Expected error to be '%s' but got '%v'", expected, err)
}
}
func testParser(input string) parser {
return parser{Dispenser: NewTestDispenser(input)}
}
-4
View File
@@ -1,4 +0,0 @@
host1 {
dir1
dir2 arg1
}
-1
View File
@@ -1 +0,0 @@
{args[0]}
-1
View File
@@ -1 +0,0 @@
{args[0]} {args[1]}
-1
View File
@@ -1 +0,0 @@
import import_recursive0.txt
-1
View File
@@ -1 +0,0 @@
import import_recursive2.txt
-1
View File
@@ -1 +0,0 @@
import import_recursive3.txt
-1
View File
@@ -1 +0,0 @@
import import_recursive1.txt
-2
View File
@@ -1,2 +0,0 @@
dir2 arg1 arg2
dir3
-7
View File
@@ -1,7 +0,0 @@
 
-138
View File
@@ -1,138 +0,0 @@
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package caddyconfig
import (
"encoding/json"
"fmt"
"github.com/caddyserver/caddy/v2"
)
// Adapter is a type which can adapt a configuration to Caddy JSON.
// It returns the results and any warnings, or an error.
type Adapter interface {
Adapt(body []byte, options map[string]any) ([]byte, []Warning, error)
}
// Warning represents a warning or notice related to conversion.
type Warning struct {
File string `json:"file,omitempty"`
Line int `json:"line,omitempty"`
Directive string `json:"directive,omitempty"`
Message string `json:"message,omitempty"`
}
func (w Warning) String() string {
var directive string
if w.Directive != "" {
directive = fmt.Sprintf(" (%s)", w.Directive)
}
return fmt.Sprintf("%s:%d%s: %s", w.File, w.Line, directive, w.Message)
}
// JSON encodes val as JSON, returning it as a json.RawMessage. Any
// marshaling errors (which are highly unlikely with correct code)
// are converted to warnings. This is convenient when filling config
// structs that require a json.RawMessage, without having to worry
// about errors.
func JSON(val any, warnings *[]Warning) json.RawMessage {
b, err := json.Marshal(val)
if err != nil {
if warnings != nil {
*warnings = append(*warnings, Warning{Message: err.Error()})
}
return nil
}
return b
}
// JSONModuleObject is like JSON(), except it marshals val into a JSON object
// with an added key named fieldName with the value fieldVal. This is useful
// for encoding module values where the module name has to be described within
// the object by a certain key; for example, `"handler": "file_server"` for a
// file server HTTP handler (fieldName="handler" and fieldVal="file_server").
// The val parameter must encode into a map[string]any (i.e. it must be
// a struct or map). Any errors are converted into warnings.
func JSONModuleObject(val any, fieldName, fieldVal string, warnings *[]Warning) json.RawMessage {
// encode to a JSON object first
enc, err := json.Marshal(val)
if err != nil {
if warnings != nil {
*warnings = append(*warnings, Warning{Message: err.Error()})
}
return nil
}
// then decode the object
var tmp map[string]any
err = json.Unmarshal(enc, &tmp)
if err != nil {
if warnings != nil {
*warnings = append(*warnings, Warning{Message: err.Error()})
}
return nil
}
// so we can easily add the module's field with its appointed value
tmp[fieldName] = fieldVal
// then re-marshal as JSON
result, err := json.Marshal(tmp)
if err != nil {
if warnings != nil {
*warnings = append(*warnings, Warning{Message: err.Error()})
}
return nil
}
return result
}
// RegisterAdapter registers a config adapter with the given name.
// This should usually be done at init-time. It panics if the
// adapter cannot be registered successfully.
func RegisterAdapter(name string, adapter Adapter) {
if _, ok := configAdapters[name]; ok {
panic(fmt.Errorf("%s: already registered", name))
}
configAdapters[name] = adapter
caddy.RegisterModule(adapterModule{name, adapter})
}
// GetAdapter returns the adapter with the given name,
// or nil if one with that name is not registered.
func GetAdapter(name string) Adapter {
return configAdapters[name]
}
// adapterModule is a wrapper type that can turn any config
// adapter into a Caddy module, which has the benefit of being
// counted with other modules, even though they do not
// technically extend the Caddy configuration structure.
// See caddyserver/caddy#3132.
type adapterModule struct {
name string
Adapter
}
func (am adapterModule) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
ID: caddy.ModuleID("caddy.adapters." + am.name),
New: func() caddy.Module { return am },
}
}
var configAdapters = make(map[string]Adapter)
-425
View File
@@ -1,425 +0,0 @@
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package httpcaddyfile
import (
"fmt"
"net"
"net/netip"
"reflect"
"sort"
"strconv"
"strings"
"unicode"
"github.com/caddyserver/certmagic"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
)
// mapAddressToServerBlocks returns a map of listener address to list of server
// blocks that will be served on that address. To do this, each server block is
// expanded so that each one is considered individually, although keys of a
// server block that share the same address stay grouped together so the config
// isn't repeated unnecessarily. For example, this Caddyfile:
//
// example.com {
// bind 127.0.0.1
// }
// www.example.com, example.net/path, localhost:9999 {
// bind 127.0.0.1 1.2.3.4
// }
//
// has two server blocks to start with. But expressed in this Caddyfile are
// actually 4 listener addresses: 127.0.0.1:443, 1.2.3.4:443, 127.0.0.1:9999,
// and 127.0.0.1:9999. This is because the bind directive is applied to each
// key of its server block (specifying the host part), and each key may have
// a different port. And we definitely need to be sure that a site which is
// bound to be served on a specific interface is not served on others just
// because that is more convenient: it would be a potential security risk
// if the difference between interfaces means private vs. public.
//
// So what this function does for the example above is iterate each server
// block, and for each server block, iterate its keys. For the first, it
// finds one key (example.com) and determines its listener address
// (127.0.0.1:443 - because of 'bind' and automatic HTTPS). It then adds
// the listener address to the map value returned by this function, with
// the first server block as one of its associations.
//
// It then iterates each key on the second server block and associates them
// with one or more listener addresses. Indeed, each key in this block has
// two listener addresses because of the 'bind' directive. Once we know
// which addresses serve which keys, we can create a new server block for
// each address containing the contents of the server block and only those
// specific keys of the server block which use that address.
//
// It is possible and even likely that some keys in the returned map have
// the exact same list of server blocks (i.e. they are identical). This
// happens when multiple hosts are declared with a 'bind' directive and
// the resulting listener addresses are not shared by any other server
// block (or the other server blocks are exactly identical in their token
// contents). This happens with our example above because 1.2.3.4:443
// and 1.2.3.4:9999 are used exclusively with the second server block. This
// repetition may be undesirable, so call consolidateAddrMappings() to map
// multiple addresses to the same lists of server blocks (a many:many mapping).
// (Doing this is essentially a map-reduce technique.)
func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []serverBlock,
options map[string]any,
) (map[string][]serverBlock, error) {
sbmap := make(map[string][]serverBlock)
for i, sblock := range originalServerBlocks {
// within a server block, we need to map all the listener addresses
// implied by the server block to the keys of the server block which
// will be served by them; this has the effect of treating each
// key of a server block as its own, but without having to repeat its
// contents in cases where multiple keys really can be served together
addrToKeys := make(map[string][]caddyfile.Token)
for j, key := range sblock.block.Keys {
// a key can have multiple listener addresses if there are multiple
// arguments to the 'bind' directive (although they will all have
// the same port, since the port is defined by the key or is implicit
// through automatic HTTPS)
addrs, err := st.listenerAddrsForServerBlockKey(sblock, key.Text, options)
if err != nil {
return nil, fmt.Errorf("server block %d, key %d (%s): determining listener address: %v", i, j, key.Text, err)
}
// associate this key with each listener address it is served on
for _, addr := range addrs {
addrToKeys[addr] = append(addrToKeys[addr], key)
}
}
// make a slice of the map keys so we can iterate in sorted order
addrs := make([]string, 0, len(addrToKeys))
for k := range addrToKeys {
addrs = append(addrs, k)
}
sort.Strings(addrs)
// now that we know which addresses serve which keys of this
// server block, we iterate that mapping and create a list of
// new server blocks for each address where the keys of the
// server block are only the ones which use the address; but
// the contents (tokens) are of course the same
for _, addr := range addrs {
keys := addrToKeys[addr]
// parse keys so that we only have to do it once
parsedKeys := make([]Address, 0, len(keys))
for _, key := range keys {
addr, err := ParseAddress(key.Text)
if err != nil {
return nil, fmt.Errorf("parsing key '%s': %v", key.Text, err)
}
parsedKeys = append(parsedKeys, addr.Normalize())
}
sbmap[addr] = append(sbmap[addr], serverBlock{
block: caddyfile.ServerBlock{
Keys: keys,
Segments: sblock.block.Segments,
},
pile: sblock.pile,
keys: parsedKeys,
})
}
}
return sbmap, nil
}
// consolidateAddrMappings eliminates repetition of identical server blocks in a mapping of
// single listener addresses to lists of server blocks. Since multiple addresses may serve
// identical sites (server block contents), this function turns a 1:many mapping into a
// many:many mapping. Server block contents (tokens) must be exactly identical so that
// reflect.DeepEqual returns true in order for the addresses to be combined. Identical
// entries are deleted from the addrToServerBlocks map. Essentially, each pairing (each
// association from multiple addresses to multiple server blocks; i.e. each element of
// the returned slice) becomes a server definition in the output JSON.
func (st *ServerType) consolidateAddrMappings(addrToServerBlocks map[string][]serverBlock) []sbAddrAssociation {
sbaddrs := make([]sbAddrAssociation, 0, len(addrToServerBlocks))
for addr, sblocks := range addrToServerBlocks {
// we start with knowing that at least this address
// maps to these server blocks
a := sbAddrAssociation{
addresses: []string{addr},
serverBlocks: sblocks,
}
// now find other addresses that map to identical
// server blocks and add them to our list of
// addresses, while removing them from the map
for otherAddr, otherSblocks := range addrToServerBlocks {
if addr == otherAddr {
continue
}
if reflect.DeepEqual(sblocks, otherSblocks) {
a.addresses = append(a.addresses, otherAddr)
delete(addrToServerBlocks, otherAddr)
}
}
sort.Strings(a.addresses)
sbaddrs = append(sbaddrs, a)
}
// sort them by their first address (we know there will always be at least one)
// to avoid problems with non-deterministic ordering (makes tests flaky)
sort.Slice(sbaddrs, func(i, j int) bool {
return sbaddrs[i].addresses[0] < sbaddrs[j].addresses[0]
})
return sbaddrs
}
// listenerAddrsForServerBlockKey essentially converts the Caddyfile
// site addresses to Caddy listener addresses for each server block.
func (st *ServerType) listenerAddrsForServerBlockKey(sblock serverBlock, key string,
options map[string]any,
) ([]string, error) {
addr, err := ParseAddress(key)
if err != nil {
return nil, fmt.Errorf("parsing key: %v", err)
}
addr = addr.Normalize()
switch addr.Scheme {
case "wss":
return nil, fmt.Errorf("the scheme wss:// is only supported in browsers; use https:// instead")
case "ws":
return nil, fmt.Errorf("the scheme ws:// is only supported in browsers; use http:// instead")
case "https", "http", "":
// Do nothing or handle the valid schemes
default:
return nil, fmt.Errorf("unsupported URL scheme %s://", addr.Scheme)
}
// figure out the HTTP and HTTPS ports; either
// use defaults, or override with user config
httpPort, httpsPort := strconv.Itoa(caddyhttp.DefaultHTTPPort), strconv.Itoa(caddyhttp.DefaultHTTPSPort)
if hport, ok := options["http_port"]; ok {
httpPort = strconv.Itoa(hport.(int))
}
if hsport, ok := options["https_port"]; ok {
httpsPort = strconv.Itoa(hsport.(int))
}
// default port is the HTTPS port
lnPort := httpsPort
if addr.Port != "" {
// port explicitly defined
lnPort = addr.Port
} else if addr.Scheme == "http" {
// port inferred from scheme
lnPort = httpPort
}
// error if scheme and port combination violate convention
if (addr.Scheme == "http" && lnPort == httpsPort) || (addr.Scheme == "https" && lnPort == httpPort) {
return nil, fmt.Errorf("[%s] scheme and port violate convention", key)
}
// the bind directive specifies hosts (and potentially network), but is optional
lnHosts := make([]string, 0, len(sblock.pile["bind"]))
for _, cfgVal := range sblock.pile["bind"] {
lnHosts = append(lnHosts, cfgVal.Value.([]string)...)
}
if len(lnHosts) == 0 {
if defaultBind, ok := options["default_bind"].([]string); ok {
lnHosts = defaultBind
} else {
lnHosts = []string{""}
}
}
// use a map to prevent duplication
listeners := make(map[string]struct{})
for _, lnHost := range lnHosts {
// normally we would simply append the port,
// but if lnHost is IPv6, we need to ensure it
// is enclosed in [ ]; net.JoinHostPort does
// this for us, but lnHost might also have a
// network type in front (e.g. "tcp/") leading
// to "[tcp/::1]" which causes parsing failures
// later; what we need is "tcp/[::1]", so we have
// to split the network and host, then re-combine
network, host, ok := strings.Cut(lnHost, "/")
if !ok {
host = network
network = ""
}
host = strings.Trim(host, "[]") // IPv6
networkAddr := caddy.JoinNetworkAddress(network, host, lnPort)
addr, err := caddy.ParseNetworkAddress(networkAddr)
if err != nil {
return nil, fmt.Errorf("parsing network address: %v", err)
}
listeners[addr.String()] = struct{}{}
}
// now turn map into list
listenersList := make([]string, 0, len(listeners))
for lnStr := range listeners {
listenersList = append(listenersList, lnStr)
}
sort.Strings(listenersList)
return listenersList, nil
}
// Address represents a site address. It contains
// the original input value, and the component
// parts of an address. The component parts may be
// updated to the correct values as setup proceeds,
// but the original value should never be changed.
//
// The Host field must be in a normalized form.
type Address struct {
Original, Scheme, Host, Port, Path string
}
// ParseAddress parses an address string into a structured format with separate
// scheme, host, port, and path portions, as well as the original input string.
func ParseAddress(str string) (Address, error) {
const maxLen = 4096
if len(str) > maxLen {
str = str[:maxLen]
}
remaining := strings.TrimSpace(str)
a := Address{Original: remaining}
// extract scheme
splitScheme := strings.SplitN(remaining, "://", 2)
switch len(splitScheme) {
case 0:
return a, nil
case 1:
remaining = splitScheme[0]
case 2:
a.Scheme = splitScheme[0]
remaining = splitScheme[1]
}
// extract host and port
hostSplit := strings.SplitN(remaining, "/", 2)
if len(hostSplit) > 0 {
host, port, err := net.SplitHostPort(hostSplit[0])
if err != nil {
host, port, err = net.SplitHostPort(hostSplit[0] + ":")
if err != nil {
host = hostSplit[0]
}
}
a.Host = host
a.Port = port
}
if len(hostSplit) == 2 {
// all that remains is the path
a.Path = "/" + hostSplit[1]
}
// make sure port is valid
if a.Port != "" {
if portNum, err := strconv.Atoi(a.Port); err != nil {
return Address{}, fmt.Errorf("invalid port '%s': %v", a.Port, err)
} else if portNum < 0 || portNum > 65535 {
return Address{}, fmt.Errorf("port %d is out of range", portNum)
}
}
return a, nil
}
// String returns a human-readable form of a. It will
// be a cleaned-up and filled-out URL string.
func (a Address) String() string {
if a.Host == "" && a.Port == "" {
return ""
}
scheme := a.Scheme
if scheme == "" {
if a.Port == strconv.Itoa(certmagic.HTTPSPort) {
scheme = "https"
} else {
scheme = "http"
}
}
s := scheme
if s != "" {
s += "://"
}
if a.Port != "" &&
((scheme == "https" && a.Port != strconv.Itoa(caddyhttp.DefaultHTTPSPort)) ||
(scheme == "http" && a.Port != strconv.Itoa(caddyhttp.DefaultHTTPPort))) {
s += net.JoinHostPort(a.Host, a.Port)
} else {
s += a.Host
}
if a.Path != "" {
s += a.Path
}
return s
}
// Normalize returns a normalized version of a.
func (a Address) Normalize() Address {
path := a.Path
// ensure host is normalized if it's an IP address
host := strings.TrimSpace(a.Host)
if ip, err := netip.ParseAddr(host); err == nil {
if ip.Is6() && !ip.Is4() && !ip.Is4In6() {
host = ip.String()
}
}
return Address{
Original: a.Original,
Scheme: lowerExceptPlaceholders(a.Scheme),
Host: lowerExceptPlaceholders(host),
Port: a.Port,
Path: path,
}
}
// lowerExceptPlaceholders lowercases s except within
// placeholders (substrings in non-escaped '{ }' spans).
// See https://github.com/caddyserver/caddy/issues/3264
func lowerExceptPlaceholders(s string) string {
var sb strings.Builder
var escaped, inPlaceholder bool
for _, ch := range s {
if ch == '\\' && !escaped {
escaped = true
sb.WriteRune(ch)
continue
}
if ch == '{' && !escaped {
inPlaceholder = true
}
if ch == '}' && inPlaceholder && !escaped {
inPlaceholder = false
}
if inPlaceholder {
sb.WriteRune(ch)
} else {
sb.WriteRune(unicode.ToLower(ch))
}
escaped = false
}
return sb.String()
}
@@ -1,28 +0,0 @@
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build gofuzz
package httpcaddyfile
func FuzzParseAddress(data []byte) int {
addr, err := ParseAddress(string(data))
if err != nil {
if addr == (Address{}) {
return 1
}
return 0
}
return 1
}
-253
View File
@@ -1,253 +0,0 @@
package httpcaddyfile
import (
"testing"
)
func TestParseAddress(t *testing.T) {
for i, test := range []struct {
input string
scheme, host, port, path string
shouldErr bool
}{
{``, "", "", "", "", false},
{`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},
{`:http`, "", "", "", "", true},
{`:https`, "", "", "", "", true},
{`localhost:http`, "", "", "", "", true}, // using service name in port is verboten, as of Go 1.12.8
{`localhost:https`, "", "", "", "", true},
{`http://localhost:https`, "", "", "", "", true}, // conflict
{`http://localhost:http`, "", "", "", "", true}, // repeated scheme
{`host:https/path`, "", "", "", "", true},
{`http://localhost:443`, "http", "localhost", "443", "", false}, // NOTE: not conventional
{`https://localhost:80`, "https", "localhost", "80", "", false}, // NOTE: not conventional
{`http://localhost`, "http", "localhost", "", "", false},
{`https://localhost`, "https", "localhost", "", "", false},
{`http://{env.APP_DOMAIN}`, "http", "{env.APP_DOMAIN}", "", "", false},
{`{env.APP_DOMAIN}:80`, "", "{env.APP_DOMAIN}", "80", "", false},
{`{env.APP_DOMAIN}/path`, "", "{env.APP_DOMAIN}", "", "/path", false},
{`example.com/{env.APP_PATH}`, "", "example.com", "", "/{env.APP_PATH}", false},
{`http://127.0.0.1`, "http", "127.0.0.1", "", "", false},
{`https://127.0.0.1`, "https", "127.0.0.1", "", "", false},
{`http://[::1]`, "http", "::1", "", "", false},
{`http://localhost:1234`, "http", "localhost", "1234", "", false},
{`https://127.0.0.1:1234`, "https", "127.0.0.1", "1234", "", false},
{`http://[::1]:1234`, "http", "::1", "1234", "", false},
{``, "", "", "", "", false},
{`::1`, "", "::1", "", "", false},
{`localhost::`, "", "localhost::", "", "", false},
{`#$%@`, "", "#$%@", "", "", false}, // don't want to presume what the hostname could be
{`host/path`, "", "host", "", "/path", false},
{`http://host/`, "http", "host", "", "/", false},
{`//asdf`, "", "", "", "//asdf", false},
{`:1234/asdf`, "", "", "1234", "/asdf", false},
{`http://host/path`, "http", "host", "", "/path", false},
{`https://host:443/path/foo`, "https", "host", "443", "/path/foo", false},
{`host:80/path`, "", "host", "80", "/path", false},
{`/path`, "", "", "", "/path", false},
} {
actual, err := ParseAddress(test.input)
if err != nil && !test.shouldErr {
t.Errorf("Test %d (%s): Expected no error, but had error: %v", i, test.input, err)
}
if err == nil && test.shouldErr {
t.Errorf("Test %d (%s): Expected error, but had none (%#v)", i, test.input, actual)
}
if !test.shouldErr && actual.Original != test.input {
t.Errorf("Test %d (%s): Expected original '%s', got '%s'", i, test.input, test.input, actual.Original)
}
if actual.Scheme != test.scheme {
t.Errorf("Test %d (%s): Expected scheme '%s', got '%s'", i, test.input, test.scheme, actual.Scheme)
}
if actual.Host != test.host {
t.Errorf("Test %d (%s): Expected host '%s', got '%s'", i, test.input, test.host, actual.Host)
}
if actual.Port != test.port {
t.Errorf("Test %d (%s): Expected port '%s', got '%s'", i, test.input, test.port, actual.Port)
}
if actual.Path != test.path {
t.Errorf("Test %d (%s): Expected path '%s', got '%s'", i, test.input, test.path, actual.Path)
}
}
}
func TestAddressString(t *testing.T) {
for i, test := range []struct {
addr Address
expected string
}{
{Address{Scheme: "http", Host: "host", Port: "1234", Path: "/path"}, "http://host:1234/path"},
{Address{Scheme: "", Host: "host", Port: "", Path: ""}, "http://host"},
{Address{Scheme: "", Host: "host", Port: "80", Path: ""}, "http://host"},
{Address{Scheme: "", Host: "host", Port: "443", Path: ""}, "https://host"},
{Address{Scheme: "https", Host: "host", Port: "443", Path: ""}, "https://host"},
{Address{Scheme: "https", Host: "host", Port: "", Path: ""}, "https://host"},
{Address{Scheme: "", Host: "host", Port: "80", Path: "/path"}, "http://host/path"},
{Address{Scheme: "http", Host: "", Port: "1234", Path: ""}, "http://:1234"},
{Address{Scheme: "", Host: "", Port: "", Path: ""}, ""},
} {
actual := test.addr.String()
if actual != test.expected {
t.Errorf("Test %d: expected '%s' but got '%s'", i, test.expected, actual)
}
}
}
func TestKeyNormalization(t *testing.T) {
testCases := []struct {
input string
expect Address
}{
{
input: "example.com",
expect: Address{
Host: "example.com",
},
},
{
input: "http://host:1234/path",
expect: Address{
Scheme: "http",
Host: "host",
Port: "1234",
Path: "/path",
},
},
{
input: "HTTP://A/ABCDEF",
expect: Address{
Scheme: "http",
Host: "a",
Path: "/ABCDEF",
},
},
{
input: "A/ABCDEF",
expect: Address{
Host: "a",
Path: "/ABCDEF",
},
},
{
input: "A:2015/Path",
expect: Address{
Host: "a",
Port: "2015",
Path: "/Path",
},
},
{
input: "sub.{env.MY_DOMAIN}",
expect: Address{
Host: "sub.{env.MY_DOMAIN}",
},
},
{
input: "sub.ExAmPle",
expect: Address{
Host: "sub.example",
},
},
{
input: "sub.\\{env.MY_DOMAIN\\}",
expect: Address{
Host: "sub.\\{env.my_domain\\}",
},
},
{
input: "sub.{env.MY_DOMAIN}.com",
expect: Address{
Host: "sub.{env.MY_DOMAIN}.com",
},
},
{
input: ":80",
expect: Address{
Port: "80",
},
},
{
input: ":443",
expect: Address{
Port: "443",
},
},
{
input: ":1234",
expect: Address{
Port: "1234",
},
},
{
input: "",
expect: Address{},
},
{
input: ":",
expect: Address{},
},
{
input: "[::]",
expect: Address{
Host: "::",
},
},
{
input: "127.0.0.1",
expect: Address{
Host: "127.0.0.1",
},
},
{
input: "[2001:db8:85a3:8d3:1319:8a2e:370:7348]:1234",
expect: Address{
Host: "2001:db8:85a3:8d3:1319:8a2e:370:7348",
Port: "1234",
},
},
{
// IPv4 address in IPv6 form (#4381)
input: "[::ffff:cff4:e77d]:1234",
expect: Address{
Host: "::ffff:cff4:e77d",
Port: "1234",
},
},
{
input: "::ffff:cff4:e77d",
expect: Address{
Host: "::ffff:cff4:e77d",
},
},
}
for i, tc := range testCases {
addr, err := ParseAddress(tc.input)
if err != nil {
t.Errorf("Test %d: Parsing address '%s': %v", i, tc.input, err)
continue
}
actual := addr.Normalize()
if actual.Scheme != tc.expect.Scheme {
t.Errorf("Test %d: Input '%s': Expected Scheme='%s' but got Scheme='%s'", i, tc.input, tc.expect.Scheme, actual.Scheme)
}
if actual.Host != tc.expect.Host {
t.Errorf("Test %d: Input '%s': Expected Host='%s' but got Host='%s'", i, tc.input, tc.expect.Host, actual.Host)
}
if actual.Port != tc.expect.Port {
t.Errorf("Test %d: Input '%s': Expected Port='%s' but got Port='%s'", i, tc.input, tc.expect.Port, actual.Port)
}
if actual.Path != tc.expect.Path {
t.Errorf("Test %d: Input '%s': Expected Path='%s' but got Path='%s'", i, tc.input, tc.expect.Path, actual.Path)
}
}
}
File diff suppressed because it is too large Load Diff
-355
View File
@@ -1,355 +0,0 @@
package httpcaddyfile
import (
"strings"
"testing"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
_ "github.com/caddyserver/caddy/v2/modules/logging"
)
func TestLogDirectiveSyntax(t *testing.T) {
for i, tc := range []struct {
input string
output string
expectError bool
}{
{
input: `:8080 {
log
}
`,
output: `{"apps":{"http":{"servers":{"srv0":{"listen":[":8080"],"logs":{}}}}}}`,
expectError: false,
},
{
input: `:8080 {
log {
core mock
output file foo.log
}
}
`,
output: `{"logging":{"logs":{"default":{"exclude":["http.log.access.log0"]},"log0":{"writer":{"filename":"foo.log","output":"file"},"core":{"module":"mock"},"include":["http.log.access.log0"]}}},"apps":{"http":{"servers":{"srv0":{"listen":[":8080"],"logs":{"default_logger_name":"log0"}}}}}}`,
expectError: false,
},
{
input: `:8080 {
log {
format filter {
wrap console
fields {
request>remote_ip ip_mask {
ipv4 24
ipv6 32
}
}
}
}
}
`,
output: `{"logging":{"logs":{"default":{"exclude":["http.log.access.log0"]},"log0":{"encoder":{"fields":{"request\u003eremote_ip":{"filter":"ip_mask","ipv4_cidr":24,"ipv6_cidr":32}},"format":"filter","wrap":{"format":"console"}},"include":["http.log.access.log0"]}}},"apps":{"http":{"servers":{"srv0":{"listen":[":8080"],"logs":{"default_logger_name":"log0"}}}}}}`,
expectError: false,
},
{
input: `:8080 {
log name-override {
core mock
output file foo.log
}
}
`,
output: `{"logging":{"logs":{"default":{"exclude":["http.log.access.name-override"]},"name-override":{"writer":{"filename":"foo.log","output":"file"},"core":{"module":"mock"},"include":["http.log.access.name-override"]}}},"apps":{"http":{"servers":{"srv0":{"listen":[":8080"],"logs":{"default_logger_name":"name-override"}}}}}}`,
expectError: false,
},
} {
adapter := caddyfile.Adapter{
ServerType: ServerType{},
}
out, _, err := adapter.Adapt([]byte(tc.input), nil)
if err != nil != tc.expectError {
t.Errorf("Test %d error expectation failed Expected: %v, got %s", i, tc.expectError, err)
continue
}
if string(out) != tc.output {
t.Errorf("Test %d error output mismatch Expected: %s, got %s", i, tc.output, out)
}
}
}
func TestRedirDirectiveSyntax(t *testing.T) {
for i, tc := range []struct {
input string
expectError bool
}{
{
input: `:8080 {
redir :8081
}`,
expectError: false,
},
{
input: `:8080 {
redir * :8081
}`,
expectError: false,
},
{
input: `:8080 {
redir /api/* :8081 300
}`,
expectError: false,
},
{
input: `:8080 {
redir :8081 300
}`,
expectError: false,
},
{
input: `:8080 {
redir /api/* :8081 399
}`,
expectError: false,
},
{
input: `:8080 {
redir :8081 399
}`,
expectError: false,
},
{
input: `:8080 {
redir /old.html /new.html
}`,
expectError: false,
},
{
input: `:8080 {
redir /old.html /new.html temporary
}`,
expectError: false,
},
{
input: `:8080 {
redir https://example.com{uri} permanent
}`,
expectError: false,
},
{
input: `:8080 {
redir /old.html /new.html permanent
}`,
expectError: false,
},
{
input: `:8080 {
redir /old.html /new.html html
}`,
expectError: false,
},
{
// this is now allowed so a Location header
// can be written and consumed by JS
// in the case of XHR requests
input: `:8080 {
redir * :8081 401
}`,
expectError: false,
},
{
input: `:8080 {
redir * :8081 402
}`,
expectError: true,
},
{
input: `:8080 {
redir * :8081 {http.reverse_proxy.status_code}
}`,
expectError: false,
},
{
input: `:8080 {
redir /old.html /new.html htlm
}`,
expectError: true,
},
{
input: `:8080 {
redir * :8081 200
}`,
expectError: true,
},
{
input: `:8080 {
redir * :8081 temp
}`,
expectError: true,
},
{
input: `:8080 {
redir * :8081 perm
}`,
expectError: true,
},
{
input: `:8080 {
redir * :8081 php
}`,
expectError: true,
},
} {
adapter := caddyfile.Adapter{
ServerType: ServerType{},
}
_, _, err := adapter.Adapt([]byte(tc.input), nil)
if err != nil != tc.expectError {
t.Errorf("Test %d error expectation failed Expected: %v, got %s", i, tc.expectError, err)
continue
}
}
}
func TestImportErrorLine(t *testing.T) {
for i, tc := range []struct {
input string
errorFunc func(err error) bool
}{
{
input: `(t1) {
abort {args[:]}
}
:8080 {
import t1
import t1 true
}`,
errorFunc: func(err error) bool {
return err != nil && strings.Contains(err.Error(), "Caddyfile:6 (import t1)")
},
},
{
input: `(t1) {
abort {args[:]}
}
:8080 {
import t1 true
}`,
errorFunc: func(err error) bool {
return err != nil && strings.Contains(err.Error(), "Caddyfile:5 (import t1)")
},
},
{
input: `
import testdata/import_variadic_snippet.txt
:8080 {
import t1 true
}`,
errorFunc: func(err error) bool {
return err == nil
},
},
{
input: `
import testdata/import_variadic_with_import.txt
:8080 {
import t1 true
import t2 true
}`,
errorFunc: func(err error) bool {
return err == nil
},
},
} {
adapter := caddyfile.Adapter{
ServerType: ServerType{},
}
_, _, err := adapter.Adapt([]byte(tc.input), nil)
if !tc.errorFunc(err) {
t.Errorf("Test %d error expectation failed, got %s", i, err)
continue
}
}
}
func TestNestedImport(t *testing.T) {
for i, tc := range []struct {
input string
errorFunc func(err error) bool
}{
{
input: `(t1) {
respond {args[0]} {args[1]}
}
(t2) {
import t1 {args[0]} 202
}
:8080 {
handle {
import t2 "foobar"
}
}`,
errorFunc: func(err error) bool {
return err == nil
},
},
{
input: `(t1) {
respond {args[:]}
}
(t2) {
import t1 {args[0]} {args[1]}
}
:8080 {
handle {
import t2 "foobar" 202
}
}`,
errorFunc: func(err error) bool {
return err == nil
},
},
{
input: `(t1) {
respond {args[0]} {args[1]}
}
(t2) {
import t1 {args[:]}
}
:8080 {
handle {
import t2 "foobar" 202
}
}`,
errorFunc: func(err error) bool {
return err == nil
},
},
} {
adapter := caddyfile.Adapter{
ServerType: ServerType{},
}
_, _, err := adapter.Adapt([]byte(tc.input), nil)
if !tc.errorFunc(err) {
t.Errorf("Test %d error expectation failed, got %s", i, err)
continue
}
}
}
-661
View File
@@ -1,661 +0,0 @@
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package httpcaddyfile
import (
"encoding/json"
"net"
"sort"
"strconv"
"strings"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
)
// defaultDirectiveOrder specifies the default order
// to apply directives in HTTP routes. This must only
// consist of directives that are included in Caddy's
// standard distribution.
//
// e.g. The 'root' directive goes near the start in
// case rewrites or redirects depend on existence of
// files, i.e. the file matcher, which must know the
// root first.
//
// e.g. The 'header' directive goes before 'redir' so
// that headers can be manipulated before doing redirects.
//
// e.g. The 'respond' directive is near the end because it
// writes a response and terminates the middleware chain.
var defaultDirectiveOrder = []string{
"tracing",
// set variables that may be used by other directives
"map",
"vars",
"fs",
"root",
"log_append",
"skip_log", // TODO: deprecated, renamed to log_skip
"log_skip",
"log_name",
"header",
"copy_response_headers", // only in reverse_proxy's handle_response
"request_body",
"redir",
// incoming request manipulation
"method",
"rewrite",
"uri",
"try_files",
// middleware handlers; some wrap responses
"basicauth", // TODO: deprecated, renamed to basic_auth
"basic_auth",
"forward_auth",
"request_header",
"encode",
"push",
"intercept",
"templates",
// special routing & dispatching directives
"invoke",
"handle",
"handle_path",
"route",
// handlers that typically respond to requests
"abort",
"error",
"copy_response", // only in reverse_proxy's handle_response
"respond",
"metrics",
"reverse_proxy",
"php_fastcgi",
"file_server",
"acme_server",
}
// directiveOrder specifies the order to apply directives
// in HTTP routes, after being modified by either the
// plugins or by the user via the "order" global option.
var directiveOrder = defaultDirectiveOrder
// directiveIsOrdered returns true if dir is
// a known, ordered (sorted) directive.
func directiveIsOrdered(dir string) bool {
for _, d := range directiveOrder {
if d == dir {
return true
}
}
return false
}
// RegisterDirective registers a unique directive dir with an
// associated unmarshaling (setup) function. When directive dir
// is encountered in a Caddyfile, setupFunc will be called to
// unmarshal its tokens.
func RegisterDirective(dir string, setupFunc UnmarshalFunc) {
if _, ok := registeredDirectives[dir]; ok {
panic("directive " + dir + " already registered")
}
registeredDirectives[dir] = setupFunc
}
// RegisterHandlerDirective is like RegisterDirective, but for
// directives which specifically output only an HTTP handler.
// Directives registered with this function will always have
// an optional matcher token as the first argument.
func RegisterHandlerDirective(dir string, setupFunc UnmarshalHandlerFunc) {
RegisterDirective(dir, func(h Helper) ([]ConfigValue, error) {
if !h.Next() {
return nil, h.ArgErr()
}
matcherSet, err := h.ExtractMatcherSet()
if err != nil {
return nil, err
}
val, err := setupFunc(h)
if err != nil {
return nil, err
}
return h.NewRoute(matcherSet, val), nil
})
}
// RegisterDirectiveOrder registers the default order for a
// directive from a plugin.
//
// This is useful when a plugin has a well-understood place
// it should run in the middleware pipeline, and it allows
// users to avoid having to define the order themselves.
//
// The directive dir may be placed in the position relative
// to ('before' or 'after') a directive included in Caddy's
// standard distribution. It cannot be relative to another
// plugin's directive.
//
// EXPERIMENTAL: This API may change or be removed.
func RegisterDirectiveOrder(dir string, position Positional, standardDir string) {
// check if directive was already ordered
if directiveIsOrdered(dir) {
panic("directive '" + dir + "' already ordered")
}
if position != Before && position != After {
panic("the 2nd argument must be either 'before' or 'after', got '" + position + "'")
}
// check if directive exists in standard distribution, since
// we can't allow plugins to depend on one another; we can't
// guarantee the order that plugins are loaded in.
foundStandardDir := false
for _, d := range defaultDirectiveOrder {
if d == standardDir {
foundStandardDir = true
}
}
if !foundStandardDir {
panic("the 3rd argument '" + standardDir + "' must be a directive that exists in the standard distribution of Caddy")
}
// insert directive into proper position
newOrder := directiveOrder
for i, d := range newOrder {
if d != standardDir {
continue
}
if position == Before {
newOrder = append(newOrder[:i], append([]string{dir}, newOrder[i:]...)...)
} else if position == After {
newOrder = append(newOrder[:i+1], append([]string{dir}, newOrder[i+1:]...)...)
}
break
}
directiveOrder = newOrder
}
// RegisterGlobalOption registers a unique global option opt with
// an associated unmarshaling (setup) function. When the global
// option opt is encountered in a Caddyfile, setupFunc will be
// called to unmarshal its tokens.
func RegisterGlobalOption(opt string, setupFunc UnmarshalGlobalFunc) {
if _, ok := registeredGlobalOptions[opt]; ok {
panic("global option " + opt + " already registered")
}
registeredGlobalOptions[opt] = setupFunc
}
// Helper is a type which helps setup a value from
// Caddyfile tokens.
type Helper struct {
*caddyfile.Dispenser
// State stores intermediate variables during caddyfile adaptation.
State map[string]any
options map[string]any
warnings *[]caddyconfig.Warning
matcherDefs map[string]caddy.ModuleMap
parentBlock caddyfile.ServerBlock
groupCounter counter
}
// Option gets the option keyed by name.
func (h Helper) Option(name string) any {
return h.options[name]
}
// Caddyfiles returns the list of config files from
// which tokens in the current server block were loaded.
func (h Helper) Caddyfiles() []string {
// first obtain set of names of files involved
// in this server block, without duplicates
files := make(map[string]struct{})
for _, segment := range h.parentBlock.Segments {
for _, token := range segment {
files[token.File] = struct{}{}
}
}
// then convert the set into a slice
filesSlice := make([]string, 0, len(files))
for file := range files {
filesSlice = append(filesSlice, file)
}
sort.Strings(filesSlice)
return filesSlice
}
// JSON converts val into JSON. Any errors are added to warnings.
func (h Helper) JSON(val any) json.RawMessage {
return caddyconfig.JSON(val, h.warnings)
}
// MatcherToken assumes the next argument token is (possibly) a matcher,
// and if so, returns the matcher set along with a true value. If the next
// token is not a matcher, nil and false is returned. Note that a true
// value may be returned with a nil matcher set if it is a catch-all.
func (h Helper) MatcherToken() (caddy.ModuleMap, bool, error) {
if !h.NextArg() {
return nil, false, nil
}
return matcherSetFromMatcherToken(h.Dispenser.Token(), h.matcherDefs, h.warnings)
}
// ExtractMatcherSet is like MatcherToken, except this is a higher-level
// method that returns the matcher set described by the matcher token,
// or nil if there is none, and deletes the matcher token from the
// dispenser and resets it as if this look-ahead never happened. Useful
// when wrapping a route (one or more handlers) in a user-defined matcher.
func (h Helper) ExtractMatcherSet() (caddy.ModuleMap, error) {
matcherSet, hasMatcher, err := h.MatcherToken()
if err != nil {
return nil, err
}
if hasMatcher {
// strip matcher token; we don't need to
// use the return value here because a
// new dispenser should have been made
// solely for this directive's tokens,
// with no other uses of same slice
h.Dispenser.Delete()
}
h.Dispenser.Reset() // pretend this lookahead never happened
return matcherSet, nil
}
// NewRoute returns config values relevant to creating a new HTTP route.
func (h Helper) NewRoute(matcherSet caddy.ModuleMap,
handler caddyhttp.MiddlewareHandler,
) []ConfigValue {
mod, err := caddy.GetModule(caddy.GetModuleID(handler))
if err != nil {
*h.warnings = append(*h.warnings, caddyconfig.Warning{
File: h.File(),
Line: h.Line(),
Message: err.Error(),
})
return nil
}
var matcherSetsRaw []caddy.ModuleMap
if matcherSet != nil {
matcherSetsRaw = append(matcherSetsRaw, matcherSet)
}
return []ConfigValue{
{
Class: "route",
Value: caddyhttp.Route{
MatcherSetsRaw: matcherSetsRaw,
HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(handler, "handler", mod.ID.Name(), h.warnings)},
},
},
}
}
// GroupRoutes adds the routes (caddyhttp.Route type) in vals to the
// same group, if there is more than one route in vals.
func (h Helper) GroupRoutes(vals []ConfigValue) {
// ensure there's at least two routes; group of one is pointless
var count int
for _, v := range vals {
if _, ok := v.Value.(caddyhttp.Route); ok {
count++
if count > 1 {
break
}
}
}
if count < 2 {
return
}
// now that we know the group will have some effect, do it
groupName := h.groupCounter.nextGroup()
for i := range vals {
if route, ok := vals[i].Value.(caddyhttp.Route); ok {
route.Group = groupName
vals[i].Value = route
}
}
}
// WithDispenser returns a new instance based on d. All others Helper
// fields are copied, so typically maps are shared with this new instance.
func (h Helper) WithDispenser(d *caddyfile.Dispenser) Helper {
h.Dispenser = d
return h
}
// ParseSegmentAsSubroute parses the segment such that its subdirectives
// are themselves treated as directives, from which a subroute is built
// and returned.
func ParseSegmentAsSubroute(h Helper) (caddyhttp.MiddlewareHandler, error) {
allResults, err := parseSegmentAsConfig(h)
if err != nil {
return nil, err
}
return buildSubroute(allResults, h.groupCounter, true)
}
// parseSegmentAsConfig parses the segment such that its subdirectives
// are themselves treated as directives, including named matcher definitions,
// and the raw Config structs are returned.
func parseSegmentAsConfig(h Helper) ([]ConfigValue, error) {
var allResults []ConfigValue
for h.Next() {
// don't allow non-matcher args on the first line
if h.NextArg() {
return nil, h.ArgErr()
}
// slice the linear list of tokens into top-level segments
var segments []caddyfile.Segment
for nesting := h.Nesting(); h.NextBlock(nesting); {
segments = append(segments, h.NextSegment())
}
// copy existing matcher definitions so we can augment
// new ones that are defined only in this scope
matcherDefs := make(map[string]caddy.ModuleMap, len(h.matcherDefs))
for key, val := range h.matcherDefs {
matcherDefs[key] = val
}
// find and extract any embedded matcher definitions in this scope
for i := 0; i < len(segments); i++ {
seg := segments[i]
if strings.HasPrefix(seg.Directive(), matcherPrefix) {
// parse, then add the matcher to matcherDefs
err := parseMatcherDefinitions(caddyfile.NewDispenser(seg), matcherDefs)
if err != nil {
return nil, err
}
// remove the matcher segment (consumed), then step back the loop
segments = append(segments[:i], segments[i+1:]...)
i--
}
}
// with matchers ready to go, evaluate each directive's segment
for _, seg := range segments {
dir := seg.Directive()
dirFunc, ok := registeredDirectives[dir]
if !ok {
return nil, h.Errf("unrecognized directive: %s - are you sure your Caddyfile structure (nesting and braces) is correct?", dir)
}
subHelper := h
subHelper.Dispenser = caddyfile.NewDispenser(seg)
subHelper.matcherDefs = matcherDefs
results, err := dirFunc(subHelper)
if err != nil {
return nil, h.Errf("parsing caddyfile tokens for '%s': %v", dir, err)
}
dir = normalizeDirectiveName(dir)
for _, result := range results {
result.directive = dir
allResults = append(allResults, result)
}
}
}
return allResults, nil
}
// ConfigValue represents a value to be added to the final
// configuration, or a value to be consulted when building
// the final configuration.
type ConfigValue struct {
// The kind of value this is. As the config is
// being built, the adapter will look in the
// "pile" for values belonging to a certain
// class when it is setting up a certain part
// of the config. The associated value will be
// type-asserted and placed accordingly.
Class string
// The value to be used when building the config.
// Generally its type is associated with the
// name of the Class.
Value any
directive string
}
func sortRoutes(routes []ConfigValue) {
dirPositions := make(map[string]int)
for i, dir := range directiveOrder {
dirPositions[dir] = i
}
sort.SliceStable(routes, func(i, j int) bool {
// if the directives are different, just use the established directive order
iDir, jDir := routes[i].directive, routes[j].directive
if iDir != jDir {
return dirPositions[iDir] < dirPositions[jDir]
}
// directives are the same; sub-sort by path matcher length if there's
// only one matcher set and one path (this is a very common case and
// usually -- but not always -- helpful/expected, oh well; user can
// always take manual control of order using handler or route blocks)
iRoute, ok := routes[i].Value.(caddyhttp.Route)
if !ok {
return false
}
jRoute, ok := routes[j].Value.(caddyhttp.Route)
if !ok {
return false
}
// decode the path matchers if there is just one matcher set
var iPM, jPM caddyhttp.MatchPath
if len(iRoute.MatcherSetsRaw) == 1 {
_ = json.Unmarshal(iRoute.MatcherSetsRaw[0]["path"], &iPM)
}
if len(jRoute.MatcherSetsRaw) == 1 {
_ = json.Unmarshal(jRoute.MatcherSetsRaw[0]["path"], &jPM)
}
// if there is only one path in the path matcher, sort by longer path
// (more specific) first; missing path matchers or multi-matchers are
// treated as zero-length paths
var iPathLen, jPathLen int
if len(iPM) == 1 {
iPathLen = len(iPM[0])
}
if len(jPM) == 1 {
jPathLen = len(jPM[0])
}
sortByPath := func() bool {
// we can only confidently compare path lengths if both
// directives have a single path to match (issue #5037)
if iPathLen > 0 && jPathLen > 0 {
// if both paths are the same except for a trailing wildcard,
// sort by the shorter path first (which is more specific)
if strings.TrimSuffix(iPM[0], "*") == strings.TrimSuffix(jPM[0], "*") {
return iPathLen < jPathLen
}
// sort most-specific (longest) path first
return iPathLen > jPathLen
}
// if both directives don't have a single path to compare,
// sort whichever one has a matcher first; if both have
// a matcher, sort equally (stable sort preserves order)
return len(iRoute.MatcherSetsRaw) > 0 && len(jRoute.MatcherSetsRaw) == 0
}()
// some directives involve setting values which can overwrite
// each other, so it makes most sense to reverse the order so
// that the least-specific matcher is first, allowing the last
// matching one to win
if iDir == "vars" {
return !sortByPath
}
// everything else is most-specific matcher first
return sortByPath
})
}
// serverBlock pairs a Caddyfile server block with
// a "pile" of config values, keyed by class name,
// as well as its parsed keys for convenience.
type serverBlock struct {
block caddyfile.ServerBlock
pile map[string][]ConfigValue // config values obtained from directives
keys []Address
}
// hostsFromKeys returns a list of all the non-empty hostnames found in
// the keys of the server block sb. If logger mode is false, a key with
// an empty hostname portion will return an empty slice, since that
// server block is interpreted to effectively match all hosts. An empty
// string is never added to the slice.
//
// If loggerMode is true, then the non-standard ports of keys will be
// joined to the hostnames. This is to effectively match the Host
// header of requests that come in for that key.
//
// The resulting slice is not sorted but will never have duplicates.
func (sb serverBlock) hostsFromKeys(loggerMode bool) []string {
// ensure each entry in our list is unique
hostMap := make(map[string]struct{})
for _, addr := range sb.keys {
if addr.Host == "" {
if !loggerMode {
// server block contains a key like ":443", i.e. the host portion
// is empty / catch-all, which means to match all hosts
return []string{}
}
// never append an empty string
continue
}
if loggerMode &&
addr.Port != "" &&
addr.Port != strconv.Itoa(caddyhttp.DefaultHTTPPort) &&
addr.Port != strconv.Itoa(caddyhttp.DefaultHTTPSPort) {
hostMap[net.JoinHostPort(addr.Host, addr.Port)] = struct{}{}
} else {
hostMap[addr.Host] = struct{}{}
}
}
// convert map to slice
sblockHosts := make([]string, 0, len(hostMap))
for host := range hostMap {
sblockHosts = append(sblockHosts, host)
}
return sblockHosts
}
func (sb serverBlock) hostsFromKeysNotHTTP(httpPort string) []string {
// ensure each entry in our list is unique
hostMap := make(map[string]struct{})
for _, addr := range sb.keys {
if addr.Host == "" {
continue
}
if addr.Scheme != "http" && addr.Port != httpPort {
hostMap[addr.Host] = struct{}{}
}
}
// convert map to slice
sblockHosts := make([]string, 0, len(hostMap))
for host := range hostMap {
sblockHosts = append(sblockHosts, host)
}
return sblockHosts
}
// hasHostCatchAllKey returns true if sb has a key that
// omits a host portion, i.e. it "catches all" hosts.
func (sb serverBlock) hasHostCatchAllKey() bool {
for _, addr := range sb.keys {
if addr.Host == "" {
return true
}
}
return false
}
// isAllHTTP returns true if all sb keys explicitly specify
// the http:// scheme
func (sb serverBlock) isAllHTTP() bool {
for _, addr := range sb.keys {
if addr.Scheme != "http" {
return false
}
}
return true
}
// Positional are the supported modes for ordering directives.
type Positional string
const (
Before Positional = "before"
After Positional = "after"
First Positional = "first"
Last Positional = "last"
)
type (
// UnmarshalFunc is a function which can unmarshal Caddyfile
// tokens into zero or more config values using a Helper type.
// These are passed in a call to RegisterDirective.
UnmarshalFunc func(h Helper) ([]ConfigValue, error)
// UnmarshalHandlerFunc is like UnmarshalFunc, except the
// output of the unmarshaling is an HTTP handler. This
// function does not need to deal with HTTP request matching
// which is abstracted away. Since writing HTTP handlers
// with Caddyfile support is very common, this is a more
// convenient way to add a handler to the chain since a lot
// of the details common to HTTP handlers are taken care of
// for you. These are passed to a call to
// RegisterHandlerDirective.
UnmarshalHandlerFunc func(h Helper) (caddyhttp.MiddlewareHandler, error)
// UnmarshalGlobalFunc is a function which can unmarshal Caddyfile
// tokens from a global option. It is passed the tokens to parse and
// existing value from the previous instance of this global option
// (if any). It returns the value to associate with this global option.
UnmarshalGlobalFunc func(d *caddyfile.Dispenser, existingVal any) (any, error)
)
var registeredDirectives = make(map[string]UnmarshalFunc)
var registeredGlobalOptions = make(map[string]UnmarshalGlobalFunc)
@@ -1,97 +0,0 @@
package httpcaddyfile
import (
"reflect"
"sort"
"testing"
)
func TestHostsFromKeys(t *testing.T) {
for i, tc := range []struct {
keys []Address
expectNormalMode []string
expectLoggerMode []string
}{
{
[]Address{
{Original: "foo", Host: "foo"},
},
[]string{"foo"},
[]string{"foo"},
},
{
[]Address{
{Original: "foo", Host: "foo"},
{Original: "bar", Host: "bar"},
},
[]string{"bar", "foo"},
[]string{"bar", "foo"},
},
{
[]Address{
{Original: ":2015", Port: "2015"},
},
[]string{},
[]string{},
},
{
[]Address{
{Original: ":443", Port: "443"},
},
[]string{},
[]string{},
},
{
[]Address{
{Original: "foo", Host: "foo"},
{Original: ":2015", Port: "2015"},
},
[]string{},
[]string{"foo"},
},
{
[]Address{
{Original: "example.com:2015", Host: "example.com", Port: "2015"},
},
[]string{"example.com"},
[]string{"example.com:2015"},
},
{
[]Address{
{Original: "example.com:80", Host: "example.com", Port: "80"},
},
[]string{"example.com"},
[]string{"example.com"},
},
{
[]Address{
{Original: "https://:2015/foo", Scheme: "https", Port: "2015", Path: "/foo"},
},
[]string{},
[]string{},
},
{
[]Address{
{Original: "https://example.com:2015/foo", Scheme: "https", Host: "example.com", Port: "2015", Path: "/foo"},
},
[]string{"example.com"},
[]string{"example.com:2015"},
},
} {
sb := serverBlock{keys: tc.keys}
// test in normal mode
actual := sb.hostsFromKeys(false)
sort.Strings(actual)
if !reflect.DeepEqual(tc.expectNormalMode, actual) {
t.Errorf("Test %d (loggerMode=false): Expected: %v Actual: %v", i, tc.expectNormalMode, actual)
}
// test in logger mode
actual = sb.hostsFromKeys(true)
sort.Strings(actual)
if !reflect.DeepEqual(tc.expectLoggerMode, actual) {
t.Errorf("Test %d (loggerMode=true): Expected: %v Actual: %v", i, tc.expectLoggerMode, actual)
}
}
}
File diff suppressed because it is too large Load Diff
-211
View File
@@ -1,211 +0,0 @@
package httpcaddyfile
import (
"testing"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
)
func TestMatcherSyntax(t *testing.T) {
for i, tc := range []struct {
input string
expectError bool
}{
{
input: `http://localhost
@debug {
query showdebug=1
}
`,
expectError: false,
},
{
input: `http://localhost
@debug {
query bad format
}
`,
expectError: true,
},
{
input: `http://localhost
@debug {
not {
path /somepath*
}
}
`,
expectError: false,
},
{
input: `http://localhost
@debug {
not path /somepath*
}
`,
expectError: false,
},
{
input: `http://localhost
@debug not path /somepath*
`,
expectError: false,
},
{
input: `@matcher {
path /matcher-not-allowed/outside-of-site-block/*
}
http://localhost
`,
expectError: true,
},
} {
adapter := caddyfile.Adapter{
ServerType: ServerType{},
}
_, _, err := adapter.Adapt([]byte(tc.input), nil)
if err != nil != tc.expectError {
t.Errorf("Test %d error expectation failed Expected: %v, got %s", i, tc.expectError, err)
continue
}
}
}
func TestSpecificity(t *testing.T) {
for i, tc := range []struct {
input string
expect int
}{
{"", 0},
{"*", 0},
{"*.*", 1},
{"{placeholder}", 0},
{"/{placeholder}", 1},
{"foo", 3},
{"example.com", 11},
{"a.example.com", 13},
{"*.example.com", 12},
{"/foo", 4},
{"/foo*", 4},
{"{placeholder}.example.com", 12},
{"{placeholder.example.com", 24},
{"}.", 2},
{"}{", 2},
{"{}", 0},
{"{{{}}", 1},
} {
actual := specificity(tc.input)
if actual != tc.expect {
t.Errorf("Test %d (%s): Expected %d but got %d", i, tc.input, tc.expect, actual)
}
}
}
func TestGlobalOptions(t *testing.T) {
for i, tc := range []struct {
input string
expectError bool
}{
{
input: `
{
email test@example.com
}
:80
`,
expectError: false,
},
{
input: `
{
admin off
}
:80
`,
expectError: false,
},
{
input: `
{
admin 127.0.0.1:2020
}
:80
`,
expectError: false,
},
{
input: `
{
admin {
disabled false
}
}
:80
`,
expectError: true,
},
{
input: `
{
admin {
enforce_origin
origins 192.168.1.1:2020 127.0.0.1:2020
}
}
:80
`,
expectError: false,
},
{
input: `
{
admin 127.0.0.1:2020 {
enforce_origin
origins 192.168.1.1:2020 127.0.0.1:2020
}
}
:80
`,
expectError: false,
},
{
input: `
{
admin 192.168.1.1:2020 127.0.0.1:2020 {
enforce_origin
origins 192.168.1.1:2020 127.0.0.1:2020
}
}
:80
`,
expectError: true,
},
{
input: `
{
admin off {
enforce_origin
origins 192.168.1.1:2020 127.0.0.1:2020
}
}
:80
`,
expectError: true,
},
} {
adapter := caddyfile.Adapter{
ServerType: ServerType{},
}
_, _, err := adapter.Adapt([]byte(tc.input), nil)
if err != nil != tc.expectError {
t.Errorf("Test %d error expectation failed Expected: %v, got %s", i, tc.expectError, err)
continue
}
}
}
-517
View File
@@ -1,517 +0,0 @@
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package httpcaddyfile
import (
"strconv"
"github.com/caddyserver/certmagic"
"github.com/mholt/acmez/v2/acme"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/modules/caddytls"
)
func init() {
RegisterGlobalOption("debug", parseOptTrue)
RegisterGlobalOption("http_port", parseOptHTTPPort)
RegisterGlobalOption("https_port", parseOptHTTPSPort)
RegisterGlobalOption("default_bind", parseOptStringList)
RegisterGlobalOption("grace_period", parseOptDuration)
RegisterGlobalOption("shutdown_delay", parseOptDuration)
RegisterGlobalOption("default_sni", parseOptSingleString)
RegisterGlobalOption("fallback_sni", parseOptSingleString)
RegisterGlobalOption("order", parseOptOrder)
RegisterGlobalOption("storage", parseOptStorage)
RegisterGlobalOption("storage_clean_interval", parseOptDuration)
RegisterGlobalOption("renew_interval", parseOptDuration)
RegisterGlobalOption("ocsp_interval", parseOptDuration)
RegisterGlobalOption("acme_ca", parseOptSingleString)
RegisterGlobalOption("acme_ca_root", parseOptSingleString)
RegisterGlobalOption("acme_dns", parseOptACMEDNS)
RegisterGlobalOption("acme_eab", parseOptACMEEAB)
RegisterGlobalOption("cert_issuer", parseOptCertIssuer)
RegisterGlobalOption("skip_install_trust", parseOptTrue)
RegisterGlobalOption("email", parseOptSingleString)
RegisterGlobalOption("admin", parseOptAdmin)
RegisterGlobalOption("on_demand_tls", parseOptOnDemand)
RegisterGlobalOption("local_certs", parseOptTrue)
RegisterGlobalOption("key_type", parseOptSingleString)
RegisterGlobalOption("auto_https", parseOptAutoHTTPS)
RegisterGlobalOption("servers", parseServerOptions)
RegisterGlobalOption("ocsp_stapling", parseOCSPStaplingOptions)
RegisterGlobalOption("cert_lifetime", parseOptDuration)
RegisterGlobalOption("log", parseLogOptions)
RegisterGlobalOption("preferred_chains", parseOptPreferredChains)
RegisterGlobalOption("persist_config", parseOptPersistConfig)
}
func parseOptTrue(d *caddyfile.Dispenser, _ any) (any, error) { return true, nil }
func parseOptHTTPPort(d *caddyfile.Dispenser, _ any) (any, error) {
d.Next() // consume option name
var httpPort int
var httpPortStr string
if !d.AllArgs(&httpPortStr) {
return 0, d.ArgErr()
}
var err error
httpPort, err = strconv.Atoi(httpPortStr)
if err != nil {
return 0, d.Errf("converting port '%s' to integer value: %v", httpPortStr, err)
}
return httpPort, nil
}
func parseOptHTTPSPort(d *caddyfile.Dispenser, _ any) (any, error) {
d.Next() // consume option name
var httpsPort int
var httpsPortStr string
if !d.AllArgs(&httpsPortStr) {
return 0, d.ArgErr()
}
var err error
httpsPort, err = strconv.Atoi(httpsPortStr)
if err != nil {
return 0, d.Errf("converting port '%s' to integer value: %v", httpsPortStr, err)
}
return httpsPort, nil
}
func parseOptOrder(d *caddyfile.Dispenser, _ any) (any, error) {
d.Next() // consume option name
// get directive name
if !d.Next() {
return nil, d.ArgErr()
}
dirName := d.Val()
if _, ok := registeredDirectives[dirName]; !ok {
return nil, d.Errf("%s is not a registered directive", dirName)
}
// get positional token
if !d.Next() {
return nil, d.ArgErr()
}
pos := Positional(d.Val())
newOrder := directiveOrder
// if directive exists, first remove it
for i, d := range newOrder {
if d == dirName {
newOrder = append(newOrder[:i], newOrder[i+1:]...)
break
}
}
// act on the positional
switch pos {
case First:
newOrder = append([]string{dirName}, newOrder...)
if d.NextArg() {
return nil, d.ArgErr()
}
directiveOrder = newOrder
return newOrder, nil
case Last:
newOrder = append(newOrder, dirName)
if d.NextArg() {
return nil, d.ArgErr()
}
directiveOrder = newOrder
return newOrder, nil
case Before:
case After:
default:
return nil, d.Errf("unknown positional '%s'", pos)
}
// get name of other directive
if !d.NextArg() {
return nil, d.ArgErr()
}
otherDir := d.Val()
if d.NextArg() {
return nil, d.ArgErr()
}
// insert directive into proper position
for i, d := range newOrder {
if d == otherDir {
if pos == Before {
newOrder = append(newOrder[:i], append([]string{dirName}, newOrder[i:]...)...)
} else if pos == After {
newOrder = append(newOrder[:i+1], append([]string{dirName}, newOrder[i+1:]...)...)
}
break
}
}
directiveOrder = newOrder
return newOrder, nil
}
func parseOptStorage(d *caddyfile.Dispenser, _ any) (any, error) {
if !d.Next() { // consume option name
return nil, d.ArgErr()
}
if !d.Next() { // get storage module name
return nil, d.ArgErr()
}
modID := "caddy.storage." + d.Val()
unm, err := caddyfile.UnmarshalModule(d, modID)
if err != nil {
return nil, err
}
storage, ok := unm.(caddy.StorageConverter)
if !ok {
return nil, d.Errf("module %s is not a caddy.StorageConverter", modID)
}
return storage, nil
}
func parseOptDuration(d *caddyfile.Dispenser, _ any) (any, error) {
if !d.Next() { // consume option name
return nil, d.ArgErr()
}
if !d.Next() { // get duration value
return nil, d.ArgErr()
}
dur, err := caddy.ParseDuration(d.Val())
if err != nil {
return nil, err
}
return caddy.Duration(dur), nil
}
func parseOptACMEDNS(d *caddyfile.Dispenser, _ any) (any, error) {
if !d.Next() { // consume option name
return nil, d.ArgErr()
}
if !d.Next() { // get DNS module name
return nil, d.ArgErr()
}
modID := "dns.providers." + d.Val()
unm, err := caddyfile.UnmarshalModule(d, modID)
if err != nil {
return nil, err
}
prov, ok := unm.(certmagic.DNSProvider)
if !ok {
return nil, d.Errf("module %s (%T) is not a certmagic.DNSProvider", modID, unm)
}
return prov, nil
}
func parseOptACMEEAB(d *caddyfile.Dispenser, _ any) (any, error) {
eab := new(acme.EAB)
d.Next() // consume option name
if d.NextArg() {
return nil, d.ArgErr()
}
for d.NextBlock(0) {
switch d.Val() {
case "key_id":
if !d.NextArg() {
return nil, d.ArgErr()
}
eab.KeyID = d.Val()
case "mac_key":
if !d.NextArg() {
return nil, d.ArgErr()
}
eab.MACKey = d.Val()
default:
return nil, d.Errf("unrecognized parameter '%s'", d.Val())
}
}
return eab, nil
}
func parseOptCertIssuer(d *caddyfile.Dispenser, existing any) (any, error) {
d.Next() // consume option name
var issuers []certmagic.Issuer
if existing != nil {
issuers = existing.([]certmagic.Issuer)
}
// get issuer module name
if !d.Next() {
return nil, d.ArgErr()
}
modID := "tls.issuance." + d.Val()
unm, err := caddyfile.UnmarshalModule(d, modID)
if err != nil {
return nil, err
}
iss, ok := unm.(certmagic.Issuer)
if !ok {
return nil, d.Errf("module %s (%T) is not a certmagic.Issuer", modID, unm)
}
issuers = append(issuers, iss)
return issuers, nil
}
func parseOptSingleString(d *caddyfile.Dispenser, _ any) (any, error) {
d.Next() // consume option name
if !d.Next() {
return "", d.ArgErr()
}
val := d.Val()
if d.Next() {
return "", d.ArgErr()
}
return val, nil
}
func parseOptStringList(d *caddyfile.Dispenser, _ any) (any, error) {
d.Next() // consume option name
val := d.RemainingArgs()
if len(val) == 0 {
return "", d.ArgErr()
}
return val, nil
}
func parseOptAdmin(d *caddyfile.Dispenser, _ any) (any, error) {
d.Next() // consume option name
adminCfg := new(caddy.AdminConfig)
if d.NextArg() {
listenAddress := d.Val()
if listenAddress == "off" {
adminCfg.Disabled = true
if d.Next() { // Do not accept any remaining options including block
return nil, d.Err("No more option is allowed after turning off admin config")
}
} else {
adminCfg.Listen = listenAddress
if d.NextArg() { // At most 1 arg is allowed
return nil, d.ArgErr()
}
}
}
for d.NextBlock(0) {
switch d.Val() {
case "enforce_origin":
adminCfg.EnforceOrigin = true
case "origins":
adminCfg.Origins = d.RemainingArgs()
default:
return nil, d.Errf("unrecognized parameter '%s'", d.Val())
}
}
if adminCfg.Listen == "" && !adminCfg.Disabled {
adminCfg.Listen = caddy.DefaultAdminListen
}
return adminCfg, nil
}
func parseOptOnDemand(d *caddyfile.Dispenser, _ any) (any, error) {
d.Next() // consume option name
if d.NextArg() {
return nil, d.ArgErr()
}
var ond *caddytls.OnDemandConfig
for nesting := d.Nesting(); d.NextBlock(nesting); {
switch d.Val() {
case "ask":
if !d.NextArg() {
return nil, d.ArgErr()
}
if ond == nil {
ond = new(caddytls.OnDemandConfig)
}
if ond.PermissionRaw != nil {
return nil, d.Err("on-demand TLS permission module (or 'ask') already specified")
}
perm := caddytls.PermissionByHTTP{Endpoint: d.Val()}
ond.PermissionRaw = caddyconfig.JSONModuleObject(perm, "module", "http", nil)
case "permission":
if !d.NextArg() {
return nil, d.ArgErr()
}
if ond == nil {
ond = new(caddytls.OnDemandConfig)
}
if ond.PermissionRaw != nil {
return nil, d.Err("on-demand TLS permission module (or 'ask') already specified")
}
modName := d.Val()
modID := "tls.permission." + modName
unm, err := caddyfile.UnmarshalModule(d, modID)
if err != nil {
return nil, err
}
perm, ok := unm.(caddytls.OnDemandPermission)
if !ok {
return nil, d.Errf("module %s (%T) is not an on-demand TLS permission module", modID, unm)
}
ond.PermissionRaw = caddyconfig.JSONModuleObject(perm, "module", modName, nil)
case "interval":
if !d.NextArg() {
return nil, d.ArgErr()
}
dur, err := caddy.ParseDuration(d.Val())
if err != nil {
return nil, err
}
if ond == nil {
ond = new(caddytls.OnDemandConfig)
}
if ond.RateLimit == nil {
ond.RateLimit = new(caddytls.RateLimit)
}
ond.RateLimit.Interval = caddy.Duration(dur)
case "burst":
if !d.NextArg() {
return nil, d.ArgErr()
}
burst, err := strconv.Atoi(d.Val())
if err != nil {
return nil, err
}
if ond == nil {
ond = new(caddytls.OnDemandConfig)
}
if ond.RateLimit == nil {
ond.RateLimit = new(caddytls.RateLimit)
}
ond.RateLimit.Burst = burst
default:
return nil, d.Errf("unrecognized parameter '%s'", d.Val())
}
}
if ond == nil {
return nil, d.Err("expected at least one config parameter for on_demand_tls")
}
return ond, nil
}
func parseOptPersistConfig(d *caddyfile.Dispenser, _ any) (any, error) {
d.Next() // consume option name
if !d.Next() {
return "", d.ArgErr()
}
val := d.Val()
if d.Next() {
return "", d.ArgErr()
}
if val != "off" {
return "", d.Errf("persist_config must be 'off'")
}
return val, nil
}
func parseOptAutoHTTPS(d *caddyfile.Dispenser, _ any) (any, error) {
d.Next() // consume option name
if !d.Next() {
return "", d.ArgErr()
}
val := d.Val()
if d.Next() {
return "", d.ArgErr()
}
if val != "off" && val != "disable_redirects" && val != "disable_certs" && val != "ignore_loaded_certs" {
return "", d.Errf("auto_https must be one of 'off', 'disable_redirects', 'disable_certs', or 'ignore_loaded_certs'")
}
return val, nil
}
func parseServerOptions(d *caddyfile.Dispenser, _ any) (any, error) {
return unmarshalCaddyfileServerOptions(d)
}
func parseOCSPStaplingOptions(d *caddyfile.Dispenser, _ any) (any, error) {
d.Next() // consume option name
var val string
if !d.AllArgs(&val) {
return nil, d.ArgErr()
}
if val != "off" {
return nil, d.Errf("invalid argument '%s'", val)
}
return certmagic.OCSPConfig{
DisableStapling: val == "off",
}, nil
}
// parseLogOptions parses the global log option. Syntax:
//
// log [name] {
// output <writer_module> ...
// format <encoder_module> ...
// level <level>
// include <namespaces...>
// exclude <namespaces...>
// }
//
// When the name argument is unspecified, this directive modifies the default
// logger.
func parseLogOptions(d *caddyfile.Dispenser, existingVal any) (any, error) {
currentNames := make(map[string]struct{})
if existingVal != nil {
innerVals, ok := existingVal.([]ConfigValue)
if !ok {
return nil, d.Errf("existing log values of unexpected type: %T", existingVal)
}
for _, rawVal := range innerVals {
val, ok := rawVal.Value.(namedCustomLog)
if !ok {
return nil, d.Errf("existing log value of unexpected type: %T", existingVal)
}
currentNames[val.name] = struct{}{}
}
}
var warnings []caddyconfig.Warning
// Call out the same parser that handles server-specific log configuration.
configValues, err := parseLogHelper(
Helper{
Dispenser: d,
warnings: &warnings,
},
currentNames,
)
if err != nil {
return nil, err
}
if len(warnings) > 0 {
return nil, d.Errf("warnings found in parsing global log options: %+v", warnings)
}
return configValues, nil
}
func parseOptPreferredChains(d *caddyfile.Dispenser, _ any) (any, error) {
d.Next()
return caddytls.ParseCaddyfilePreferredChainsOptions(d)
}
-64
View File
@@ -1,64 +0,0 @@
package httpcaddyfile
import (
"testing"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
_ "github.com/caddyserver/caddy/v2/modules/logging"
)
func TestGlobalLogOptionSyntax(t *testing.T) {
for i, tc := range []struct {
input string
output string
expectError bool
}{
// NOTE: Additional test cases of successful Caddyfile parsing
// are present in: caddytest/integration/caddyfile_adapt/
{
input: `{
log default
}
`,
output: `{}`,
expectError: false,
},
{
input: `{
log example {
output file foo.log
}
log example {
format json
}
}
`,
expectError: true,
},
{
input: `{
log example /foo {
output file foo.log
}
}
`,
expectError: true,
},
} {
adapter := caddyfile.Adapter{
ServerType: ServerType{},
}
out, _, err := adapter.Adapt([]byte(tc.input), nil)
if err != nil != tc.expectError {
t.Errorf("Test %d error expectation failed Expected: %v, got %v", i, tc.expectError, err)
continue
}
if string(out) != tc.output {
t.Errorf("Test %d error output mismatch Expected: %s, got %s", i, tc.output, out)
}
}
}
-229
View File
@@ -1,229 +0,0 @@
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package httpcaddyfile
import (
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/modules/caddypki"
)
func init() {
RegisterGlobalOption("pki", parsePKIApp)
}
// parsePKIApp parses the global log option. Syntax:
//
// pki {
// ca [<id>] {
// name <name>
// root_cn <name>
// intermediate_cn <name>
// intermediate_lifetime <duration>
// root {
// cert <path>
// key <path>
// format <format>
// }
// intermediate {
// cert <path>
// key <path>
// format <format>
// }
// }
// }
//
// When the CA ID is unspecified, 'local' is assumed.
func parsePKIApp(d *caddyfile.Dispenser, existingVal any) (any, error) {
d.Next() // consume app name
pki := &caddypki.PKI{
CAs: make(map[string]*caddypki.CA),
}
for d.NextBlock(0) {
switch d.Val() {
case "ca":
pkiCa := new(caddypki.CA)
if d.NextArg() {
pkiCa.ID = d.Val()
if d.NextArg() {
return nil, d.ArgErr()
}
}
if pkiCa.ID == "" {
pkiCa.ID = caddypki.DefaultCAID
}
for nesting := d.Nesting(); d.NextBlock(nesting); {
switch d.Val() {
case "name":
if !d.NextArg() {
return nil, d.ArgErr()
}
pkiCa.Name = d.Val()
case "root_cn":
if !d.NextArg() {
return nil, d.ArgErr()
}
pkiCa.RootCommonName = d.Val()
case "intermediate_cn":
if !d.NextArg() {
return nil, d.ArgErr()
}
pkiCa.IntermediateCommonName = d.Val()
case "intermediate_lifetime":
if !d.NextArg() {
return nil, d.ArgErr()
}
dur, err := caddy.ParseDuration(d.Val())
if err != nil {
return nil, err
}
pkiCa.IntermediateLifetime = caddy.Duration(dur)
case "root":
if pkiCa.Root == nil {
pkiCa.Root = new(caddypki.KeyPair)
}
for nesting := d.Nesting(); d.NextBlock(nesting); {
switch d.Val() {
case "cert":
if !d.NextArg() {
return nil, d.ArgErr()
}
pkiCa.Root.Certificate = d.Val()
case "key":
if !d.NextArg() {
return nil, d.ArgErr()
}
pkiCa.Root.PrivateKey = d.Val()
case "format":
if !d.NextArg() {
return nil, d.ArgErr()
}
pkiCa.Root.Format = d.Val()
default:
return nil, d.Errf("unrecognized pki ca root option '%s'", d.Val())
}
}
case "intermediate":
if pkiCa.Intermediate == nil {
pkiCa.Intermediate = new(caddypki.KeyPair)
}
for nesting := d.Nesting(); d.NextBlock(nesting); {
switch d.Val() {
case "cert":
if !d.NextArg() {
return nil, d.ArgErr()
}
pkiCa.Intermediate.Certificate = d.Val()
case "key":
if !d.NextArg() {
return nil, d.ArgErr()
}
pkiCa.Intermediate.PrivateKey = d.Val()
case "format":
if !d.NextArg() {
return nil, d.ArgErr()
}
pkiCa.Intermediate.Format = d.Val()
default:
return nil, d.Errf("unrecognized pki ca intermediate option '%s'", d.Val())
}
}
default:
return nil, d.Errf("unrecognized pki ca option '%s'", d.Val())
}
}
pki.CAs[pkiCa.ID] = pkiCa
default:
return nil, d.Errf("unrecognized pki option '%s'", d.Val())
}
}
return pki, nil
}
func (st ServerType) buildPKIApp(
pairings []sbAddrAssociation,
options map[string]any,
warnings []caddyconfig.Warning,
) (*caddypki.PKI, []caddyconfig.Warning, error) {
skipInstallTrust := false
if _, ok := options["skip_install_trust"]; ok {
skipInstallTrust = true
}
falseBool := false
// Load the PKI app configured via global options
var pkiApp *caddypki.PKI
unwrappedPki, ok := options["pki"].(*caddypki.PKI)
if ok {
pkiApp = unwrappedPki
} else {
pkiApp = &caddypki.PKI{CAs: make(map[string]*caddypki.CA)}
}
for _, ca := range pkiApp.CAs {
if skipInstallTrust {
ca.InstallTrust = &falseBool
}
pkiApp.CAs[ca.ID] = ca
}
// Add in the CAs configured via directives
for _, p := range pairings {
for _, sblock := range p.serverBlocks {
// find all the CAs that were defined and add them to the app config
// i.e. from any "acme_server" directives
for _, caCfgValue := range sblock.pile["pki.ca"] {
ca := caCfgValue.Value.(*caddypki.CA)
if skipInstallTrust {
ca.InstallTrust = &falseBool
}
// the CA might already exist from global options, so
// don't overwrite it in that case
if _, ok := pkiApp.CAs[ca.ID]; !ok {
pkiApp.CAs[ca.ID] = ca
}
}
}
}
// if there was no CAs defined in any of the servers,
// and we were requested to not install trust, then
// add one for the default/local CA to do so
if len(pkiApp.CAs) == 0 && skipInstallTrust {
ca := new(caddypki.CA)
ca.ID = caddypki.DefaultCAID
ca.InstallTrust = &falseBool
pkiApp.CAs[ca.ID] = ca
}
return pkiApp, warnings, nil
}
-351
View File
@@ -1,351 +0,0 @@
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package httpcaddyfile
import (
"encoding/json"
"fmt"
"github.com/dustin/go-humanize"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
)
// serverOptions collects server config overrides parsed from Caddyfile global options
type serverOptions struct {
// If set, will only apply these options to servers that contain a
// listener address that matches exactly. If empty, will apply to all
// servers that were not already matched by another serverOptions.
ListenerAddress string
// These will all map 1:1 to the caddyhttp.Server struct
Name string
ListenerWrappersRaw []json.RawMessage
ReadTimeout caddy.Duration
ReadHeaderTimeout caddy.Duration
WriteTimeout caddy.Duration
IdleTimeout caddy.Duration
KeepAliveInterval caddy.Duration
MaxHeaderBytes int
EnableFullDuplex bool
Protocols []string
StrictSNIHost *bool
TrustedProxiesRaw json.RawMessage
TrustedProxiesStrict int
ClientIPHeaders []string
ShouldLogCredentials bool
Metrics *caddyhttp.Metrics
Trace bool // TODO: EXPERIMENTAL
}
func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
d.Next() // consume option name
serverOpts := serverOptions{}
if d.NextArg() {
serverOpts.ListenerAddress = d.Val()
if d.NextArg() {
return nil, d.ArgErr()
}
}
for d.NextBlock(0) {
switch d.Val() {
case "name":
if serverOpts.ListenerAddress == "" {
return nil, d.Errf("cannot set a name for a server without a listener address")
}
if !d.NextArg() {
return nil, d.ArgErr()
}
serverOpts.Name = d.Val()
case "listener_wrappers":
for nesting := d.Nesting(); d.NextBlock(nesting); {
modID := "caddy.listeners." + d.Val()
unm, err := caddyfile.UnmarshalModule(d, modID)
if err != nil {
return nil, err
}
listenerWrapper, ok := unm.(caddy.ListenerWrapper)
if !ok {
return nil, fmt.Errorf("module %s (%T) is not a listener wrapper", modID, unm)
}
jsonListenerWrapper := caddyconfig.JSONModuleObject(
listenerWrapper,
"wrapper",
listenerWrapper.(caddy.Module).CaddyModule().ID.Name(),
nil,
)
serverOpts.ListenerWrappersRaw = append(serverOpts.ListenerWrappersRaw, jsonListenerWrapper)
}
case "timeouts":
for nesting := d.Nesting(); d.NextBlock(nesting); {
switch d.Val() {
case "read_body":
if !d.NextArg() {
return nil, d.ArgErr()
}
dur, err := caddy.ParseDuration(d.Val())
if err != nil {
return nil, d.Errf("parsing read_body timeout duration: %v", err)
}
serverOpts.ReadTimeout = caddy.Duration(dur)
case "read_header":
if !d.NextArg() {
return nil, d.ArgErr()
}
dur, err := caddy.ParseDuration(d.Val())
if err != nil {
return nil, d.Errf("parsing read_header timeout duration: %v", err)
}
serverOpts.ReadHeaderTimeout = caddy.Duration(dur)
case "write":
if !d.NextArg() {
return nil, d.ArgErr()
}
dur, err := caddy.ParseDuration(d.Val())
if err != nil {
return nil, d.Errf("parsing write timeout duration: %v", err)
}
serverOpts.WriteTimeout = caddy.Duration(dur)
case "idle":
if !d.NextArg() {
return nil, d.ArgErr()
}
dur, err := caddy.ParseDuration(d.Val())
if err != nil {
return nil, d.Errf("parsing idle timeout duration: %v", err)
}
serverOpts.IdleTimeout = caddy.Duration(dur)
default:
return nil, d.Errf("unrecognized timeouts option '%s'", d.Val())
}
}
case "keepalive_interval":
if !d.NextArg() {
return nil, d.ArgErr()
}
dur, err := caddy.ParseDuration(d.Val())
if err != nil {
return nil, d.Errf("parsing keepalive interval duration: %v", err)
}
serverOpts.KeepAliveInterval = caddy.Duration(dur)
case "max_header_size":
var sizeStr string
if !d.AllArgs(&sizeStr) {
return nil, d.ArgErr()
}
size, err := humanize.ParseBytes(sizeStr)
if err != nil {
return nil, d.Errf("parsing max_header_size: %v", err)
}
serverOpts.MaxHeaderBytes = int(size)
case "enable_full_duplex":
if d.NextArg() {
return nil, d.ArgErr()
}
serverOpts.EnableFullDuplex = true
case "log_credentials":
if d.NextArg() {
return nil, d.ArgErr()
}
serverOpts.ShouldLogCredentials = true
case "protocols":
protos := d.RemainingArgs()
for _, proto := range protos {
if proto != "h1" && proto != "h2" && proto != "h2c" && proto != "h3" {
return nil, d.Errf("unknown protocol '%s': expected h1, h2, h2c, or h3", proto)
}
if sliceContains(serverOpts.Protocols, proto) {
return nil, d.Errf("protocol %s specified more than once", proto)
}
serverOpts.Protocols = append(serverOpts.Protocols, proto)
}
if nesting := d.Nesting(); d.NextBlock(nesting) {
return nil, d.ArgErr()
}
case "strict_sni_host":
if d.NextArg() && d.Val() != "insecure_off" && d.Val() != "on" {
return nil, d.Errf("strict_sni_host only supports 'on' or 'insecure_off', got '%s'", d.Val())
}
boolVal := true
if d.Val() == "insecure_off" {
boolVal = false
}
serverOpts.StrictSNIHost = &boolVal
case "trusted_proxies":
if !d.NextArg() {
return nil, d.Err("trusted_proxies expects an IP range source module name as its first argument")
}
modID := "http.ip_sources." + d.Val()
unm, err := caddyfile.UnmarshalModule(d, modID)
if err != nil {
return nil, err
}
source, ok := unm.(caddyhttp.IPRangeSource)
if !ok {
return nil, fmt.Errorf("module %s (%T) is not an IP range source", modID, unm)
}
jsonSource := caddyconfig.JSONModuleObject(
source,
"source",
source.(caddy.Module).CaddyModule().ID.Name(),
nil,
)
serverOpts.TrustedProxiesRaw = jsonSource
case "trusted_proxies_strict":
if d.NextArg() {
return nil, d.ArgErr()
}
serverOpts.TrustedProxiesStrict = 1
case "client_ip_headers":
headers := d.RemainingArgs()
for _, header := range headers {
if sliceContains(serverOpts.ClientIPHeaders, header) {
return nil, d.Errf("client IP header %s specified more than once", header)
}
serverOpts.ClientIPHeaders = append(serverOpts.ClientIPHeaders, header)
}
if nesting := d.Nesting(); d.NextBlock(nesting) {
return nil, d.ArgErr()
}
case "metrics":
if d.NextArg() {
return nil, d.ArgErr()
}
if nesting := d.Nesting(); d.NextBlock(nesting) {
return nil, d.ArgErr()
}
serverOpts.Metrics = new(caddyhttp.Metrics)
case "trace":
if d.NextArg() {
return nil, d.ArgErr()
}
serverOpts.Trace = true
default:
return nil, d.Errf("unrecognized servers option '%s'", d.Val())
}
}
return serverOpts, nil
}
// applyServerOptions sets the server options on the appropriate servers
func applyServerOptions(
servers map[string]*caddyhttp.Server,
options map[string]any,
_ *[]caddyconfig.Warning,
) error {
serverOpts, ok := options["servers"].([]serverOptions)
if !ok {
return nil
}
// check for duplicate names, which would clobber the config
existingNames := map[string]bool{}
for _, opts := range serverOpts {
if opts.Name == "" {
continue
}
if existingNames[opts.Name] {
return fmt.Errorf("cannot use duplicate server name '%s'", opts.Name)
}
existingNames[opts.Name] = true
}
// collect the server name overrides
nameReplacements := map[string]string{}
for key, server := range servers {
// find the options that apply to this server
opts := func() *serverOptions {
for _, entry := range serverOpts {
if entry.ListenerAddress == "" {
return &entry
}
for _, listener := range server.Listen {
if entry.ListenerAddress == listener {
return &entry
}
}
}
return nil
}()
// if none apply, then move to the next server
if opts == nil {
continue
}
// set all the options
server.ListenerWrappersRaw = opts.ListenerWrappersRaw
server.ReadTimeout = opts.ReadTimeout
server.ReadHeaderTimeout = opts.ReadHeaderTimeout
server.WriteTimeout = opts.WriteTimeout
server.IdleTimeout = opts.IdleTimeout
server.KeepAliveInterval = opts.KeepAliveInterval
server.MaxHeaderBytes = opts.MaxHeaderBytes
server.EnableFullDuplex = opts.EnableFullDuplex
server.Protocols = opts.Protocols
server.StrictSNIHost = opts.StrictSNIHost
server.TrustedProxiesRaw = opts.TrustedProxiesRaw
server.ClientIPHeaders = opts.ClientIPHeaders
server.TrustedProxiesStrict = opts.TrustedProxiesStrict
server.Metrics = opts.Metrics
if opts.ShouldLogCredentials {
if server.Logs == nil {
server.Logs = new(caddyhttp.ServerLogConfig)
}
server.Logs.ShouldLogCredentials = opts.ShouldLogCredentials
}
if opts.Trace {
// TODO: THIS IS EXPERIMENTAL (MAY 2024)
if server.Logs == nil {
server.Logs = new(caddyhttp.ServerLogConfig)
}
server.Logs.Trace = opts.Trace
}
if opts.Name != "" {
nameReplacements[key] = opts.Name
}
}
// rename the servers if marked to do so
for old, new := range nameReplacements {
servers[new] = servers[old]
delete(servers, old)
}
return nil
}
-94
View File
@@ -1,94 +0,0 @@
package httpcaddyfile
import (
"regexp"
"strings"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
)
type ComplexShorthandReplacer struct {
search *regexp.Regexp
replace string
}
type ShorthandReplacer struct {
complex []ComplexShorthandReplacer
simple *strings.Replacer
}
func NewShorthandReplacer() ShorthandReplacer {
// replace shorthand placeholders (which are convenient
// when writing a Caddyfile) with their actual placeholder
// identifiers or variable names
replacer := strings.NewReplacer(placeholderShorthands()...)
// these are placeholders that allow a user-defined final
// parameters, but we still want to provide a shorthand
// for those, so we use a regexp to replace
regexpReplacements := []ComplexShorthandReplacer{
{regexp.MustCompile(`{header\.([\w-]*)}`), "{http.request.header.$1}"},
{regexp.MustCompile(`{cookie\.([\w-]*)}`), "{http.request.cookie.$1}"},
{regexp.MustCompile(`{labels\.([\w-]*)}`), "{http.request.host.labels.$1}"},
{regexp.MustCompile(`{path\.([\w-]*)}`), "{http.request.uri.path.$1}"},
{regexp.MustCompile(`{file\.([\w-]*)}`), "{http.request.uri.path.file.$1}"},
{regexp.MustCompile(`{query\.([\w-]*)}`), "{http.request.uri.query.$1}"},
{regexp.MustCompile(`{re\.([\w-\.]*)}`), "{http.regexp.$1}"},
{regexp.MustCompile(`{vars\.([\w-]*)}`), "{http.vars.$1}"},
{regexp.MustCompile(`{rp\.([\w-\.]*)}`), "{http.reverse_proxy.$1}"},
{regexp.MustCompile(`{resp\.([\w-\.]*)}`), "{http.intercept.$1}"},
{regexp.MustCompile(`{err\.([\w-\.]*)}`), "{http.error.$1}"},
{regexp.MustCompile(`{file_match\.([\w-]*)}`), "{http.matchers.file.$1}"},
}
return ShorthandReplacer{
complex: regexpReplacements,
simple: replacer,
}
}
// placeholderShorthands returns a slice of old-new string pairs,
// where the left of the pair is a placeholder shorthand that may
// be used in the Caddyfile, and the right is the replacement.
func placeholderShorthands() []string {
return []string{
"{dir}", "{http.request.uri.path.dir}",
"{file}", "{http.request.uri.path.file}",
"{host}", "{http.request.host}",
"{hostport}", "{http.request.hostport}",
"{port}", "{http.request.port}",
"{method}", "{http.request.method}",
"{path}", "{http.request.uri.path}",
"{query}", "{http.request.uri.query}",
"{remote}", "{http.request.remote}",
"{remote_host}", "{http.request.remote.host}",
"{remote_port}", "{http.request.remote.port}",
"{scheme}", "{http.request.scheme}",
"{uri}", "{http.request.uri}",
"{uuid}", "{http.request.uuid}",
"{tls_cipher}", "{http.request.tls.cipher_suite}",
"{tls_version}", "{http.request.tls.version}",
"{tls_client_fingerprint}", "{http.request.tls.client.fingerprint}",
"{tls_client_issuer}", "{http.request.tls.client.issuer}",
"{tls_client_serial}", "{http.request.tls.client.serial}",
"{tls_client_subject}", "{http.request.tls.client.subject}",
"{tls_client_certificate_pem}", "{http.request.tls.client.certificate_pem}",
"{tls_client_certificate_der_base64}", "{http.request.tls.client.certificate_der_base64}",
"{upstream_hostport}", "{http.reverse_proxy.upstream.hostport}",
"{client_ip}", "{http.vars.client_ip}",
}
}
// ApplyToSegment replaces shorthand placeholder to its full placeholder, understandable by Caddy.
func (s ShorthandReplacer) ApplyToSegment(segment *caddyfile.Segment) {
if segment != nil {
for i := 0; i < len(*segment); i++ {
// simple string replacements
(*segment)[i].Text = s.simple.Replace((*segment)[i].Text)
// complex regexp replacements
for _, r := range s.complex {
(*segment)[i].Text = r.search.ReplaceAllString((*segment)[i].Text, r.replace)
}
}
}
}
@@ -1,9 +0,0 @@
(t2) {
respond 200 {
body {args[:]}
}
}
:8082 {
import t2 false
}
@@ -1,9 +0,0 @@
(t1) {
respond 200 {
body {args[:]}
}
}
:8081 {
import t1 false
}
@@ -1,15 +0,0 @@
(t1) {
respond 200 {
body {args[:]}
}
}
:8081 {
import t1 false
}
import import_variadic.txt
:8083 {
import t2 true
}
-722
View File
@@ -1,722 +0,0 @@
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package httpcaddyfile
import (
"bytes"
"encoding/json"
"fmt"
"reflect"
"sort"
"strconv"
"strings"
"github.com/caddyserver/certmagic"
"github.com/mholt/acmez/v2/acme"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
"github.com/caddyserver/caddy/v2/modules/caddytls"
)
func (st ServerType) buildTLSApp(
pairings []sbAddrAssociation,
options map[string]any,
warnings []caddyconfig.Warning,
) (*caddytls.TLS, []caddyconfig.Warning, error) {
tlsApp := &caddytls.TLS{CertificatesRaw: make(caddy.ModuleMap)}
var certLoaders []caddytls.CertificateLoader
httpPort := strconv.Itoa(caddyhttp.DefaultHTTPPort)
if hp, ok := options["http_port"].(int); ok {
httpPort = strconv.Itoa(hp)
}
autoHTTPS := "on"
if ah, ok := options["auto_https"].(string); ok {
autoHTTPS = ah
}
// find all hosts that share a server block with a hostless
// key, so that they don't get forgotten/omitted by auto-HTTPS
// (since they won't appear in route matchers)
httpsHostsSharedWithHostlessKey := make(map[string]struct{})
if autoHTTPS != "off" {
for _, pair := range pairings {
for _, sb := range pair.serverBlocks {
for _, addr := range sb.keys {
if addr.Host == "" {
// this server block has a hostless key, now
// go through and add all the hosts to the set
for _, otherAddr := range sb.keys {
if otherAddr.Original == addr.Original {
continue
}
if otherAddr.Host != "" && otherAddr.Scheme != "http" && otherAddr.Port != httpPort {
httpsHostsSharedWithHostlessKey[otherAddr.Host] = struct{}{}
}
}
break
}
}
}
}
}
// a catch-all automation policy is used as a "default" for all subjects that
// don't have custom configuration explicitly associated with them; this
// is only to add if the global settings or defaults are non-empty
catchAllAP, err := newBaseAutomationPolicy(options, warnings, false)
if err != nil {
return nil, warnings, err
}
if catchAllAP != nil {
if tlsApp.Automation == nil {
tlsApp.Automation = new(caddytls.AutomationConfig)
}
tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, catchAllAP)
}
for _, p := range pairings {
// avoid setting up TLS automation policies for a server that is HTTP-only
if !listenersUseAnyPortOtherThan(p.addresses, httpPort) {
continue
}
for _, sblock := range p.serverBlocks {
// check the scheme of all the site addresses,
// skip building AP if they all had http://
if sblock.isAllHTTP() {
continue
}
// get values that populate an automation policy for this block
ap, err := newBaseAutomationPolicy(options, warnings, true)
if err != nil {
return nil, warnings, err
}
sblockHosts := sblock.hostsFromKeys(false)
if len(sblockHosts) == 0 && catchAllAP != nil {
ap = catchAllAP
}
// on-demand tls
if _, ok := sblock.pile["tls.on_demand"]; ok {
ap.OnDemand = true
}
// reuse private keys tls
if _, ok := sblock.pile["tls.reuse_private_keys"]; ok {
ap.ReusePrivateKeys = true
}
if keyTypeVals, ok := sblock.pile["tls.key_type"]; ok {
ap.KeyType = keyTypeVals[0].Value.(string)
}
// certificate issuers
if issuerVals, ok := sblock.pile["tls.cert_issuer"]; ok {
var issuers []certmagic.Issuer
for _, issuerVal := range issuerVals {
issuers = append(issuers, issuerVal.Value.(certmagic.Issuer))
}
if ap == catchAllAP && !reflect.DeepEqual(ap.Issuers, issuers) {
// this more correctly implements an error check that was removed
// below; try it with this config:
//
// :443 {
// bind 127.0.0.1
// }
//
// :443 {
// bind ::1
// tls {
// issuer acme
// }
// }
return nil, warnings, fmt.Errorf("automation policy from site block is also default/catch-all policy because of key without hostname, and the two are in conflict: %#v != %#v", ap.Issuers, issuers)
}
ap.Issuers = issuers
}
// certificate managers
if certManagerVals, ok := sblock.pile["tls.cert_manager"]; ok {
for _, certManager := range certManagerVals {
certGetterName := certManager.Value.(caddy.Module).CaddyModule().ID.Name()
ap.ManagersRaw = append(ap.ManagersRaw, caddyconfig.JSONModuleObject(certManager.Value, "via", certGetterName, &warnings))
}
}
// custom bind host
for _, cfgVal := range sblock.pile["bind"] {
for _, iss := range ap.Issuers {
// if an issuer was already configured and it is NOT an ACME issuer,
// skip, since we intend to adjust only ACME issuers; ensure we
// include any issuer that embeds/wraps an underlying ACME issuer
var acmeIssuer *caddytls.ACMEIssuer
if acmeWrapper, ok := iss.(acmeCapable); ok {
acmeIssuer = acmeWrapper.GetACMEIssuer()
}
if acmeIssuer == nil {
continue
}
// proceed to configure the ACME issuer's bind host, without
// overwriting any existing settings
if acmeIssuer.Challenges == nil {
acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
}
if acmeIssuer.Challenges.BindHost == "" {
// only binding to one host is supported
var bindHost string
if bindHosts, ok := cfgVal.Value.([]string); ok && len(bindHosts) > 0 {
bindHost = bindHosts[0]
}
acmeIssuer.Challenges.BindHost = bindHost
}
}
}
// we used to ensure this block is allowed to create an automation policy;
// doing so was forbidden if it has a key with no host (i.e. ":443")
// and if there is a different server block that also has a key with no
// host -- since a key with no host matches any host, we need its
// associated automation policy to have an empty Subjects list, i.e. no
// host filter, which is indistinguishable between the two server blocks
// because automation is not done in the context of a particular server...
// this is an example of a poor mapping from Caddyfile to JSON but that's
// the least-leaky abstraction I could figure out -- however, this check
// was preventing certain listeners, like those provided by plugins, from
// being used as desired (see the Tailscale listener plugin), so I removed
// the check: and I think since I originally wrote the check I added a new
// check above which *properly* detects this ambiguity without breaking the
// listener plugin; see the check above with a commented example config
if len(sblockHosts) == 0 && catchAllAP == nil {
// this server block has a key with no hosts, but there is not yet
// a catch-all automation policy (probably because no global options
// were set), so this one becomes it
catchAllAP = ap
}
// associate our new automation policy with this server block's hosts
ap.SubjectsRaw = sblock.hostsFromKeysNotHTTP(httpPort)
sort.Strings(ap.SubjectsRaw) // solely for deterministic test results
// if a combination of public and internal names were given
// for this same server block and no issuer was specified, we
// need to separate them out in the automation policies so
// that the internal names can use the internal issuer and
// the other names can use the default/public/ACME issuer
var ap2 *caddytls.AutomationPolicy
if len(ap.Issuers) == 0 {
var internal, external []string
for _, s := range ap.SubjectsRaw {
// do not create Issuers for Tailscale domains; they will be given a Manager instead
if isTailscaleDomain(s) {
continue
}
if !certmagic.SubjectQualifiesForCert(s) {
return nil, warnings, fmt.Errorf("subject does not qualify for certificate: '%s'", s)
}
// we don't use certmagic.SubjectQualifiesForPublicCert() because of one nuance:
// names like *.*.tld that may not qualify for a public certificate are actually
// fine when used with OnDemand, since OnDemand (currently) does not obtain
// wildcards (if it ever does, there will be a separate config option to enable
// it that we would need to check here) since the hostname is known at handshake;
// and it is unexpected to switch to internal issuer when the user wants to get
// regular certificates on-demand for a class of certs like *.*.tld.
if subjectQualifiesForPublicCert(ap, s) {
external = append(external, s)
} else {
internal = append(internal, s)
}
}
if len(external) > 0 && len(internal) > 0 {
ap.SubjectsRaw = external
apCopy := *ap
ap2 = &apCopy
ap2.SubjectsRaw = internal
ap2.IssuersRaw = []json.RawMessage{caddyconfig.JSONModuleObject(caddytls.InternalIssuer{}, "module", "internal", &warnings)}
}
}
if tlsApp.Automation == nil {
tlsApp.Automation = new(caddytls.AutomationConfig)
}
tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, ap)
if ap2 != nil {
tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, ap2)
}
// certificate loaders
if clVals, ok := sblock.pile["tls.cert_loader"]; ok {
for _, clVal := range clVals {
certLoaders = append(certLoaders, clVal.Value.(caddytls.CertificateLoader))
}
}
}
}
// group certificate loaders by module name, then add to config
if len(certLoaders) > 0 {
loadersByName := make(map[string]caddytls.CertificateLoader)
for _, cl := range certLoaders {
name := caddy.GetModuleName(cl)
// ugh... technically, we may have multiple FileLoader and FolderLoader
// modules (because the tls directive returns one per occurrence), but
// the config structure expects only one instance of each kind of loader
// module, so we have to combine them... instead of enumerating each
// possible cert loader module in a type switch, we can use reflection,
// which works on any cert loaders that are slice types
if reflect.TypeOf(cl).Kind() == reflect.Slice {
combined := reflect.ValueOf(loadersByName[name])
if !combined.IsValid() {
combined = reflect.New(reflect.TypeOf(cl)).Elem()
}
clVal := reflect.ValueOf(cl)
for i := 0; i < clVal.Len(); i++ {
combined = reflect.Append(combined, clVal.Index(i))
}
loadersByName[name] = combined.Interface().(caddytls.CertificateLoader)
}
}
for certLoaderName, loaders := range loadersByName {
tlsApp.CertificatesRaw[certLoaderName] = caddyconfig.JSON(loaders, &warnings)
}
}
// set any of the on-demand options, for if/when on-demand TLS is enabled
if onDemand, ok := options["on_demand_tls"].(*caddytls.OnDemandConfig); ok {
if tlsApp.Automation == nil {
tlsApp.Automation = new(caddytls.AutomationConfig)
}
tlsApp.Automation.OnDemand = onDemand
}
// set the storage clean interval if configured
if storageCleanInterval, ok := options["storage_clean_interval"].(caddy.Duration); ok {
if tlsApp.Automation == nil {
tlsApp.Automation = new(caddytls.AutomationConfig)
}
tlsApp.Automation.StorageCleanInterval = storageCleanInterval
}
// set the expired certificates renew interval if configured
if renewCheckInterval, ok := options["renew_interval"].(caddy.Duration); ok {
if tlsApp.Automation == nil {
tlsApp.Automation = new(caddytls.AutomationConfig)
}
tlsApp.Automation.RenewCheckInterval = renewCheckInterval
}
// set the OCSP check interval if configured
if ocspCheckInterval, ok := options["ocsp_interval"].(caddy.Duration); ok {
if tlsApp.Automation == nil {
tlsApp.Automation = new(caddytls.AutomationConfig)
}
tlsApp.Automation.OCSPCheckInterval = ocspCheckInterval
}
// set whether OCSP stapling should be disabled for manually-managed certificates
if ocspConfig, ok := options["ocsp_stapling"].(certmagic.OCSPConfig); ok {
tlsApp.DisableOCSPStapling = ocspConfig.DisableStapling
}
// if any hostnames appear on the same server block as a key with
// no host, they will not be used with route matchers because the
// hostless key matches all hosts, therefore, it wouldn't be
// considered for auto-HTTPS, so we need to make sure those hosts
// are manually considered for managed certificates; we also need
// to make sure that any of these names which are internal-only
// get internal certificates by default rather than ACME
var al caddytls.AutomateLoader
internalAP := &caddytls.AutomationPolicy{
IssuersRaw: []json.RawMessage{json.RawMessage(`{"module":"internal"}`)},
}
if autoHTTPS != "off" && autoHTTPS != "disable_certs" {
for h := range httpsHostsSharedWithHostlessKey {
al = append(al, h)
if !certmagic.SubjectQualifiesForPublicCert(h) {
internalAP.SubjectsRaw = append(internalAP.SubjectsRaw, h)
}
}
}
if len(al) > 0 {
tlsApp.CertificatesRaw["automate"] = caddyconfig.JSON(al, &warnings)
}
if len(internalAP.SubjectsRaw) > 0 {
if tlsApp.Automation == nil {
tlsApp.Automation = new(caddytls.AutomationConfig)
}
tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, internalAP)
}
// if there are any global options set for issuers (ACME ones in particular), make sure they
// take effect in every automation policy that does not have any issuers
if tlsApp.Automation != nil {
globalEmail := options["email"]
globalACMECA := options["acme_ca"]
globalACMECARoot := options["acme_ca_root"]
globalACMEDNS := options["acme_dns"]
globalACMEEAB := options["acme_eab"]
globalPreferredChains := options["preferred_chains"]
hasGlobalACMEDefaults := globalEmail != nil || globalACMECA != nil || globalACMECARoot != nil || globalACMEDNS != nil || globalACMEEAB != nil || globalPreferredChains != nil
if hasGlobalACMEDefaults {
for i := 0; i < len(tlsApp.Automation.Policies); i++ {
ap := tlsApp.Automation.Policies[i]
if len(ap.Issuers) == 0 && automationPolicyHasAllPublicNames(ap) {
// for public names, create default issuers which will later be filled in with configured global defaults
// (internal names will implicitly use the internal issuer at auto-https time)
emailStr, _ := globalEmail.(string)
ap.Issuers = caddytls.DefaultIssuers(emailStr)
// if a specific endpoint is configured, can't use multiple default issuers
if globalACMECA != nil {
ap.Issuers = []certmagic.Issuer{new(caddytls.ACMEIssuer)}
}
}
}
}
}
// finalize and verify policies; do cleanup
if tlsApp.Automation != nil {
for i, ap := range tlsApp.Automation.Policies {
// ensure all issuers have global defaults filled in
for j, issuer := range ap.Issuers {
err := fillInGlobalACMEDefaults(issuer, options)
if err != nil {
return nil, warnings, fmt.Errorf("filling in global issuer defaults for AP %d, issuer %d: %v", i, j, err)
}
}
// encode all issuer values we created, so they will be rendered in the output
if len(ap.Issuers) > 0 && ap.IssuersRaw == nil {
for _, iss := range ap.Issuers {
issuerName := iss.(caddy.Module).CaddyModule().ID.Name()
ap.IssuersRaw = append(ap.IssuersRaw, caddyconfig.JSONModuleObject(iss, "module", issuerName, &warnings))
}
}
}
// consolidate automation policies that are the exact same
tlsApp.Automation.Policies = consolidateAutomationPolicies(tlsApp.Automation.Policies)
// ensure automation policies don't overlap subjects (this should be
// an error at provision-time as well, but catch it in the adapt phase
// for convenience)
automationHostSet := make(map[string]struct{})
for _, ap := range tlsApp.Automation.Policies {
for _, s := range ap.SubjectsRaw {
if _, ok := automationHostSet[s]; ok {
return nil, warnings, fmt.Errorf("hostname appears in more than one automation policy, making certificate management ambiguous: %s", s)
}
automationHostSet[s] = struct{}{}
}
}
// if nothing remains, remove any excess values to clean up the resulting config
if len(tlsApp.Automation.Policies) == 0 {
tlsApp.Automation.Policies = nil
}
if reflect.DeepEqual(tlsApp.Automation, new(caddytls.AutomationConfig)) {
tlsApp.Automation = nil
}
}
return tlsApp, warnings, nil
}
type acmeCapable interface{ GetACMEIssuer() *caddytls.ACMEIssuer }
func fillInGlobalACMEDefaults(issuer certmagic.Issuer, options map[string]any) error {
acmeWrapper, ok := issuer.(acmeCapable)
if !ok {
return nil
}
acmeIssuer := acmeWrapper.GetACMEIssuer()
if acmeIssuer == nil {
return nil
}
globalEmail := options["email"]
globalACMECA := options["acme_ca"]
globalACMECARoot := options["acme_ca_root"]
globalACMEDNS := options["acme_dns"]
globalACMEEAB := options["acme_eab"]
globalPreferredChains := options["preferred_chains"]
globalCertLifetime := options["cert_lifetime"]
globalHTTPPort, globalHTTPSPort := options["http_port"], options["https_port"]
if globalEmail != nil && acmeIssuer.Email == "" {
acmeIssuer.Email = globalEmail.(string)
}
if globalACMECA != nil && acmeIssuer.CA == "" {
acmeIssuer.CA = globalACMECA.(string)
}
if globalACMECARoot != nil && !sliceContains(acmeIssuer.TrustedRootsPEMFiles, globalACMECARoot.(string)) {
acmeIssuer.TrustedRootsPEMFiles = append(acmeIssuer.TrustedRootsPEMFiles, globalACMECARoot.(string))
}
if globalACMEDNS != nil && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.DNS == nil) {
acmeIssuer.Challenges = &caddytls.ChallengesConfig{
DNS: &caddytls.DNSChallengeConfig{
ProviderRaw: caddyconfig.JSONModuleObject(globalACMEDNS, "name", globalACMEDNS.(caddy.Module).CaddyModule().ID.Name(), nil),
},
}
}
if globalACMEEAB != nil && acmeIssuer.ExternalAccount == nil {
acmeIssuer.ExternalAccount = globalACMEEAB.(*acme.EAB)
}
if globalPreferredChains != nil && acmeIssuer.PreferredChains == nil {
acmeIssuer.PreferredChains = globalPreferredChains.(*caddytls.ChainPreference)
}
if globalHTTPPort != nil && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.HTTP == nil || acmeIssuer.Challenges.HTTP.AlternatePort == 0) {
if acmeIssuer.Challenges == nil {
acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
}
if acmeIssuer.Challenges.HTTP == nil {
acmeIssuer.Challenges.HTTP = new(caddytls.HTTPChallengeConfig)
}
acmeIssuer.Challenges.HTTP.AlternatePort = globalHTTPPort.(int)
}
if globalHTTPSPort != nil && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.TLSALPN == nil || acmeIssuer.Challenges.TLSALPN.AlternatePort == 0) {
if acmeIssuer.Challenges == nil {
acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
}
if acmeIssuer.Challenges.TLSALPN == nil {
acmeIssuer.Challenges.TLSALPN = new(caddytls.TLSALPNChallengeConfig)
}
acmeIssuer.Challenges.TLSALPN.AlternatePort = globalHTTPSPort.(int)
}
if globalCertLifetime != nil && acmeIssuer.CertificateLifetime == 0 {
acmeIssuer.CertificateLifetime = globalCertLifetime.(caddy.Duration)
}
return nil
}
// newBaseAutomationPolicy returns a new TLS automation policy that gets
// its values from the global options map. It should be used as the base
// for any other automation policies. A nil policy (and no error) will be
// returned if there are no default/global options. However, if always is
// true, a non-nil value will always be returned (unless there is an error).
func newBaseAutomationPolicy(
options map[string]any,
_ []caddyconfig.Warning,
always bool,
) (*caddytls.AutomationPolicy, error) {
issuers, hasIssuers := options["cert_issuer"]
_, hasLocalCerts := options["local_certs"]
keyType, hasKeyType := options["key_type"]
ocspStapling, hasOCSPStapling := options["ocsp_stapling"]
hasGlobalAutomationOpts := hasIssuers || hasLocalCerts || hasKeyType || hasOCSPStapling
// if there are no global options related to automation policies
// set, then we can just return right away
if !hasGlobalAutomationOpts {
if always {
return new(caddytls.AutomationPolicy), nil
}
return nil, nil
}
ap := new(caddytls.AutomationPolicy)
if hasKeyType {
ap.KeyType = keyType.(string)
}
if hasIssuers && hasLocalCerts {
return nil, fmt.Errorf("global options are ambiguous: local_certs is confusing when combined with cert_issuer, because local_certs is also a specific kind of issuer")
}
if hasIssuers {
ap.Issuers = issuers.([]certmagic.Issuer)
} else if hasLocalCerts {
ap.Issuers = []certmagic.Issuer{new(caddytls.InternalIssuer)}
}
if hasOCSPStapling {
ocspConfig := ocspStapling.(certmagic.OCSPConfig)
ap.DisableOCSPStapling = ocspConfig.DisableStapling
ap.OCSPOverrides = ocspConfig.ResponderOverrides
}
return ap, nil
}
// consolidateAutomationPolicies combines automation policies that are the same,
// for a cleaner overall output.
func consolidateAutomationPolicies(aps []*caddytls.AutomationPolicy) []*caddytls.AutomationPolicy {
// sort from most specific to least specific; we depend on this ordering
sort.SliceStable(aps, func(i, j int) bool {
if automationPolicyIsSubset(aps[i], aps[j]) {
return true
}
if automationPolicyIsSubset(aps[j], aps[i]) {
return false
}
return len(aps[i].SubjectsRaw) > len(aps[j].SubjectsRaw)
})
emptyAPCount := 0
origLenAPs := len(aps)
// compute the number of empty policies (disregarding subjects) - see #4128
emptyAP := new(caddytls.AutomationPolicy)
for i := 0; i < len(aps); i++ {
emptyAP.SubjectsRaw = aps[i].SubjectsRaw
if reflect.DeepEqual(aps[i], emptyAP) {
emptyAPCount++
if !automationPolicyHasAllPublicNames(aps[i]) {
// if this automation policy has internal names, we might as well remove it
// so auto-https can implicitly use the internal issuer
aps = append(aps[:i], aps[i+1:]...)
i--
}
}
}
// If all policies are empty, we can return nil, as there is no need to set any policy
if emptyAPCount == origLenAPs {
return nil
}
// remove or combine duplicate policies
outer:
for i := 0; i < len(aps); i++ {
// compare only with next policies; we sorted by specificity so we must not delete earlier policies
for j := i + 1; j < len(aps); j++ {
// if they're exactly equal in every way, just keep one of them
if reflect.DeepEqual(aps[i], aps[j]) {
aps = append(aps[:j], aps[j+1:]...)
// must re-evaluate current i against next j; can't skip it!
// even if i decrements to -1, will be incremented to 0 immediately
i--
continue outer
}
// if the policy is the same, we can keep just one, but we have
// to be careful which one we keep; if only one has any hostnames
// defined, then we need to keep the one without any hostnames,
// otherwise the one without any subjects (a catch-all) would be
// eaten up by the one with subjects; and if both have subjects, we
// need to combine their lists
if reflect.DeepEqual(aps[i].IssuersRaw, aps[j].IssuersRaw) &&
reflect.DeepEqual(aps[i].ManagersRaw, aps[j].ManagersRaw) &&
bytes.Equal(aps[i].StorageRaw, aps[j].StorageRaw) &&
aps[i].MustStaple == aps[j].MustStaple &&
aps[i].KeyType == aps[j].KeyType &&
aps[i].OnDemand == aps[j].OnDemand &&
aps[i].ReusePrivateKeys == aps[j].ReusePrivateKeys &&
aps[i].RenewalWindowRatio == aps[j].RenewalWindowRatio {
if len(aps[i].SubjectsRaw) > 0 && len(aps[j].SubjectsRaw) == 0 {
// later policy (at j) has no subjects ("catch-all"), so we can
// remove the identical-but-more-specific policy that comes first
// AS LONG AS it is not shadowed by another policy before it; e.g.
// if policy i is for example.com, policy i+1 is '*.com', and policy
// j is catch-all, we cannot remove policy i because that would
// cause example.com to be served by the less specific policy for
// '*.com', which might be different (yes we've seen this happen)
if automationPolicyShadows(i, aps) >= j {
aps = append(aps[:i], aps[i+1:]...)
i--
continue outer
}
} else {
// avoid repeated subjects
for _, subj := range aps[j].SubjectsRaw {
if !sliceContains(aps[i].SubjectsRaw, subj) {
aps[i].SubjectsRaw = append(aps[i].SubjectsRaw, subj)
}
}
aps = append(aps[:j], aps[j+1:]...)
j--
}
}
}
}
return aps
}
// automationPolicyIsSubset returns true if a's subjects are a subset
// of b's subjects.
func automationPolicyIsSubset(a, b *caddytls.AutomationPolicy) bool {
if len(b.SubjectsRaw) == 0 {
return true
}
if len(a.SubjectsRaw) == 0 {
return false
}
for _, aSubj := range a.SubjectsRaw {
var inSuperset bool
for _, bSubj := range b.SubjectsRaw {
if certmagic.MatchWildcard(aSubj, bSubj) {
inSuperset = true
break
}
}
if !inSuperset {
return false
}
}
return true
}
// automationPolicyShadows returns the index of a policy that aps[i] shadows;
// in other words, for all policies after position i, if that policy covers
// the same subjects but is less specific, that policy's position is returned,
// or -1 if no shadowing is found. For example, if policy i is for
// "foo.example.com" and policy i+2 is for "*.example.com", then i+2 will be
// returned, since that policy is shadowed by i, which is in front.
func automationPolicyShadows(i int, aps []*caddytls.AutomationPolicy) int {
for j := i + 1; j < len(aps); j++ {
if automationPolicyIsSubset(aps[i], aps[j]) {
return j
}
}
return -1
}
// subjectQualifiesForPublicCert is like certmagic.SubjectQualifiesForPublicCert() except
// that this allows domains with multiple wildcard levels like '*.*.example.com' to qualify
// if the automation policy has OnDemand enabled (i.e. this function is more lenient).
//
// IP subjects are considered as non-qualifying for public certs. Technically, there are
// now public ACME CAs as well as non-ACME CAs that issue IP certificates. But this function
// is used solely for implicit automation (defaults), where it gets really complicated to
// keep track of which issuers support IP certificates in which circumstances. Currently,
// issuers that support IP certificates are very few, and all require some sort of config
// from the user anyway (such as an account credential). Since we cannot implicitly and
// automatically get public IP certs without configuration from the user, we treat IPs as
// not qualifying for public certificates. Users should expressly configure an issuer
// that supports IP certs for that purpose.
func subjectQualifiesForPublicCert(ap *caddytls.AutomationPolicy, subj string) bool {
return !certmagic.SubjectIsIP(subj) &&
!certmagic.SubjectIsInternal(subj) &&
(strings.Count(subj, "*.") < 2 || ap.OnDemand)
}
// automationPolicyHasAllPublicNames returns true if all the names on the policy
// do NOT qualify for public certs OR are tailscale domains.
func automationPolicyHasAllPublicNames(ap *caddytls.AutomationPolicy) bool {
for _, subj := range ap.SubjectsRaw {
if !subjectQualifiesForPublicCert(ap, subj) || isTailscaleDomain(subj) {
return false
}
}
return true
}
func isTailscaleDomain(name string) bool {
return strings.HasSuffix(strings.ToLower(name), ".ts.net")
}
-56
View File
@@ -1,56 +0,0 @@
package httpcaddyfile
import (
"testing"
"github.com/caddyserver/caddy/v2/modules/caddytls"
)
func TestAutomationPolicyIsSubset(t *testing.T) {
for i, test := range []struct {
a, b []string
expect bool
}{
{
a: []string{"example.com"},
b: []string{},
expect: true,
},
{
a: []string{},
b: []string{"example.com"},
expect: false,
},
{
a: []string{"foo.example.com"},
b: []string{"*.example.com"},
expect: true,
},
{
a: []string{"foo.example.com"},
b: []string{"foo.example.com"},
expect: true,
},
{
a: []string{"foo.example.com"},
b: []string{"example.com"},
expect: false,
},
{
a: []string{"example.com", "foo.example.com"},
b: []string{"*.com", "*.*.com"},
expect: true,
},
{
a: []string{"example.com", "foo.example.com"},
b: []string{"*.com"},
expect: false,
},
} {
apA := &caddytls.AutomationPolicy{SubjectsRaw: test.a}
apB := &caddytls.AutomationPolicy{SubjectsRaw: test.b}
if actual := automationPolicyIsSubset(apA, apB); actual != test.expect {
t.Errorf("Test %d: Expected %t but got %t (A: %v B: %v)", i, test.expect, actual, test.a, test.b)
}
}
}
-218
View File
@@ -1,218 +0,0 @@
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package caddyconfig
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io"
"net/http"
"os"
"time"
"github.com/caddyserver/caddy/v2"
)
func init() {
caddy.RegisterModule(HTTPLoader{})
}
// HTTPLoader can load Caddy configs over HTTP(S).
//
// If the response is not a JSON config, a config adapter must be specified
// either in the loader config (`adapter`), or in the Content-Type HTTP header
// returned in the HTTP response from the server. The Content-Type header is
// read just like the admin API's `/load` endpoint. Uf you don't have control
// over the HTTP server (but can still trust its response), you can override
// the Content-Type header by setting the `adapter` property in this config.
type HTTPLoader struct {
// The method for the request. Default: GET
Method string `json:"method,omitempty"`
// The URL of the request.
URL string `json:"url,omitempty"`
// HTTP headers to add to the request.
Headers http.Header `json:"header,omitempty"`
// Maximum time allowed for a complete connection and request.
Timeout caddy.Duration `json:"timeout,omitempty"`
// The name of the config adapter to use, if any. Only needed
// if the HTTP response is not a JSON config and if the server's
// Content-Type header is missing or incorrect.
Adapter string `json:"adapter,omitempty"`
TLS *struct {
// Present this instance's managed remote identity credentials to the server.
UseServerIdentity bool `json:"use_server_identity,omitempty"`
// PEM-encoded client certificate filename to present to the server.
ClientCertificateFile string `json:"client_certificate_file,omitempty"`
// PEM-encoded key to use with the client certificate.
ClientCertificateKeyFile string `json:"client_certificate_key_file,omitempty"`
// List of PEM-encoded CA certificate files to add to the same trust
// store as RootCAPool (or root_ca_pool in the JSON).
RootCAPEMFiles []string `json:"root_ca_pem_files,omitempty"`
} `json:"tls,omitempty"`
}
// CaddyModule returns the Caddy module information.
func (HTTPLoader) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
ID: "caddy.config_loaders.http",
New: func() caddy.Module { return new(HTTPLoader) },
}
}
// LoadConfig loads a Caddy config.
func (hl HTTPLoader) LoadConfig(ctx caddy.Context) ([]byte, error) {
repl := caddy.NewReplacer()
client, err := hl.makeClient(ctx)
if err != nil {
return nil, err
}
method := repl.ReplaceAll(hl.Method, "")
if method == "" {
method = http.MethodGet
}
url := repl.ReplaceAll(hl.URL, "")
req, err := http.NewRequest(method, url, nil)
if err != nil {
return nil, err
}
for key, vals := range hl.Headers {
for _, val := range vals {
req.Header.Add(repl.ReplaceAll(key, ""), repl.ReplaceKnown(val, ""))
}
}
resp, err := doHttpCallWithRetries(ctx, client, req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
return nil, fmt.Errorf("server responded with HTTP %d", resp.StatusCode)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
// adapt the config based on either manually-configured adapter or server's response header
ct := resp.Header.Get("Content-Type")
if hl.Adapter != "" {
ct = "text/" + hl.Adapter
}
result, warnings, err := adaptByContentType(ct, body)
if err != nil {
return nil, err
}
for _, warn := range warnings {
ctx.Logger().Warn(warn.String())
}
return result, nil
}
func attemptHttpCall(client *http.Client, request *http.Request) (*http.Response, error) {
resp, err := client.Do(request)
if err != nil {
return nil, fmt.Errorf("problem calling http loader url: %v", err)
} else if resp.StatusCode < 200 || resp.StatusCode > 499 {
resp.Body.Close()
return nil, fmt.Errorf("bad response status code from http loader url: %v", resp.StatusCode)
}
return resp, nil
}
func doHttpCallWithRetries(ctx caddy.Context, client *http.Client, request *http.Request) (*http.Response, error) {
var resp *http.Response
var err error
const maxAttempts = 10
for i := 0; i < maxAttempts; i++ {
resp, err = attemptHttpCall(client, request)
if err != nil && i < maxAttempts-1 {
select {
case <-time.After(time.Millisecond * 500):
case <-ctx.Done():
return resp, ctx.Err()
}
} else {
break
}
}
return resp, err
}
func (hl HTTPLoader) makeClient(ctx caddy.Context) (*http.Client, error) {
client := &http.Client{
Timeout: time.Duration(hl.Timeout),
}
if hl.TLS != nil {
var tlsConfig *tls.Config
// client authentication
if hl.TLS.UseServerIdentity {
certs, err := ctx.IdentityCredentials(ctx.Logger())
if err != nil {
return nil, fmt.Errorf("getting server identity credentials: %v", err)
}
// See https://github.com/securego/gosec/issues/1054#issuecomment-2072235199
//nolint:gosec
tlsConfig = &tls.Config{Certificates: certs}
} else if hl.TLS.ClientCertificateFile != "" && hl.TLS.ClientCertificateKeyFile != "" {
cert, err := tls.LoadX509KeyPair(hl.TLS.ClientCertificateFile, hl.TLS.ClientCertificateKeyFile)
if err != nil {
return nil, err
}
//nolint:gosec
tlsConfig = &tls.Config{Certificates: []tls.Certificate{cert}}
}
// trusted server certs
if len(hl.TLS.RootCAPEMFiles) > 0 {
rootPool := x509.NewCertPool()
for _, pemFile := range hl.TLS.RootCAPEMFiles {
pemData, err := os.ReadFile(pemFile)
if err != nil {
return nil, fmt.Errorf("failed reading ca cert: %v", err)
}
rootPool.AppendCertsFromPEM(pemData)
}
if tlsConfig == nil {
tlsConfig = new(tls.Config)
}
tlsConfig.RootCAs = rootPool
}
client.Transport = &http.Transport{TLSClientConfig: tlsConfig}
}
return client, nil
}
var _ caddy.ConfigLoader = (*HTTPLoader)(nil)
-214
View File
@@ -1,214 +0,0 @@
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package caddyconfig
import (
"bytes"
"encoding/json"
"fmt"
"io"
"mime"
"net/http"
"strings"
"sync"
"github.com/caddyserver/caddy/v2"
)
func init() {
caddy.RegisterModule(adminLoad{})
}
// adminLoad is a module that provides the /load endpoint
// for the Caddy admin API. The only reason it's not baked
// into the caddy package directly is because of the import
// of the caddyconfig package for its GetAdapter function.
// If the caddy package depends on the caddyconfig package,
// then the caddyconfig package will not be able to import
// the caddy package, and it can more easily cause backward
// edges in the dependency tree (i.e. import cycle).
// Fortunately, the admin API has first-class support for
// adding endpoints from modules.
type adminLoad struct{}
// CaddyModule returns the Caddy module information.
func (adminLoad) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
ID: "admin.api.load",
New: func() caddy.Module { return new(adminLoad) },
}
}
// Routes returns a route for the /load endpoint.
func (al adminLoad) Routes() []caddy.AdminRoute {
return []caddy.AdminRoute{
{
Pattern: "/load",
Handler: caddy.AdminHandlerFunc(al.handleLoad),
},
{
Pattern: "/adapt",
Handler: caddy.AdminHandlerFunc(al.handleAdapt),
},
}
}
// handleLoad replaces the entire current configuration with
// a new one provided in the response body. It supports config
// adapters through the use of the Content-Type header. A
// config that is identical to the currently-running config
// will be a no-op unless Cache-Control: must-revalidate is set.
func (adminLoad) handleLoad(w http.ResponseWriter, r *http.Request) error {
if r.Method != http.MethodPost {
return caddy.APIError{
HTTPStatus: http.StatusMethodNotAllowed,
Err: fmt.Errorf("method not allowed"),
}
}
buf := bufPool.Get().(*bytes.Buffer)
buf.Reset()
defer bufPool.Put(buf)
_, err := io.Copy(buf, r.Body)
if err != nil {
return caddy.APIError{
HTTPStatus: http.StatusBadRequest,
Err: fmt.Errorf("reading request body: %v", err),
}
}
body := buf.Bytes()
// if the config is formatted other than Caddy's native
// JSON, we need to adapt it before loading it
if ctHeader := r.Header.Get("Content-Type"); ctHeader != "" {
result, warnings, err := adaptByContentType(ctHeader, body)
if err != nil {
return caddy.APIError{
HTTPStatus: http.StatusBadRequest,
Err: err,
}
}
if len(warnings) > 0 {
respBody, err := json.Marshal(warnings)
if err != nil {
caddy.Log().Named("admin.api.load").Error(err.Error())
}
_, _ = w.Write(respBody)
}
body = result
}
forceReload := r.Header.Get("Cache-Control") == "must-revalidate"
err = caddy.Load(body, forceReload)
if err != nil {
return caddy.APIError{
HTTPStatus: http.StatusBadRequest,
Err: fmt.Errorf("loading config: %v", err),
}
}
caddy.Log().Named("admin.api").Info("load complete")
return nil
}
// handleAdapt adapts the given Caddy config to JSON and responds with the result.
func (adminLoad) handleAdapt(w http.ResponseWriter, r *http.Request) error {
if r.Method != http.MethodPost {
return caddy.APIError{
HTTPStatus: http.StatusMethodNotAllowed,
Err: fmt.Errorf("method not allowed"),
}
}
buf := bufPool.Get().(*bytes.Buffer)
buf.Reset()
defer bufPool.Put(buf)
_, err := io.Copy(buf, r.Body)
if err != nil {
return caddy.APIError{
HTTPStatus: http.StatusBadRequest,
Err: fmt.Errorf("reading request body: %v", err),
}
}
result, warnings, err := adaptByContentType(r.Header.Get("Content-Type"), buf.Bytes())
if err != nil {
return caddy.APIError{
HTTPStatus: http.StatusBadRequest,
Err: err,
}
}
out := struct {
Warnings []Warning `json:"warnings,omitempty"`
Result json.RawMessage `json:"result"`
}{
Warnings: warnings,
Result: result,
}
w.Header().Set("Content-Type", "application/json")
return json.NewEncoder(w).Encode(out)
}
// adaptByContentType adapts body to Caddy JSON using the adapter specified by contentType.
// If contentType is empty or ends with "/json", the input will be returned, as a no-op.
func adaptByContentType(contentType string, body []byte) ([]byte, []Warning, error) {
// assume JSON as the default
if contentType == "" {
return body, nil, nil
}
ct, _, err := mime.ParseMediaType(contentType)
if err != nil {
return nil, nil, caddy.APIError{
HTTPStatus: http.StatusBadRequest,
Err: fmt.Errorf("invalid Content-Type: %v", err),
}
}
// if already JSON, no need to adapt
if strings.HasSuffix(ct, "/json") {
return body, nil, nil
}
// adapter name should be suffix of MIME type
_, adapterName, slashFound := strings.Cut(ct, "/")
if !slashFound {
return nil, nil, fmt.Errorf("malformed Content-Type")
}
cfgAdapter := GetAdapter(adapterName)
if cfgAdapter == nil {
return nil, nil, fmt.Errorf("unrecognized config adapter '%s'", adapterName)
}
result, warnings, err := cfgAdapter.Adapt(body, nil)
if err != nil {
return nil, nil, fmt.Errorf("adapting config using %s adapter: %v", adapterName, err)
}
return result, warnings, nil
}
var bufPool = sync.Pool{
New: func() any {
return new(bytes.Buffer)
},
}
+246
View File
@@ -0,0 +1,246 @@
package caddyfile
import (
"errors"
"fmt"
"io"
"strings"
)
// Dispenser is a type that dispenses tokens, similarly to a lexer,
// except that it can do so with some notion of structure and has
// some really convenient methods.
type Dispenser struct {
filename string
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: tokens,
cursor: -1,
}
}
// NewDispenserTokens returns a Dispenser filled with the given tokens.
func NewDispenserTokens(filename string, tokens []Token) Dispenser {
return Dispenser{
filename: filename,
tokens: tokens,
cursor: -1,
}
}
// Next loads the next token. Returns true if a token
// was loaded; false otherwise. If false, all tokens
// have been consumed.
func (d *Dispenser) Next() bool {
if d.cursor < len(d.tokens)-1 {
d.cursor++
return true
}
return false
}
// NextArg loads the next token if it is on the same
// line. Returns true if a token was loaded; false
// otherwise. If false, all tokens on the line have
// been consumed. It handles imported tokens correctly.
func (d *Dispenser) NextArg() bool {
if d.cursor < 0 {
d.cursor++
return true
}
if d.cursor >= len(d.tokens) {
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.cursor++
return true
}
return false
}
// NextLine loads the next token only if it is not on the same
// line as the current token, and returns true if a token was
// loaded; false otherwise. If false, there is not another token
// or it is on the same line. It handles imported tokens correctly.
func (d *Dispenser) NextLine() bool {
if d.cursor < 0 {
d.cursor++
return true
}
if d.cursor >= len(d.tokens) {
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.cursor++
return true
}
return false
}
// NextBlock can be used as the condition of a for loop
// to load the next token as long as it opens a block or
// is already in a block. It returns true if a token was
// loaded, or false when the block's closing curly brace
// was loaded and thus the block ended. Nested blocks are
// not supported.
func (d *Dispenser) NextBlock() bool {
if d.nesting > 0 {
d.Next()
if d.Val() == "}" {
d.nesting--
return false
}
return true
}
if !d.NextArg() { // block must open on same line
return false
}
if d.Val() != "{" {
d.cursor-- // roll back if not opening brace
return false
}
d.Next()
if d.Val() == "}" {
// Open and then closed right away
return false
}
d.nesting++
return true
}
// 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
}
// Line gets the line number of the current token. If there is no token
// loaded, it returns 0.
func (d *Dispenser) Line() int {
if d.cursor < 0 || d.cursor >= len(d.tokens) {
return 0
}
return d.tokens[d.cursor].Line
}
// File gets the filename of the current token. If there is no token loaded,
// it returns the filename originally given when parsing started.
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 != "" {
return tokenFilename
}
return d.filename
}
// Args is a convenience function that loads the next arguments
// (tokens on the same line) into an arbitrary number of strings
// pointed to in targets. If there are fewer tokens available
// than string pointers, the remaining strings will not be changed
// and false will be returned. If there were enough tokens available
// to fill the arguments, then true will be returned.
func (d *Dispenser) Args(targets ...*string) bool {
enough := true
for i := 0; i < len(targets); i++ {
if !d.NextArg() {
enough = false
break
}
*targets[i] = d.Val()
}
return enough
}
// RemainingArgs loads any more arguments (tokens on the same line)
// into a slice and returns them. Open curly brace tokens also indicate
// the end of arguments, and the curly brace is not included in
// the return value nor is it loaded.
func (d *Dispenser) RemainingArgs() []string {
var args []string
for d.NextArg() {
if d.Val() == "{" {
d.cursor--
break
}
args = append(args, d.Val())
}
return args
}
// ArgErr returns an argument error, meaning that another
// argument was expected but not found. In other words,
// a line break or open curly brace was encountered instead of
// an argument.
func (d *Dispenser) ArgErr() error {
if d.Val() == "{" {
return d.Err("Unexpected token '{', expecting argument")
}
return d.Errf("Wrong argument count or unexpected line ending after '%s'", d.Val())
}
// SyntaxErr creates a generic syntax error which explains what was
// found and what was expected.
func (d *Dispenser) SyntaxErr(expected string) error {
msg := fmt.Sprintf("%s:%d - Syntax error: Unexpected token '%s', expecting '%s'", d.File(), d.Line(), d.Val(), expected)
return errors.New(msg)
}
// EOFErr returns an error indicating that the dispenser reached
// the end of the input when searching for the next token.
func (d *Dispenser) EOFErr() error {
return d.Errf("Unexpected EOF")
}
// Err generates a custom parse 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)
return errors.New(msg)
}
// Errf is like Err, but for formatted error messages
func (d *Dispenser) Errf(format string, args ...interface{}) error {
return d.Err(fmt.Sprintf(format, args...))
}
// numLineBreaks counts how many line breaks are in the token
// value given by the token index tknIdx. It returns 0 if the
// token does not exist or there are no line breaks.
func (d *Dispenser) numLineBreaks(tknIdx int) int {
if tknIdx < 0 || tknIdx >= len(d.tokens) {
return 0
}
return strings.Count(d.tokens[tknIdx].Text, "\n")
}
// isNewLine determines whether the current token is on a different
// line (higher line number) than the previous token. It handles imported
// tokens correctly. If there isn't a previous token, it returns true.
func (d *Dispenser) isNewLine() bool {
if d.cursor < 1 {
return true
}
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
}
@@ -1,21 +1,6 @@
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package caddyfile
import (
"errors"
"reflect"
"strings"
"testing"
@@ -26,7 +11,7 @@ func TestDispenser_Val_Next(t *testing.T) {
dir1 arg1
dir2 arg2 arg3
dir3`
d := NewTestDispenser(input)
d := NewDispenser("Testfile", strings.NewReader(input))
if val := d.Val(); val != "" {
t.Fatalf("Val(): Should return empty string when no token loaded; got '%s'", val)
@@ -64,7 +49,7 @@ func TestDispenser_NextArg(t *testing.T) {
input := `dir1 arg1
dir2 arg2 arg3
dir3`
d := NewTestDispenser(input)
d := NewDispenser("Testfile", strings.NewReader(input))
assertNext := func(shouldLoad bool, expectedVal string, expectedCursor int) {
if d.Next() != shouldLoad {
@@ -111,7 +96,7 @@ func TestDispenser_NextLine(t *testing.T) {
input := `host:port
dir1 arg1
dir2 arg2 arg3`
d := NewTestDispenser(input)
d := NewDispenser("Testfile", strings.NewReader(input))
assertNextLine := func(shouldLoad bool, expectedVal string, expectedCursor int) {
if d.NextLine() != shouldLoad {
@@ -144,10 +129,10 @@ func TestDispenser_NextBlock(t *testing.T) {
}
foobar2 {
}`
d := NewTestDispenser(input)
d := NewDispenser("Testfile", strings.NewReader(input))
assertNextBlock := func(shouldLoad bool, expectedCursor, expectedNesting int) {
if loaded := d.NextBlock(0); loaded != shouldLoad {
if loaded := d.NextBlock(); loaded != shouldLoad {
t.Errorf("NextBlock(): Should return %v but got %v", shouldLoad, loaded)
}
if d.cursor != expectedCursor {
@@ -174,7 +159,7 @@ func TestDispenser_Args(t *testing.T) {
dir2 arg4 arg5
dir3 arg6 arg7
dir4`
d := NewTestDispenser(input)
d := NewDispenser("Testfile", strings.NewReader(input))
d.Next() // dir1
@@ -241,7 +226,7 @@ func TestDispenser_RemainingArgs(t *testing.T) {
dir2 arg4 arg5
dir3 arg6 { arg7
dir4`
d := NewTestDispenser(input)
d := NewDispenser("Testfile", strings.NewReader(input))
d.Next() // dir1
@@ -278,7 +263,7 @@ func TestDispenser_ArgErr_Err(t *testing.T) {
input := `dir1 {
}
dir2 arg1 arg2`
d := NewTestDispenser(input)
d := NewDispenser("Testfile", strings.NewReader(input))
d.cursor = 1 // {
@@ -304,10 +289,4 @@ func TestDispenser_ArgErr_Err(t *testing.T) {
if !strings.Contains(err.Error(), "foobar") {
t.Errorf("Expected error message with custom message in it ('foobar'); got '%v'", err)
}
ErrBarIsFull := errors.New("bar is full")
bookingError := d.Errf("unable to reserve: %w", ErrBarIsFull)
if !errors.Is(bookingError, ErrBarIsFull) {
t.Errorf("Errf(): should be able to unwrap the error chain")
}
}
+184
View File
@@ -0,0 +1,184 @@
package caddyfile
import (
"bytes"
"encoding/json"
"fmt"
"sort"
"strconv"
"strings"
)
const filename = "Caddyfile"
// ToJSON converts caddyfile to its JSON representation.
func ToJSON(caddyfile []byte) ([]byte, error) {
var j EncodedCaddyfile
serverBlocks, err := Parse(filename, bytes.NewReader(caddyfile), nil)
if err != nil {
return nil, err
}
for _, sb := range serverBlocks {
block := EncodedServerBlock{
Keys: sb.Keys,
Body: [][]interface{}{},
}
// Extract directives deterministically by sorting them
var directives = make([]string, len(sb.Tokens))
for dir := range sb.Tokens {
directives = append(directives, dir)
}
sort.Strings(directives)
// Convert each directive's tokens into our JSON structure
for _, dir := range directives {
disp := NewDispenserTokens(filename, sb.Tokens[dir])
for disp.Next() {
block.Body = append(block.Body, constructLine(&disp))
}
}
// tack this block onto the end of the list
j = append(j, block)
}
result, err := json.Marshal(j)
if err != nil {
return nil, err
}
return result, nil
}
// constructLine transforms tokens into a JSON-encodable structure;
// 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.
func constructLine(d *Dispenser) []interface{} {
var args []interface{}
args = append(args, d.Val())
for d.NextArg() {
if d.Val() == "{" {
args = append(args, constructBlock(d))
continue
}
args = append(args, d.Val())
}
return args
}
// constructBlock recursively processes tokens into a
// 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() {
if d.Val() == "}" {
break
}
block = append(block, constructLine(d))
}
return block
}
// FromJSON converts JSON-encoded jsonBytes to Caddyfile text
func FromJSON(jsonBytes []byte) ([]byte, error) {
var j EncodedCaddyfile
var result string
err := json.Unmarshal(jsonBytes, &j)
if err != nil {
return nil, err
}
for sbPos, sb := range j {
if sbPos > 0 {
result += "\n\n"
}
for i, key := range sb.Keys {
if i > 0 {
result += ", "
}
//result += standardizeScheme(key)
result += key
}
result += jsonToText(sb.Body, 1)
}
return []byte(result), nil
}
// jsonToText recursively transforms a scope of JSON into plain
// Caddyfile text.
func jsonToText(scope interface{}, depth int) string {
var result string
switch val := scope.(type) {
case string:
if strings.ContainsAny(val, "\" \n\t\r") {
result += `"` + strings.Replace(val, "\"", "\\\"", -1) + `"`
} else {
result += val
}
case int:
result += strconv.Itoa(val)
case float64:
result += fmt.Sprintf("%v", val)
case bool:
result += fmt.Sprintf("%t", val)
case [][]interface{}:
result += " {\n"
for _, arg := range val {
result += strings.Repeat("\t", depth) + jsonToText(arg, depth+1) + "\n"
}
result += strings.Repeat("\t", depth-1) + "}"
case []interface{}:
for i, v := range val {
if block, ok := v.([]interface{}); ok {
result += "{\n"
for _, arg := range block {
result += strings.Repeat("\t", depth) + jsonToText(arg, depth+1) + "\n"
}
result += strings.Repeat("\t", depth-1) + "}"
continue
}
result += jsonToText(v, depth)
if i < len(val)-1 {
result += " "
}
}
}
return result
}
// 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"`
}
+164
View File
@@ -0,0 +1,164 @@
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)
}
}
*/
+136
View File
@@ -0,0 +1,136 @@
package caddyfile
import (
"bufio"
"io"
"unicode"
)
type (
// lexer is a utility which can get values, token by
// token, from a Reader. A token is a word, and tokens
// are separated by whitespace. A word can be enclosed
// in quotes if it contains whitespace.
lexer struct {
reader *bufio.Reader
token Token
line int
}
// 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
}
// next loads the next token into the lexer.
// A token is delimited by whitespace, unless
// the token starts with a quotes character (")
// in which case the token goes until the closing
// quotes (the enclosing quotes are not included).
// Inside quoted strings, quotes may be escaped
// with a preceding \ character. No other chars
// may be escaped. The rest of the line is skipped
// if a "#" character is read in. Returns true if
// a token was loaded; false otherwise.
func (l *lexer) next() bool {
var val []rune
var comment, quoted, escaped bool
makeToken := func() bool {
l.token.Text = string(val)
return true
}
for {
ch, _, err := l.reader.ReadRune()
if err != nil {
if len(val) > 0 {
return makeToken()
}
if err == io.EOF {
return false
}
panic(err)
}
if quoted {
if !escaped {
if ch == '\\' {
escaped = true
continue
} else if ch == '"' {
quoted = false
return makeToken()
}
}
if ch == '\n' {
l.line++
}
if escaped {
// only escape quotes
if ch != '"' {
val = append(val, '\\')
}
}
val = append(val, ch)
escaped = false
continue
}
if unicode.IsSpace(ch) {
if ch == '\r' {
continue
}
if ch == '\n' {
l.line++
comment = false
}
if len(val) > 0 {
return makeToken()
}
continue
}
if ch == '#' {
comment = true
}
if comment {
continue
}
if len(val) == 0 {
l.token = Token{Line: l.line}
if ch == '"' {
quoted = true
continue
}
}
val = append(val, ch)
}
}
+171
View File
@@ -0,0 +1,171 @@
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
}
}
}
+416
View File
@@ -0,0 +1,416 @@
package caddyfile
import (
"io"
"log"
"os"
"path/filepath"
"strings"
)
// 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
}
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
}
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)")
}
// make path relative to Caddyfile rather than current working directory (issue #867)
// and then use glob to get list of matching filenames
absFile, err := filepath.Abs(p.Dispenser.filename)
if err != nil {
return p.Errf("Failed to get absolute path of file: %s", p.Dispenser.filename)
}
var matches []string
var globPattern string
if !filepath.IsAbs(importPattern) {
globPattern = filepath.Join(filepath.Dir(absFile), importPattern)
} else {
globPattern = importPattern
}
matches, err = filepath.Glob(globPattern)
if err != nil {
return p.Errf("Failed to use import pattern %s: %v", importPattern, err)
}
if len(matches) == 0 {
if strings.Contains(globPattern, "*") {
log.Printf("[WARNING] No files matching import pattern: %s", importPattern)
} else {
return p.Errf("File to import not found: %s", importPattern)
}
}
// splice out the import directive and its argument (2 tokens total)
tokensBefore := p.tokens[:p.cursor-1]
tokensAfter := p.tokens[p.cursor+1:]
// collect all the imported tokens
var importedTokens []Token
for _, importFile := range matches {
newTokens, err := p.doSingleImport(importFile)
if err != nil {
return err
}
var importLine int
importDir := filepath.Dir(importFile)
for i, token := range newTokens {
if token.Text == "import" {
importLine = token.Line
continue
}
if token.Line == importLine {
var abs string
if filepath.IsAbs(token.Text) {
abs = token.Text
} else if !filepath.IsAbs(importFile) {
abs = filepath.Join(filepath.Dir(absFile), token.Text)
} else {
abs = filepath.Join(importDir, token.Text)
}
newTokens[i] = Token{
Text: abs,
Line: token.Line,
File: token.File,
}
}
}
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 filename onto these tokens so errors show the imported file's name
filename := filepath.Base(importFile)
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 := 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])
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
}
// 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, 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
}
// 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
}
+502
View File
@@ -0,0 +1,502 @@
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{}},
} {
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 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")
// 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)
}
// 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)
}
}
func testParser(input string) parser {
buf := strings.NewReader(input)
p := parser{Dispenser: NewDispenser("Caddyfile", buf)}
return p
}
+171
View File
@@ -0,0 +1,171 @@
// 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
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))
}
}
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+"\"")
return http.StatusUnauthorized, nil
}
// 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
}
}
+182
View File
@@ -0,0 +1,182 @@
package basicauth
import (
"encoding/base64"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"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
}
tests := []testType{
{"/testing", http.StatusOK, "okuser", "okpass"},
{"/testing", http.StatusUnauthorized, "baduser", "okpass"},
{"/testing", http.StatusUnauthorized, "okuser", "badpass"},
{"/testing", http.StatusUnauthorized, "OKuser", "okpass"},
{"/testing", http.StatusUnauthorized, "OKuser", "badPASS"},
{"/testing", http.StatusUnauthorized, "", "okpass"},
{"/testing", http.StatusUnauthorized, "okuser", ""},
{"/testing", http.StatusUnauthorized, "", ""},
}
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 {
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
}{
{"/t", http.StatusOK, "t:p1"},
{"/t/t", http.StatusOK, "t:p1"},
{"/t/t", http.StatusOK, "t1:p2"},
{"/a", http.StatusOK, "t1:p2"},
{"/t/t", http.StatusUnauthorized, "t1:p3"},
{"/t", http.StatusUnauthorized, "t1:p2"},
}
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 {
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.Skipf("Error creating temp file (%v), 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)
}
}
}
+99
View File
@@ -0,0 +1,99 @@
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)
}
+164
View File
@@ -0,0 +1,164 @@
package basicauth
import (
"fmt"
"io/ioutil"
"os"
"strings"
"testing"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
)
func TestSetup(t *testing.T) {
c := caddy.NewTestController("http", `basicauth user pwd`)
err := setup(c)
if err != nil {
t.Errorf("Expected no errors, but got: %v", err)
}
mids := httpserver.GetConfig(c).Middleware()
if len(mids) == 0 {
t.Fatal("Expected middleware, got 0 instead")
}
handler := mids[0](httpserver.EmptyNext)
myHandler, ok := handler.(BasicAuth)
if !ok {
t.Fatalf("Expected handler to be type BasicAuth, got: %#v", handler)
}
if !httpserver.SameNext(myHandler.Next, httpserver.EmptyNext) {
t.Error("'Next' field of handler was not set properly")
}
}
func TestBasicAuthParse(t *testing.T) {
htpasswdPasswd := "IedFOuGmTpT8"
htpasswdFile := `sha1:{SHA}dcAUljwz99qFjYR0YLTXx0RqLww=
md5:$apr1$l42y8rex$pOA2VJ0x/0TwaFeAF9nX61`
var skipHtpassword bool
htfh, err := ioutil.TempFile(".", "basicauth-")
if err != nil {
t.Logf("Error creating temp file (%v), will skip htpassword test", err)
skipHtpassword = true
} else {
if _, err = htfh.Write([]byte(htpasswdFile)); err != nil {
t.Fatalf("write htpasswd file %q: %v", htfh.Name(), err)
}
htfh.Close()
defer os.Remove(htfh.Name())
}
tests := []struct {
input string
shouldErr bool
password string
expected []Rule
}{
{`basicauth user pwd`, false, "pwd", []Rule{
{Username: "user"},
}},
{`basicauth user pwd {
}`, false, "pwd", []Rule{
{Username: "user"},
}},
{`basicauth /resource1 user pwd {
}`, false, "pwd", []Rule{
{Username: "user", Resources: []string{"/resource1"}},
}},
{`basicauth /resource1 user pwd {
realm Resources
}`, false, "pwd", []Rule{
{Username: "user", Resources: []string{"/resource1"}, Realm: "Resources"},
}},
{`basicauth user pwd {
/resource1
/resource2
}`, false, "pwd", []Rule{
{Username: "user", Resources: []string{"/resource1", "/resource2"}},
}},
{`basicauth user pwd {
/resource1
/resource2
realm "Secure resources"
}`, false, "pwd", []Rule{
{Username: "user", Resources: []string{"/resource1", "/resource2"}, Realm: "Secure resources"},
}},
{`basicauth user pwd {
/resource1
realm "Secure resources"
realm Extra
/resource2
}`, true, "pwd", []Rule{}},
{`basicauth user pwd {
/resource1
foo "Resources"
/resource2
}`, true, "pwd", []Rule{}},
{`basicauth /resource user pwd`, false, "pwd", []Rule{
{Username: "user", Resources: []string{"/resource"}},
}},
{`basicauth /res1 user1 pwd1
basicauth /res2 user2 pwd2`, false, "pwd", []Rule{
{Username: "user1", Resources: []string{"/res1"}},
{Username: "user2", Resources: []string{"/res2"}},
}},
{`basicauth user`, true, "", []Rule{}},
{`basicauth`, true, "", []Rule{}},
{`basicauth /resource user pwd asdf`, true, "", []Rule{}},
{`basicauth sha1 htpasswd=` + htfh.Name(), false, htpasswdPasswd, []Rule{
{Username: "sha1"},
}},
}
for i, test := range tests {
actual, err := basicAuthParse(caddy.NewTestController("http", test.input))
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.Username != expectedRule.Username {
t.Errorf("Test %d, rule %d: Expected username '%s', got '%s'",
i, j, expectedRule.Username, actualRule.Username)
}
if actualRule.Realm != expectedRule.Realm {
t.Errorf("Test %d, rule %d: Expected realm '%s', got '%s'",
i, j, expectedRule.Realm, actualRule.Realm)
}
if strings.Contains(test.input, "htpasswd=") && skipHtpassword {
continue
}
pwd := test.password
if len(actual) > 1 {
pwd = fmt.Sprintf("%s%d", pwd, j+1)
}
if !actualRule.Password(pwd) || actualRule.Password(test.password+"!") {
t.Errorf("Test %d, rule %d: Expected password '%v', got '%v'",
i, j, test.password, actualRule.Password(""))
}
expectedRes := fmt.Sprintf("%v", expectedRule.Resources)
actualRes := fmt.Sprintf("%v", actualRule.Resources)
if actualRes != expectedRes {
t.Errorf("Test %d, rule %d: Expected resource list %s, but got %s",
i, j, expectedRes, actualRes)
}
}
}
}
+24
View File
@@ -0,0 +1,24 @@
package bind
import (
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
)
func init() {
caddy.RegisterPlugin("bind", caddy.Plugin{
ServerType: "http",
Action: setupBind,
})
}
func setupBind(c *caddy.Controller) error {
config := httpserver.GetConfig(c)
for c.Next() {
if !c.Args(&config.ListenHost) {
return c.ArgErr()
}
config.TLS.ListenHost = config.ListenHost // necessary for ACME challenges, see issue #309
}
return nil
}
+24
View File
@@ -0,0 +1,24 @@
package bind
import (
"testing"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
)
func TestSetupBind(t *testing.T) {
c := caddy.NewTestController("http", `bind 1.2.3.4`)
err := setupBind(c)
if err != nil {
t.Fatalf("Expected no errors, but got: %v", err)
}
cfg := httpserver.GetConfig(c)
if got, want := cfg.ListenHost, "1.2.3.4"; got != want {
t.Errorf("Expected the config's ListenHost to be %s, was %s", want, got)
}
if got, want := cfg.TLS.ListenHost, "1.2.3.4"; got != want {
t.Errorf("Expected the TLS config's ListenHost to be %s, was %s", want, got)
}
}
+512
View File
@@ -0,0 +1,512 @@
// Package browse provides middleware for listing files in a directory
// when directory path is requested instead of a specific file.
package browse
import (
"bytes"
"encoding/json"
"net/http"
"net/url"
"os"
"path"
"path/filepath"
"sort"
"strconv"
"strings"
"text/template"
"time"
"github.com/dustin/go-humanize"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/mholt/caddy/caddyhttp/staticfiles"
)
const (
sortByName = "name"
sortByNameDirFirst = "namedirfirst"
sortBySize = "size"
sortByTime = "time"
)
// Browse is an http.Handler that can show a file listing when
// directories in the given paths are specified.
type Browse struct {
Next httpserver.Handler
Configs []Config
IgnoreIndexes bool
}
// Config is a configuration for browsing in a particular path.
type Config struct {
PathScope string // the base path the URL must match to enable browsing
Fs staticfiles.FileServer
Variables interface{}
Template *template.Template
}
// A Listing is the context used to fill out a template.
type Listing struct {
// The name of the directory (the last element of the path)
Name string
// The full path of the request
Path string
// Whether the parent directory is browsable
CanGoUp bool
// The items (files and folders) in the path
Items []FileInfo
// The number of directories in the listing
NumDirs int
// The number of files (items that aren't directories) in the listing
NumFiles int
// Which sorting order is used
Sort string
// And which order
Order string
// If ≠0 then Items have been limited to that many elements
ItemsLimitedTo int
// Optional custom variables for use in browse templates
User interface{}
httpserver.Context
}
// Crumb represents part of a breadcrumb menu.
type Crumb struct {
Link, Text string
}
// Breadcrumbs returns l.Path where every element maps
// the link to the text to display.
func (l Listing) Breadcrumbs() []Crumb {
var result []Crumb
if len(l.Path) == 0 {
return result
}
// skip trailing slash
lpath := l.Path
if lpath[len(lpath)-1] == '/' {
lpath = lpath[:len(lpath)-1]
}
parts := strings.Split(lpath, "/")
for i := range parts {
txt := parts[i]
if i == 0 && parts[i] == "" {
txt = "/"
}
result = append(result, Crumb{Link: strings.Repeat("../", len(parts)-i-1), Text: txt})
}
return result
}
// FileInfo is the info about a particular file or directory
type FileInfo struct {
Name string
Size int64
URL string
ModTime time.Time
Mode os.FileMode
IsDir bool
IsSymlink bool
}
// HumanSize returns the size of the file as a human-readable string
// in IEC format (i.e. power of 2 or base 1024).
func (fi FileInfo) HumanSize() string {
return humanize.IBytes(uint64(fi.Size))
}
// HumanModTime returns the modified time of the file as a human-readable string.
func (fi FileInfo) HumanModTime(format string) string {
return fi.ModTime.Format(format)
}
// Implement sorting for Listing
type byName Listing
type byNameDirFirst Listing
type bySize Listing
type byTime Listing
// By Name
func (l byName) Len() int { return len(l.Items) }
func (l byName) Swap(i, j int) { l.Items[i], l.Items[j] = l.Items[j], l.Items[i] }
// Treat upper and lower case equally
func (l byName) Less(i, j int) bool {
return strings.ToLower(l.Items[i].Name) < strings.ToLower(l.Items[j].Name)
}
// By Name Dir First
func (l byNameDirFirst) Len() int { return len(l.Items) }
func (l byNameDirFirst) Swap(i, j int) { l.Items[i], l.Items[j] = l.Items[j], l.Items[i] }
// Treat upper and lower case equally
func (l byNameDirFirst) Less(i, j int) bool {
// if both are dir or file sort normally
if l.Items[i].IsDir == l.Items[j].IsDir {
return strings.ToLower(l.Items[i].Name) < strings.ToLower(l.Items[j].Name)
}
// always sort dir ahead of file
return l.Items[i].IsDir
}
// By Size
func (l bySize) Len() int { return len(l.Items) }
func (l bySize) Swap(i, j int) { l.Items[i], l.Items[j] = l.Items[j], l.Items[i] }
const directoryOffset = -1 << 31 // = math.MinInt32
func (l bySize) Less(i, j int) bool {
iSize, jSize := l.Items[i].Size, l.Items[j].Size
// Directory sizes depend on the filesystem implementation,
// which is opaque to a visitor, and should indeed does not change if the operator choses to change the fs.
// For a consistent user experience directories are pulled to the front…
if l.Items[i].IsDir {
iSize = directoryOffset
}
if l.Items[j].IsDir {
jSize = directoryOffset
}
// … and sorted by name.
if l.Items[i].IsDir && l.Items[j].IsDir {
return strings.ToLower(l.Items[i].Name) < strings.ToLower(l.Items[j].Name)
}
return iSize < jSize
}
// By Time
func (l byTime) Len() int { return len(l.Items) }
func (l byTime) Swap(i, j int) { l.Items[i], l.Items[j] = l.Items[j], l.Items[i] }
func (l byTime) Less(i, j int) bool { return l.Items[i].ModTime.Before(l.Items[j].ModTime) }
// Add sorting method to "Listing"
// it will apply what's in ".Sort" and ".Order"
func (l Listing) applySort() {
// Check '.Order' to know how to sort
if l.Order == "desc" {
switch l.Sort {
case sortByName:
sort.Sort(sort.Reverse(byName(l)))
case sortByNameDirFirst:
sort.Sort(sort.Reverse(byNameDirFirst(l)))
case sortBySize:
sort.Sort(sort.Reverse(bySize(l)))
case sortByTime:
sort.Sort(sort.Reverse(byTime(l)))
default:
// If not one of the above, do nothing
return
}
} else { // If we had more Orderings we could add them here
switch l.Sort {
case sortByName:
sort.Sort(byName(l))
case sortByNameDirFirst:
sort.Sort(byNameDirFirst(l))
case sortBySize:
sort.Sort(bySize(l))
case sortByTime:
sort.Sort(byTime(l))
default:
// If not one of the above, do nothing
return
}
}
}
func directoryListing(files []os.FileInfo, canGoUp bool, urlPath string, config *Config) (Listing, bool) {
var (
fileinfos []FileInfo
dirCount, fileCount int
hasIndexFile bool
)
for _, f := range files {
name := f.Name()
for _, indexName := range staticfiles.IndexPages {
if name == indexName {
hasIndexFile = true
break
}
}
if f.IsDir() {
name += "/"
dirCount++
} else {
fileCount++
}
if config.Fs.IsHidden(f) {
continue
}
url := url.URL{Path: "./" + name} // prepend with "./" to fix paths with ':' in the name
fileinfos = append(fileinfos, FileInfo{
IsDir: f.IsDir() || isSymlinkTargetDir(f, urlPath, config),
IsSymlink: isSymlink(f),
Name: f.Name(),
Size: f.Size(),
URL: url.String(),
ModTime: f.ModTime().UTC(),
Mode: f.Mode(),
})
}
return Listing{
Name: path.Base(urlPath),
Path: urlPath,
CanGoUp: canGoUp,
Items: fileinfos,
NumDirs: dirCount,
NumFiles: fileCount,
}, hasIndexFile
}
// isSymlink return true if f is a symbolic link
func isSymlink(f os.FileInfo) bool {
return f.Mode()&os.ModeSymlink != 0
}
// isSymlinkTargetDir return true if f's symbolic link target
// is a directory. Return false if not a symbolic link.
func isSymlinkTargetDir(f os.FileInfo, urlPath string, config *Config) bool {
if !isSymlink(f) {
return false
}
fullPath := func(fileName string) string {
fullPath := filepath.Join(string(config.Fs.Root.(http.Dir)), urlPath, fileName)
return filepath.Clean(fullPath)
}
target, err := os.Readlink(fullPath(f.Name()))
if err != nil {
return false
}
targetInfo, err := os.Lstat(fullPath(target))
if err != nil {
return false
}
return targetInfo.IsDir()
}
// ServeHTTP determines if the request is for this plugin, and if all prerequisites are met.
// If so, control is handed over to ServeListing.
func (b Browse) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
// See if there's a browse configuration to match the path
var bc *Config
for i := range b.Configs {
if httpserver.Path(r.URL.Path).Matches(b.Configs[i].PathScope) {
bc = &b.Configs[i]
break
}
}
if bc == nil {
return b.Next.ServeHTTP(w, r)
}
// Browse works on existing directories; delegate everything else
requestedFilepath, err := bc.Fs.Root.Open(r.URL.Path)
if err != nil {
switch {
case os.IsPermission(err):
return http.StatusForbidden, err
case os.IsExist(err):
return http.StatusNotFound, err
default:
return b.Next.ServeHTTP(w, r)
}
}
defer requestedFilepath.Close()
info, err := requestedFilepath.Stat()
if err != nil {
switch {
case os.IsPermission(err):
return http.StatusForbidden, err
case os.IsExist(err):
return http.StatusGone, err
default:
return b.Next.ServeHTTP(w, r)
}
}
if !info.IsDir() {
return b.Next.ServeHTTP(w, r)
}
// Do not reply to anything else because it might be nonsensical
switch r.Method {
case http.MethodGet, http.MethodHead:
// proceed, noop
case "PROPFIND", http.MethodOptions:
return http.StatusNotImplemented, nil
default:
return b.Next.ServeHTTP(w, r)
}
// Browsing navigation gets messed up if browsing a directory
// that doesn't end in "/" (which it should, anyway)
u := *r.URL
if u.Path == "" {
u.Path = "/"
}
if u.Path[len(u.Path)-1] != '/' {
u.Path += "/"
http.Redirect(w, r, u.String(), http.StatusMovedPermanently)
return http.StatusMovedPermanently, nil
}
return b.ServeListing(w, r, requestedFilepath, bc)
}
func (b Browse) loadDirectoryContents(requestedFilepath http.File, urlPath string, config *Config) (*Listing, bool, error) {
files, err := requestedFilepath.Readdir(-1)
if err != nil {
return nil, false, err
}
// Determine if user can browse up another folder
var canGoUp bool
curPathDir := path.Dir(strings.TrimSuffix(urlPath, "/"))
for _, other := range b.Configs {
if strings.HasPrefix(curPathDir, other.PathScope) {
canGoUp = true
break
}
}
// Assemble listing of directory contents
listing, hasIndex := directoryListing(files, canGoUp, urlPath, config)
return &listing, hasIndex, nil
}
// handleSortOrder gets and stores for a Listing the 'sort' and 'order',
// and reads 'limit' if given. The latter is 0 if not given.
//
// This sets Cookies.
func (b Browse) handleSortOrder(w http.ResponseWriter, r *http.Request, scope string) (sort string, order string, limit int, err error) {
sort, order, limitQuery := r.URL.Query().Get("sort"), r.URL.Query().Get("order"), r.URL.Query().Get("limit")
// If the query 'sort' or 'order' is empty, use defaults or any values previously saved in Cookies
switch sort {
case "":
sort = sortByNameDirFirst
if sortCookie, sortErr := r.Cookie("sort"); sortErr == nil {
sort = sortCookie.Value
}
case sortByName, sortByNameDirFirst, sortBySize, sortByTime:
http.SetCookie(w, &http.Cookie{Name: "sort", Value: sort, Path: scope, Secure: r.TLS != nil})
}
switch order {
case "":
order = "asc"
if orderCookie, orderErr := r.Cookie("order"); orderErr == nil {
order = orderCookie.Value
}
case "asc", "desc":
http.SetCookie(w, &http.Cookie{Name: "order", Value: order, Path: scope, Secure: r.TLS != nil})
}
if limitQuery != "" {
limit, err = strconv.Atoi(limitQuery)
if err != nil { // if the 'limit' query can't be interpreted as a number, return err
return
}
}
return
}
// ServeListing returns a formatted view of 'requestedFilepath' contents'.
func (b Browse) ServeListing(w http.ResponseWriter, r *http.Request, requestedFilepath http.File, bc *Config) (int, error) {
listing, containsIndex, err := b.loadDirectoryContents(requestedFilepath, r.URL.Path, bc)
if err != nil {
switch {
case os.IsPermission(err):
return http.StatusForbidden, err
case os.IsExist(err):
return http.StatusGone, err
default:
return http.StatusInternalServerError, err
}
}
if containsIndex && !b.IgnoreIndexes { // directory isn't browsable
return b.Next.ServeHTTP(w, r)
}
listing.Context = httpserver.Context{
Root: bc.Fs.Root,
Req: r,
URL: r.URL,
}
listing.User = bc.Variables
// Copy the query values into the Listing struct
var limit int
listing.Sort, listing.Order, limit, err = b.handleSortOrder(w, r, bc.PathScope)
if err != nil {
return http.StatusBadRequest, err
}
listing.applySort()
if limit > 0 && limit <= len(listing.Items) {
listing.Items = listing.Items[:limit]
listing.ItemsLimitedTo = limit
}
var buf *bytes.Buffer
acceptHeader := strings.ToLower(strings.Join(r.Header["Accept"], ","))
switch {
case strings.Contains(acceptHeader, "application/json"):
if buf, err = b.formatAsJSON(listing, bc); err != nil {
return http.StatusInternalServerError, err
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
default: // There's no 'application/json' in the 'Accept' header; browse normally
if buf, err = b.formatAsHTML(listing, bc); err != nil {
return http.StatusInternalServerError, err
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
}
buf.WriteTo(w)
return http.StatusOK, nil
}
func (b Browse) formatAsJSON(listing *Listing, bc *Config) (*bytes.Buffer, error) {
marsh, err := json.Marshal(listing.Items)
if err != nil {
return nil, err
}
buf := new(bytes.Buffer)
_, err = buf.Write(marsh)
return buf, err
}
func (b Browse) formatAsHTML(listing *Listing, bc *Config) (*bytes.Buffer, error) {
buf := new(bytes.Buffer)
err := bc.Template.Execute(buf, listing)
return buf, err
}
+455
View File
@@ -0,0 +1,455 @@
package browse
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"sort"
"testing"
"text/template"
"time"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/mholt/caddy/caddyhttp/staticfiles"
)
func TestSort(t *testing.T) {
// making up []fileInfo with bogus values;
// to be used to make up our "listing"
fileInfos := []FileInfo{
{
Name: "fizz",
Size: 4,
ModTime: time.Now().AddDate(-1, 1, 0),
},
{
Name: "buzz",
Size: 2,
ModTime: time.Now().AddDate(0, -3, 3),
},
{
Name: "bazz",
Size: 1,
ModTime: time.Now().AddDate(0, -2, -23),
},
{
Name: "jazz",
Size: 3,
ModTime: time.Now(),
},
}
listing := Listing{
Name: "foobar",
Path: "/fizz/buzz",
CanGoUp: false,
Items: fileInfos,
}
// sort by name
listing.Sort = "name"
listing.applySort()
if !sort.IsSorted(byName(listing)) {
t.Errorf("The listing isn't name sorted: %v", listing.Items)
}
// sort by size
listing.Sort = "size"
listing.applySort()
if !sort.IsSorted(bySize(listing)) {
t.Errorf("The listing isn't size sorted: %v", listing.Items)
}
// sort by Time
listing.Sort = "time"
listing.applySort()
if !sort.IsSorted(byTime(listing)) {
t.Errorf("The listing isn't time sorted: %v", listing.Items)
}
// sort by name dir first
listing.Sort = "namedirfirst"
listing.applySort()
if !sort.IsSorted(byNameDirFirst(listing)) {
t.Errorf("The listing isn't namedirfirst sorted: %v", listing.Items)
}
// reverse by name
listing.Sort = "name"
listing.Order = "desc"
listing.applySort()
if !isReversed(byName(listing)) {
t.Errorf("The listing isn't reversed by name: %v", listing.Items)
}
// reverse by size
listing.Sort = "size"
listing.Order = "desc"
listing.applySort()
if !isReversed(bySize(listing)) {
t.Errorf("The listing isn't reversed by size: %v", listing.Items)
}
// reverse by time
listing.Sort = "time"
listing.Order = "desc"
listing.applySort()
if !isReversed(byTime(listing)) {
t.Errorf("The listing isn't reversed by time: %v", listing.Items)
}
// reverse by name dir first
listing.Sort = "namedirfirst"
listing.Order = "desc"
listing.applySort()
if !isReversed(byNameDirFirst(listing)) {
t.Errorf("The listing isn't reversed by namedirfirst: %v", listing.Items)
}
}
func TestBrowseHTTPMethods(t *testing.T) {
tmpl, err := template.ParseFiles("testdata/photos.tpl")
if err != nil {
t.Fatalf("An error occurred while parsing the template: %v", err)
}
b := Browse{
Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
return http.StatusTeapot, nil // not t.Fatalf, or we will not see what other methods yield
}),
Configs: []Config{
{
PathScope: "/photos",
Fs: staticfiles.FileServer{
Root: http.Dir("./testdata"),
},
Template: tmpl,
},
},
}
rec := httptest.NewRecorder()
for method, expected := range map[string]int{
http.MethodGet: http.StatusOK,
http.MethodHead: http.StatusOK,
http.MethodOptions: http.StatusNotImplemented,
"PROPFIND": http.StatusNotImplemented,
} {
req, err := http.NewRequest(method, "/photos/", nil)
if err != nil {
t.Fatalf("Test: Could not create HTTP request: %v", err)
}
ctx := context.WithValue(req.Context(), httpserver.OriginalURLCtxKey, *req.URL)
req = req.WithContext(ctx)
code, _ := b.ServeHTTP(rec, req)
if code != expected {
t.Errorf("Wrong status with HTTP Method %s: expected %d, got %d", method, expected, code)
}
}
}
func TestBrowseTemplate(t *testing.T) {
tmpl, err := template.ParseFiles("testdata/photos.tpl")
if err != nil {
t.Fatalf("An error occurred while parsing the template: %v", err)
}
b := Browse{
Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
t.Fatalf("Next shouldn't be called")
return 0, nil
}),
Configs: []Config{
{
PathScope: "/photos",
Fs: staticfiles.FileServer{
Root: http.Dir("./testdata"),
Hide: []string{"photos/hidden.html"},
},
Template: tmpl,
},
},
}
req, err := http.NewRequest("GET", "/photos/", nil)
if err != nil {
t.Fatalf("Test: Could not create HTTP request: %v", err)
}
ctx := context.WithValue(req.Context(), httpserver.OriginalURLCtxKey, *req.URL)
req = req.WithContext(ctx)
rec := httptest.NewRecorder()
code, _ := b.ServeHTTP(rec, req)
if code != http.StatusOK {
t.Fatalf("Wrong status, expected %d, got %d", http.StatusOK, code)
}
respBody := rec.Body.String()
expectedBody := `<!DOCTYPE html>
<html>
<head>
<title>Template</title>
</head>
<body>
<h1>Header</h1>
<h1>/photos/</h1>
<a href="./test1/">test1</a><br>
<a href="./test.html">test.html</a><br>
<a href="./test2.html">test2.html</a><br>
<a href="./test3.html">test3.html</a><br>
</body>
</html>
`
if respBody != expectedBody {
t.Fatalf("Expected body: '%v' got: '%v'", expectedBody, respBody)
}
}
func TestBrowseJson(t *testing.T) {
b := Browse{
Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
t.Fatalf("Next shouldn't be called")
return 0, nil
}),
Configs: []Config{
{
PathScope: "/photos/",
Fs: staticfiles.FileServer{
Root: http.Dir("./testdata"),
},
},
},
}
//Getting the listing from the ./testdata/photos, the listing returned will be used to validate test results
testDataPath := filepath.Join("./testdata", "photos")
file, err := os.Open(testDataPath)
if err != nil {
if os.IsPermission(err) {
t.Fatalf("Os Permission Error")
}
}
defer file.Close()
files, err := file.Readdir(-1)
if err != nil {
t.Fatalf("Unable to Read Contents of the directory")
}
var fileinfos []FileInfo
for i, f := range files {
name := f.Name()
// Tests fail in CI environment because all file mod times are the same for
// some reason, making the sorting unpredictable. To hack around this,
// we ensure here that each file has a different mod time.
chTime := f.ModTime().UTC().Add(-(time.Duration(i) * time.Second))
if err := os.Chtimes(filepath.Join(testDataPath, name), chTime, chTime); err != nil {
t.Fatal(err)
}
if f.IsDir() {
name += "/"
}
url := url.URL{Path: "./" + name}
fileinfos = append(fileinfos, FileInfo{
IsDir: f.IsDir(),
Name: f.Name(),
Size: f.Size(),
URL: url.String(),
ModTime: chTime,
Mode: f.Mode(),
})
}
// Test that sort=name returns correct listing.
listing := Listing{Items: fileinfos} // this listing will be used for validation inside the tests
tests := []struct {
QueryURL string
SortBy string
OrderBy string
Limit int
shouldErr bool
expectedResult []FileInfo
}{
//test case 1: testing for default sort and order and without the limit parameter, default sort is by name and the default order is ascending
//without the limit query entire listing will be produced
{"/?sort=name", "", "", -1, false, listing.Items},
//test case 2: limit is set to 1, orderBy and sortBy is default
{"/?limit=1&sort=name", "", "", 1, false, listing.Items[:1]},
//test case 3 : if the listing request is bigger than total size of listing then it should return everything
{"/?limit=100000000&sort=name", "", "", 100000000, false, listing.Items},
//test case 4 : testing for negative limit
{"/?limit=-1&sort=name", "", "", -1, false, listing.Items},
//test case 5 : testing with limit set to -1 and order set to descending
{"/?limit=-1&order=desc&sort=name", "", "desc", -1, false, listing.Items},
//test case 6 : testing with limit set to 2 and order set to descending
{"/?limit=2&order=desc&sort=name", "", "desc", 2, false, listing.Items},
//test case 7 : testing with limit set to 3 and order set to descending
{"/?limit=3&order=desc&sort=name", "", "desc", 3, false, listing.Items},
//test case 8 : testing with limit set to 3 and order set to ascending
{"/?limit=3&order=asc&sort=name", "", "asc", 3, false, listing.Items},
//test case 9 : testing with limit set to 1111111 and order set to ascending
{"/?limit=1111111&order=asc&sort=name", "", "asc", 1111111, false, listing.Items},
//test case 10 : testing with limit set to default and order set to ascending and sorting by size
{"/?order=asc&sort=size&sort=name", "size", "asc", -1, false, listing.Items},
//test case 11 : testing with limit set to default and order set to ascending and sorting by last modified
{"/?order=asc&sort=time&sort=name", "time", "asc", -1, false, listing.Items},
//test case 12 : testing with limit set to 1 and order set to ascending and sorting by last modified
{"/?order=asc&sort=time&limit=1&sort=name", "time", "asc", 1, false, listing.Items},
//test case 13 : testing with limit set to -100 and order set to ascending and sorting by last modified
{"/?order=asc&sort=time&limit=-100&sort=name", "time", "asc", -100, false, listing.Items},
//test case 14 : testing with limit set to -100 and order set to ascending and sorting by size
{"/?order=asc&sort=size&limit=-100&sort=name", "size", "asc", -100, false, listing.Items},
}
for i, test := range tests {
var marsh []byte
req, err := http.NewRequest("GET", "/photos"+test.QueryURL, nil)
if err != nil && !test.shouldErr {
t.Errorf("Test %d errored when making request, but it shouldn't have; got '%v'", i, err)
}
ctx := context.WithValue(req.Context(), httpserver.OriginalURLCtxKey, *req.URL)
req = req.WithContext(ctx)
req.Header.Set("Accept", "application/json")
rec := httptest.NewRecorder()
code, err := b.ServeHTTP(rec, req)
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 code != http.StatusOK {
t.Fatalf("In test %d: Wrong status, expected %d, got %d", i, http.StatusOK, code)
}
if rec.HeaderMap.Get("Content-Type") != "application/json; charset=utf-8" {
t.Fatalf("Expected Content type to be application/json; charset=utf-8, but got %s ", rec.HeaderMap.Get("Content-Type"))
}
actualJSONResponse := rec.Body.String()
copyOflisting := listing
if test.SortBy == "" {
copyOflisting.Sort = "name"
} else {
copyOflisting.Sort = test.SortBy
}
if test.OrderBy == "" {
copyOflisting.Order = "asc"
} else {
copyOflisting.Order = test.OrderBy
}
copyOflisting.applySort()
limit := test.Limit
if limit <= len(copyOflisting.Items) && limit > 0 {
marsh, err = json.Marshal(copyOflisting.Items[:limit])
} else { // if the 'limit' query is empty, or has the wrong value, list everything
marsh, err = json.Marshal(copyOflisting.Items)
}
if err != nil {
t.Fatalf("Unable to Marshal the listing ")
}
expectedJSON := string(marsh)
if actualJSONResponse != expectedJSON {
t.Errorf("JSON response doesn't match the expected for test number %d with sort=%s, order=%s\nExpected response %s\nActual response = %s\n",
i+1, test.SortBy, test.OrderBy, expectedJSON, actualJSONResponse)
}
}
}
// "sort" package has "IsSorted" function, but no "IsReversed";
func isReversed(data sort.Interface) bool {
n := data.Len()
for i := n - 1; i > 0; i-- {
if !data.Less(i, i-1) {
return false
}
}
return true
}
func TestBrowseRedirect(t *testing.T) {
testCases := []struct {
url string
statusCode int
returnCode int
location string
}{
{
"http://www.example.com/photos",
http.StatusMovedPermanently,
http.StatusMovedPermanently,
"http://www.example.com/photos/",
},
{
"/photos",
http.StatusMovedPermanently,
http.StatusMovedPermanently,
"/photos/",
},
}
for i, tc := range testCases {
b := Browse{
Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
t.Fatalf("Test %d - Next shouldn't be called", i)
return 0, nil
}),
Configs: []Config{
{
PathScope: "/photos",
Fs: staticfiles.FileServer{
Root: http.Dir("./testdata"),
},
},
},
}
req, err := http.NewRequest("GET", tc.url, nil)
if err != nil {
t.Fatalf("Test %d - could not create HTTP request: %v", i, err)
}
ctx := context.WithValue(req.Context(), httpserver.OriginalURLCtxKey, *req.URL)
req = req.WithContext(ctx)
rec := httptest.NewRecorder()
returnCode, _ := b.ServeHTTP(rec, req)
if returnCode != tc.returnCode {
t.Fatalf("Test %d - wrong return code, expected %d, got %d",
i, tc.returnCode, returnCode)
}
if got := rec.Code; got != tc.statusCode {
t.Errorf("Test %d - wrong status, expected %d, got %d",
i, tc.statusCode, got)
}
if got := rec.Header().Get("Location"); got != tc.location {
t.Errorf("Test %d - wrong Location header, expected %s, got %s",
i, tc.location, got)
}
}
}
+488
View File
@@ -0,0 +1,488 @@
package browse
import (
"fmt"
"io/ioutil"
"net/http"
"text/template"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/mholt/caddy/caddyhttp/staticfiles"
)
func init() {
caddy.RegisterPlugin("browse", caddy.Plugin{
ServerType: "http",
Action: setup,
})
}
// setup configures a new Browse middleware instance.
func setup(c *caddy.Controller) error {
configs, err := browseParse(c)
if err != nil {
return err
}
b := Browse{
Configs: configs,
IgnoreIndexes: false,
}
httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
b.Next = next
return b
})
return nil
}
func browseParse(c *caddy.Controller) ([]Config, error) {
var configs []Config
cfg := httpserver.GetConfig(c)
appendCfg := func(bc 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 Config
// First argument is directory to allow browsing; default is site root
if c.NextArg() {
bc.PathScope = c.Val()
} else {
bc.PathScope = "/"
}
bc.Fs = staticfiles.FileServer{
Root: http.Dir(cfg.Root),
Hide: cfg.HiddenFiles,
}
// 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>{{html .Name}}</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
* { padding: 0; margin: 0; }
body {
font-family: sans-serif;
text-rendering: optimizespeed;
}
a {
color: #006ed3;
text-decoration: none;
}
a:hover,
h1 a:hover {
color: #319cff;
}
header,
#summary {
padding-left: 5%;
padding-right: 5%;
}
th:first-child,
td:first-child {
padding-left: 5%;
}
th:last-child,
td:last-child {
padding-right: 5%;
}
header {
padding-top: 25px;
padding-bottom: 15px;
background-color: #f2f2f2;
}
h1 {
font-size: 20px;
font-weight: normal;
white-space: nowrap;
overflow-x: hidden;
text-overflow: ellipsis;
color: #999;
}
h1 a {
color: #000;
margin: 0 4px;
}
h1 a:hover {
text-decoration: underline;
}
h1 a:first-child {
margin: 0;
}
main {
display: block;
}
.meta {
font-size: 12px;
font-family: Verdana, sans-serif;
border-bottom: 1px solid #9C9C9C;
padding-top: 10px;
padding-bottom: 10px;
}
.meta-item {
margin-right: 1em;
}
#filter {
padding: 4px;
border: 1px solid #CCC;
}
table {
width: 100%;
border-collapse: collapse;
}
tr {
border-bottom: 1px dashed #dadada;
}
tbody tr:hover {
background-color: #ffffec;
}
th,
td {
text-align: left;
padding: 10px 0;
}
th {
padding-top: 15px;
padding-bottom: 15px;
font-size: 16px;
white-space: nowrap;
}
th a {
color: black;
}
th svg {
vertical-align: middle;
}
td {
font-size: 14px;
}
td:first-child {
width: 50%;
}
th:last-child,
td:last-child {
text-align: right;
}
td:first-child svg {
position: absolute;
}
td .name,
td .goup {
margin-left: 1.75em;
word-break: break-all;
overflow-wrap: break-word;
white-space: pre-wrap;
}
.icon {
margin-right: 5px;
}
.icon.sort {
display: inline-block;
width: 1em;
height: 1em;
position: relative;
top: .2em;
}
.icon.sort .top {
position: absolute;
left: 0;
top: -1px;
}
.icon.sort .bottom {
position: absolute;
bottom: -1px;
left: 0;
}
footer {
padding: 40px 20px;
font-size: 12px;
text-align: center;
}
@media (max-width: 600px) {
.hideable {
display: none;
}
td:first-child {
width: auto;
}
th:nth-child(2),
td:nth-child(2) {
padding-right: 5%;
text-align: right;
}
h1 {
color: #000;
}
h1 a {
margin: 0;
}
#filter {
max-width: 100px;
}
}
</style>
</head>
<body>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="0" width="0" style="position: absolute;">
<defs>
<!-- Folder -->
<g id="folder" fill-rule="nonzero" fill="none">
<path d="M285.22 37.55h-142.6L110.9 0H31.7C14.25 0 0 16.9 0 37.55v75.1h316.92V75.1c0-20.65-14.26-37.55-31.7-37.55z" fill="#FFA000"/>
<path d="M285.22 36H31.7C14.25 36 0 50.28 0 67.74v158.7c0 17.47 14.26 31.75 31.7 31.75H285.2c17.44 0 31.7-14.3 31.7-31.75V67.75c0-17.47-14.26-31.75-31.7-31.75z" fill="#FFCA28"/>
</g>
<g id="folder-shortcut" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="folder-shortcut-group" fill-rule="nonzero">
<g id="folder-shortcut-shape">
<path d="M285.224876,37.5486902 L142.612438,37.5486902 L110.920785,0 L31.6916529,0 C14.2612438,0 0,16.8969106 0,37.5486902 L0,112.646071 L316.916529,112.646071 L316.916529,75.0973805 C316.916529,54.4456008 302.655285,37.5486902 285.224876,37.5486902 Z" id="Shape" fill="#FFA000"></path>
<path d="M285.224876,36 L31.6916529,36 C14.2612438,36 0,50.2838568 0,67.7419039 L0,226.451424 C0,243.909471 14.2612438,258.193328 31.6916529,258.193328 L285.224876,258.193328 C302.655285,258.193328 316.916529,243.909471 316.916529,226.451424 L316.916529,67.7419039 C316.916529,50.2838568 302.655285,36 285.224876,36 Z" id="Shape" fill="#FFCA28"></path>
</g>
<path d="M126.154134,250.559184 C126.850974,251.883673 127.300549,253.006122 127.772602,254.106122 C128.469442,255.206122 128.919016,256.104082 129.638335,257.002041 C130.559962,258.326531 131.728855,259 133.100057,259 C134.493737,259 135.415364,258.55102 136.112204,257.67551 C136.809044,257.002041 137.258619,255.902041 137.258619,254.577551 C137.258619,253.904082 137.258619,252.804082 137.033832,251.457143 C136.786566,249.908163 136.561779,249.032653 136.561779,248.583673 C136.089726,242.814286 135.864939,237.920408 135.864939,233.273469 C135.864939,225.057143 136.786566,217.514286 138.180246,210.846939 C139.798713,204.202041 141.889234,198.634694 144.429328,193.763265 C147.216689,188.869388 150.678411,184.873469 154.836973,181.326531 C158.995535,177.779592 163.626149,174.883673 168.481552,172.661224 C173.336954,170.438776 179.113983,168.665306 185.587852,167.340816 C192.061722,166.218367 198.760378,165.342857 205.481514,164.669388 C212.18017,164.220408 219.598146,163.995918 228.162535,163.995918 L246.055591,163.995918 L246.055591,195.514286 C246.055591,197.736735 246.752431,199.510204 248.370899,201.059184 C250.214153,202.608163 252.079886,203.506122 254.372715,203.506122 C256.463236,203.506122 258.531277,202.608163 260.172223,201.059184 L326.102289,137.797959 C327.720757,136.24898 328.642384,134.47551 328.642384,132.253061 C328.642384,130.030612 327.720757,128.257143 326.102289,126.708163 L260.172223,63.4469388 C258.553756,61.8979592 256.463236,61 254.395194,61 C252.079886,61 250.236632,61.8979592 248.393377,63.4469388 C246.77491,64.9959184 246.07807,66.7693878 246.07807,68.9918367 L246.07807,100.510204 L228.162535,100.510204 C166.863084,100.510204 129.166282,117.167347 115.274437,150.459184 C110.666301,161.54898 108.350993,175.310204 108.350993,191.742857 C108.350993,205.279592 113.903236,223.912245 124.760454,247.438776 C125.00772,248.112245 125.457294,249.010204 126.154134,250.559184 Z" id="Shape" fill="#FFFFFF" transform="translate(218.496689, 160.000000) scale(-1, 1) translate(-218.496689, -160.000000) "></path>
</g>
</g>
<!-- File -->
<g id="file" stroke="#000" stroke-width="25" fill="#FFF" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<path d="M13 24.12v274.76c0 6.16 5.87 11.12 13.17 11.12H239c7.3 0 13.17-4.96 13.17-11.12V136.15S132.6 13 128.37 13H26.17C18.87 13 13 17.96 13 24.12z"/>
<path d="M129.37 13L129 113.9c0 10.58 7.26 19.1 16.27 19.1H249L129.37 13z"/>
</g>
<g id="file-shortcut" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="file-shortcut-group" transform="translate(13.000000, 13.000000)">
<g id="file-shortcut-shape" stroke="#000000" stroke-width="25" fill="#FFFFFF" stroke-linecap="round" stroke-linejoin="round">
<path d="M0,11.1214886 L0,285.878477 C0,292.039924 5.87498876,296.999983 13.1728373,296.999983 L225.997983,296.999983 C233.295974,296.999983 239.17082,292.039942 239.17082,285.878477 L239.17082,123.145388 C239.17082,123.145388 119.58541,2.84217094e-14 115.369423,2.84217094e-14 L13.1728576,2.84217094e-14 C5.87500907,-1.71479982e-05 0,4.96022995 0,11.1214886 Z" id="rect1171"></path>
<path d="M116.37005,0 L116,100.904964 C116,111.483663 123.258008,120 132.273377,120 L236,120 L116.37005,0 L116.37005,0 Z" id="rect1794"></path>
</g>
<path d="M47.803141,294.093878 C48.4999811,295.177551 48.9495553,296.095918 49.4216083,296.995918 C50.1184484,297.895918 50.5680227,298.630612 51.2873415,299.365306 C52.2089688,300.44898 53.3778619,301 54.7490634,301 C56.1427436,301 57.0643709,300.632653 57.761211,299.916327 C58.4580511,299.365306 58.9076254,298.465306 58.9076254,297.381633 C58.9076254,296.830612 58.9076254,295.930612 58.6828382,294.828571 C58.4355724,293.561224 58.2107852,292.844898 58.2107852,292.477551 C57.7387323,287.757143 57.5139451,283.753061 57.5139451,279.95102 C57.5139451,273.228571 58.4355724,267.057143 59.8292526,261.602041 C61.44772,256.165306 63.5382403,251.610204 66.0783349,247.62449 C68.8656954,243.620408 72.3274172,240.35102 76.4859792,237.44898 C80.6445412,234.546939 85.2751561,232.177551 90.1305582,230.359184 C94.9859603,228.540816 100.76299,227.089796 107.236859,226.006122 C113.710728,225.087755 120.409385,224.371429 127.13052,223.820408 C133.829177,223.453061 141.247152,223.269388 149.811542,223.269388 L167.704598,223.269388 L167.704598,249.057143 C167.704598,250.87551 168.401438,252.326531 170.019905,253.593878 C171.86316,254.861224 173.728893,255.595918 176.021722,255.595918 C178.112242,255.595918 180.180284,254.861224 181.82123,253.593878 L247.751296,201.834694 C249.369763,200.567347 250.291391,199.116327 250.291391,197.297959 C250.291391,195.479592 249.369763,194.028571 247.751296,192.761224 L181.82123,141.002041 C180.202763,139.734694 178.112242,139 176.044201,139 C173.728893,139 171.885639,139.734694 170.042384,141.002041 C168.423917,142.269388 167.727077,143.720408 167.727077,145.538776 L167.727077,171.326531 L149.811542,171.326531 C88.5120908,171.326531 50.8152886,184.955102 36.9234437,212.193878 C32.3153075,221.267347 30,232.526531 30,245.971429 C30,257.046939 35.5522422,272.291837 46.4094607,291.540816 C46.6567266,292.091837 47.1063009,292.826531 47.803141,294.093878 Z" id="Shape-Copy" fill="#000000" fill-rule="nonzero" transform="translate(140.145695, 220.000000) scale(-1, 1) translate(-140.145695, -220.000000) "></path>
</g>
</g>
<!-- Up arrow -->
<g id="up-arrow" transform="translate(-279.22 -208.12)">
<path transform="matrix(.22413 0 0 .12089 335.67 164.35)" stroke-width="0" d="m-194.17 412.01h-28.827-28.827l14.414-24.965 14.414-24.965 14.414 24.965z"/>
</g>
<!-- Down arrow -->
<g id="down-arrow" transform="translate(-279.22 -208.12)">
<path transform="matrix(.22413 0 0 -.12089 335.67 257.93)" stroke-width="0" d="m-194.17 412.01h-28.827-28.827l14.414-24.965 14.414-24.965 14.414 24.965z"/>
</g>
</defs>
</svg>
<header>
<h1>
{{range $i, $crumb := .Breadcrumbs}}<a href="{{html $crumb.Link}}">{{html $crumb.Text}}</a>{{if ne $i 0}}/{{end}}{{end}}
</h1>
</header>
<main>
<div class="meta">
<div id="summary">
<span class="meta-item"><b>{{.NumDirs}}</b> director{{if eq 1 .NumDirs}}y{{else}}ies{{end}}</span>
<span class="meta-item"><b>{{.NumFiles}}</b> file{{if ne 1 .NumFiles}}s{{end}}</span>
{{- if ne 0 .ItemsLimitedTo}}
<span class="meta-item">(of which only <b>{{.ItemsLimitedTo}}</b> are displayed)</span>
{{- end}}
<span class="meta-item"><input type="text" placeholder="filter" id="filter" onkeyup='filter()'></span>
</div>
</div>
<div class="listing">
<table aria-describedby="summary">
<thead>
<tr>
<th>
{{- if and (eq .Sort "namedirfirst") (ne .Order "desc")}}
<a href="?sort=namedirfirst&order=desc{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}" class="icon"><svg width="1em" height=".5em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#up-arrow"></use></svg></a>
{{- else if and (eq .Sort "namedirfirst") (ne .Order "asc")}}
<a href="?sort=namedirfirst&order=asc{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}" class="icon"><svg width="1em" height=".5em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#down-arrow"></use></svg></a>
{{- else}}
<a href="?sort=namedirfirst&order=asc{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}" class="icon sort"><svg class="top" width="1em" height=".5em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#up-arrow"></use></svg><svg class="bottom" width="1em" height=".5em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#down-arrow"></use></svg></a>
{{- end}}
{{- if and (eq .Sort "name") (ne .Order "desc")}}
<a href="?sort=name&order=desc{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}">Name <svg width="1em" height=".5em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#up-arrow"></use></svg></a>
{{- else if and (eq .Sort "name") (ne .Order "asc")}}
<a href="?sort=name&order=asc{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}">Name <svg width="1em" height=".5em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#down-arrow"></use></svg></a>
{{- else}}
<a href="?sort=name&order=asc{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}">Name</a>
{{- end}}
</th>
<th>
{{- if and (eq .Sort "size") (ne .Order "desc")}}
<a href="?sort=size&order=desc{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}">Size <svg width="1em" height=".5em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#up-arrow"></use></svg></a>
{{- else if and (eq .Sort "size") (ne .Order "asc")}}
<a href="?sort=size&order=asc{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}">Size <svg width="1em" height=".5em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#down-arrow"></use></svg></a>
{{- else}}
<a href="?sort=size&order=asc{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}">Size</a>
{{- end}}
</th>
<th class="hideable">
{{- if and (eq .Sort "time") (ne .Order "desc")}}
<a href="?sort=time&order=desc{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}">Modified <svg width="1em" height=".5em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#up-arrow"></use></svg></a>
{{- else if and (eq .Sort "time") (ne .Order "asc")}}
<a href="?sort=time&order=asc{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}">Modified <svg width="1em" height=".5em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#down-arrow"></use></svg></a>
{{- else}}
<a href="?sort=time&order=asc{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}">Modified</a>
{{- end}}
</th>
</tr>
</thead>
<tbody>
{{- if .CanGoUp}}
<tr>
<td>
<a href="..">
<span class="goup">Go up</span>
</a>
</td>
<td>&mdash;</td>
<td class="hideable">&mdash;</td>
</tr>
{{- end}}
{{- range .Items}}
<tr class="file">
<td>
<a href="{{html .URL}}">
{{- if .IsDir}}
<svg width="1.5em" height="1em" version="1.1" viewBox="0 0 317 259"><use xlink:href="#folder{{if .IsSymlink}}-shortcut{{end}}"></use></svg>
{{- else}}
<svg width="1.5em" height="1em" version="1.1" viewBox="0 0 265 323"><use xlink:href="#file{{if .IsSymlink}}-shortcut{{end}}"></use></svg>
{{- end}}
<span class="name">{{html .Name}}</span>
</a>
</td>
{{- if .IsDir}}
<td data-order="-1">&mdash;</td>
{{- else}}
<td data-order="{{.Size}}">{{.HumanSize}}</td>
{{- end}}
<td class="hideable"><time datetime="{{.HumanModTime "2006-01-02T15:04:05Z"}}">{{.HumanModTime "01/02/2006 03:04:05 PM -07:00"}}</time></td>
</tr>
{{- end}}
</tbody>
</table>
</div>
</main>
<footer>
Served with <a rel="noopener noreferrer" href="https://caddyserver.com">Caddy</a>
</footer>
<script>
var filterEl = document.getElementById('filter');
filterEl.focus();
function filter() {
var q = filterEl.value.trim().toLowerCase();
var elems = document.querySelectorAll('tr.file');
elems.forEach(function(el) {
if (!q) {
el.style.display = '';
return;
}
var nameEl = el.querySelector('.name');
var nameVal = nameEl.textContent.trim().toLowerCase();
if (nameVal.indexOf(q) !== -1) {
el.style.display = '';
} else {
el.style.display = 'none';
}
});
}
function localizeDatetime(e, index, ar) {
if (e.textContent === undefined) {
return;
}
var d = new Date(e.getAttribute('datetime'));
if (isNaN(d)) {
d = new Date(e.textContent);
if (isNaN(d)) {
return;
}
}
e.textContent = d.toLocaleString();
}
var timeList = Array.prototype.slice.call(document.getElementsByTagName("time"));
timeList.forEach(localizeDatetime);
</script>
</body>
</html>`
+81
View File
@@ -0,0 +1,81 @@
package browse
import (
"io/ioutil"
"os"
"path/filepath"
"strconv"
"testing"
"time"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
)
func TestSetup(t *testing.T) {
tempDirPath := os.TempDir()
_, err := os.Stat(tempDirPath)
if err != nil {
t.Fatalf("BeforeTest: Failed to find an existing directory for testing! Error was: %v", err)
}
nonExistentDirPath := 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 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-existent template
{"browse . " + nonExistentDirPath, nil, true},
// test case #4 tests detection of duplicate pathscopes
{"browse " + tempDirPath + "\n browse " + tempDirPath, nil, true},
} {
c := caddy.NewTestController("http", test.input)
err := setup(c)
if err != nil && !test.shouldErr {
t.Errorf("Test case #%d received an error of %v", i, err)
}
if test.expectedPathScope == nil {
continue
}
mids := httpserver.GetConfig(c).Middleware()
mid := mids[len(mids)-1]
receivedConfigs := mid(nil).(Browse).Configs
for j, config := range receivedConfigs {
if config.PathScope != test.expectedPathScope[j] {
t.Errorf("Test case #%d expected a pathscope of %v, but got %v", i, test.expectedPathScope, config.PathScope)
}
}
}
// test case #6 tests startup with missing root directory in combination with default browse settings
controller := caddy.NewTestController("http", "browse")
cfg := httpserver.GetConfig(controller)
// Make sure non-existent root path doesn't return error
cfg.Root = nonExistentDirPath
err = setup(controller)
if err != nil {
t.Errorf("Test for non-existent browse path received an error, but shouldn't have: %v", err)
}
}
+1
View File
@@ -0,0 +1 @@
<h1>Header</h1>
+13
View File
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<title>Template</title>
</head>
<body>
{{.Include "header.html"}}
<h1>{{.Path}}</h1>
{{range .Items}}
<a href="{{.URL}}">{{.Name}}</a><br>
{{end}}
</body>
</html>

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